Podpora textur a texturování v OpenGL
Co se dozvíte v článku
- Podpora textur a texturování v OpenGL
- Omezení texturování v OpenGL
- První demonstrační příklad: zjištění maximální velikosti textury prakticky
- Postup při texturování
- Nastavení způsobu nanášení textury na vykreslované povrchy
- Další parametry ovlivňující způsob nanášení textur
- Vykreslení scény se zadanými texturovacími souřadnicemi pro každý vrchol
- Druhý demonstrační příklad: vykreslení trojúhelníku s nanesenou texturou
- Vykreslení čtvercové plochy s nanesenou texturou
- Třetí demonstrační příklad: vykreslení čtvercové plochy s nanesenou texturou
- Vygenerování procedurální textury
- Využití procedurální textury
- Čtvrtý demonstrační příklad: nanesení procedurální textury na plochu
- Modifikace uv-souřadnic v prostoru textury
- Pátý demonstrační příklad: zobrazení otexturovaného čtverce s modifikovanými uv-souřadnicemi
- Textury uložené v externích souborech (PNG, JPEG)
- Realizace načtení rastrového obrázku
- Šestý demonstrační příklad: čtverec s texturou načtenou z externího obrázku
- Repositář s demonstračními příklady
- Odkazy na Internetu
Jak v klasickém OpenGL, tak i v OpenGL ES jsou podporovány pouze rastrové textury, které mohou být buď jednodimenzionální, dvoudimenzionální nebo třídimenzionální. V případě jednodimenzionálních textur se prakticky jedná o pruh texelů, pomocí něhož se dají realizovat různě komplikované barvové přechody. Dvoudimenzionální textury (tedy vlastně bitmapy a pixmapy) jsou v současnosti zdaleka nejpoužívanější a jsou podporovány na naprosté většině grafických akcelerátorů, včetně historických kousků.
Třídimenzionální textury (tj. objemové či voxelové textury) se používají především ve specializovaných aplikacích (medicína apod.), v nichž je zapotřebí zobrazovat objemová data. Starší GPU tyto textury nepodporují. A pokud se vrátíme k technologiím WebGL a OpenGL ES: třídimenzionální textury jsou podporovány až v OpenGL ES 3.0, nikoli v 2.0 (i když existují volitelná rozšíření i pro tyto typy textur).
Největší podpora ze strany jak historických, tak i moderních grafických akcelerátorů je tedy u dvoudimenzionálních textur, které také splňují značnou část nároků programátora počítačové grafiky. Především tímto typem textur se budeme zabývat v dalším textu. V OpenGL (i OpenGL ES) lze také zvolit různé filtrace textur, režimy mapování textur na plošky, multitexturování a další grafické efekty, které si probereme jak v dnešním článku, tak i v článku navazujícím.
Musíme si však uvědomit, že pokud použijeme některý grafický efekt, který není grafickým akcelerátorem přímo podporován, dojde k výpočtům pomocí hlavního procesoru počítače, což značně zpomaluje celý systém. Proto je nejprve vhodné zjistit, které efekty jsou na grafickém akcelerátoru podporovány a případně dát uživateli naší aplikace na výběr mezi kvalitou a rychlostí zobrazení. Všechny operace probírané dnes jsou podporovány všemi dnes používanými GPU. Příště upozorním na některé pokročilejší operace, které už podporovány být nemusí (ovšem lze je zařadit například do fragment shaderu – to je však téma na samostatný článek).
Omezení texturování v OpenGL
V OpenGL (ES) jsme při použití rastrových (2D) textur omezeni poměrně minimálně. První omezení spočívá ve faktu, že texturování je podporováno pouze pro plnobarevné (true color) režimy. V indexových režimech (s barevnou paletou) není povoleno. Toto omezení však dnes již nemá prakticky žádný význam, protože paletové režimy se používají minimálně (otázkou je, zde ještě vůbec) a velká část moderních grafických akcelerátorů je ani nemá implementované. Další omezení spočívá v maximální povolené velikosti textury a rozlišeních textury.
Všechny textury by měly mít rozlišení, které je mocninou čísla 2, například 256×256 texelů. Lze se ale setkat i s texturami, které tento požadavek nesplňují. Takové textury jsou použitelné jen v případě, že GPU podporuje rozšíření OES_texture_npot (bez tohoto rozšíření nebude možné realizovat mipmapy apod.). Naprosto všechny implementace OpenGL ES by měly podporovat textury alespoň do velikosti 64×64 pixelů (což je pochopitelně dosti omezující), ovšem většinou jsou podporovány i mnohem větší textury). Maximální podporovanou velikost textur lze zjistit pomocí příkazů OpenGL.
První demonstrační příklad: zjištění maximální velikosti textury prakticky
Maximální rozlišení textury je možné zjistit přímo z prostředí webového prohlížeče, což je ukázáno v dnešním prvním demonstračním příkladu. Skript nejprve zjistí velikost textury a následně ji zapíše zpět do webové stránky:
function print(message) {
console.log(message);
}
// reference na objekt představující kreslicí plátno umístěné na webové stránce
const canvas = document.getElementById("web_gl_canvas");
if (!canvas) {
throw new Error("Canvas with id 'web_gl_canvas' not found");
}
print("Canvas object retrieved");
// vytvoření a získání kontextu WebGL (3D grafika)
const gl = canvas.getContext("webgl");
if (!gl) {
throw new Error("WebGL is not supported or context creation failed");
}
print("WebGL context created");
// přečtení maximálního rozlišení textury
const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
// zápis zpět na webovou stránku
document.getElementById("tsize").innerHTML = maxTextureSize + "×" + maxTextureSize + " texels";
Do webové stránky, která tento skript spouští, je nutné pouze doplnit element s ID tsize:
<!doctype html>
<html>
<head>
<title>WebGL: maximum supported texture size</title>
</head>
<body>
<p>Maximum texture size: <span id="tsize" style="color:red"></span></p>
<!-- kreslicí plátno -->
<canvas id="web_gl_canvas" width="256" height="256"></canvas>
<!-- skript, který kreslicí plátno využije -->
<script type="application/javascript" src="texsize.js"></script>
</body>
</html>
Maximální velikost textury lze ověřit na adrese https://tisnik.github.io/temp/texsize.html.
V mém případě se vypíše:
Maximum texture size: 8192×8192 texels
Postup při texturování
Vykreslování 3D objektů, na jejichž povrch je nanesena 2D textura, je poměrně složité na počet a vzájemnou provázanost kroků, které je potřeba v programu provést:
- Vytvoření rastrové předlohy textury (procedurální textury) nebo její načtení ze souboru.
- Vytvoření nového texturovacího objektu, přiřazení textury tomuto objektu a nastavení formátu textury.
- Nastavení způsobu nanášení textury na vykreslované povrchy.
- Zapnutí (povolení) nanášení textur.
- Vykreslení scény se zadanými texturovacími souřadnicemi (uv-souřadnice) pro každý vrchol trojúhelníka.
Jednotlivé kroky si podrobněji popíšeme v následujících odstavcích.
Vytvoření popř. načtení rastrové předlohy textury
Tento krok musí implementovat sám autor programu, protože grafická knihovna OpenGL ES nenabízí prakticky téměř žádnou podporu ani pro vytvoření (výpočet) rastrových textur ani pro jejich načtení ze souboru. V závěru článku si ukážeme načtení textury z externího obrázku s využitím dalších funkcí nabízených webovým prohlížečem. Další možností je provedení výpočtu procedurální textury přímo v aplikaci. Tento způsob sice poněkud zdrží start aplikace, ale na druhou stranu se textury nemusí ukládat na disk, takže se může zjednodušit distribuce programu či omezit množství požadavků provedených webovým prohlížečem. Na internetu lze nalézt velké množství algoritmů pro vytváření procedurálních textur (my dnes použijeme jednoduchý výpočet šachovnice). Texturu lze také vytvořit přečtením vykresleného obrázku (celé scény či její části) přímo z framebufferu, což je umožněno příkazem gl.CopyTexImage2D().
// textura (prozatím primitivní)
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
// primitivní textura 2x2 texely
const texels = new Uint8Array([
255, 0, 0, 255, // red
0, 255, 0, 255, // green
0, 0, 255, 255, // blue
255, 255, 0, 255, // yellow
]);
Složitější problém nastane při použití mipmappingu, kdy je zapotřebí specifikovat textury v několika rozlišeních. Tento problém lze opět řešit buď programově, nebo lze výpočet ponechat specializovaným funkcím, což je však téma pro navazující článek.
Vytvoření nového texturovacího objektu, přiřazení textury tomuto objektu, nastavení formátu textury
Popišme si ten nejjednodušší případ, ve kterém pro všechna vykreslovaná tělesa používáme pouze jednu texturu. V tomto případě musíme pouze nastavit základní parametry textury. Pokud by bylo nutné používat více textur (což je v reálných scénách pochopitelně více než pravděpodobné) musela by se aktivní textura vybírat podle čísla svého texturovacího objektu (ten se musí pro každou texturu nejprve vytvořit a teprve poté nastavovat formát textury). OpenGL ES podporuje celou řadu formátů textur, které se podle dimenze textury dají specifikovat příkazy:
gl.TexImage1D(
GLenum target,
GLint level,
GLint components,
GLsizei width,
GLint border,
GLenum format,
GLenum type,
const GLvoid *pixels
);
gl.TexImage2D(
GLenum target,
GLint level,
GLint components,
GLsizei width,
GLsizei height,
GLint border,
GLenum format,
GLenum type,
const GLvoid *pixels
);
Význam jednotlivých parametrů je následující:
- Parametrem target určujeme dimenzi textury. Pro jednodimenzionální texturu bude v tomto parametru předána hodnota GL_TEXTURE_1D, pro dvoudimenzionální GL_TEXTURE_2D atd. Pokud současně nastavíme parametry 1D a 2D textury, mají přednost parametry pro 2D texturu.
- Parametr level má svůj význam zejména při používání mipmappingu, tj. textur s více úrovněmi detailu (viz další pokračování tohoto seriálu). Pro běžnou texturu s jedním rozlišením tento parametr nastavujeme na nulovou hodnotu.
- Pomocí parametru components se specifikuje počet barevných složek použitých pro reprezentaci jednoho pixelu. Hodnota tohoto parametru může být 1, 2, 3 nebo 4. Hodnota 1 značí, že se používá pouze barevná složka R (red). Hodnotou dvě specifikujeme barevnou složku R a alfa složku A. Hodnota 3 představuje klasickou barevnou reprezentaci pixelu pomocí barevných složek R, G, B. Hodnotou 4 specifikujeme použití tří barevných složek R, G, B společně s alfa složkou A.
- Velikost (resp. rozlišení) textury se zadává v parametrech width a height. Pro 1D texturu se parametr height nezadává, protože je vždy jednotkový. Oba parametry musí mít hodnotu rovnu 2n+2(border), kde n je celé kladné číslo z rozsahu daného konkrétní implementací OpenGL ES a border je šířka okraje textury. Minimální rozlišení textury, kterou musí podporovat všechny implementace, je 64×64 pixelů bez okraje popř. 66×66 pixelů s okrajem. Pokud je jeden z parametrů width nebo height nastaven na nulu, je texturování zakázáno.
- Parametrem border se zadává šířka okraje textury v pixelech. Okraj může být použit například pro hladké navazování textur. Jeho použití si ukážeme v některém z následujících dílů. Pro běžně mapované textury se okraj nastavuje na nulovou hodnotu.
- V parametru format se předává formát dat použitý pro jednotlivé pixely textury. Tento parametr má stejný význam jako u již dříve probraných funkcí glDrawPixels() a glReadPixels(). Každá implementace by měla podporovat minimálně tyto formáty: GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE a GL_LUMINANCE_ALPHA, ale můžeme se setkat i s grafickými akcelerátory, které poslední dva formáty špatně interpretují (nenastaví například korektně všechny barevné složky).
- Parametr type, stejně jako parametr předchozí, má stejný význam jako u funkcí glDrawPixels() a glReadPixels(), tj. udává typ dat každé barevné složky pixelu. Mezi běžně podporované typy patří GL_BITMAP, GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT a GL_FLOAT. Jak je vidět, máme díky parametrům format a type značnou volnost při tvorbě a/nebo načítání textur ze souboru.
- V posledním parametru pixels je předáván ukazatel na rastrová data textury.
V příkladu použijeme:
gl.texImage2D(gl.TEXTURE_2D, // typ textury: zde 2D textura
0, // úroveň detailu
gl.RGBA, // počet barvových komponent (zde 4)
2, // horizontální rozlišení textury
2, // vertikální rozlišení textury
0, // šířka okraje textury
gl.RGBA, // formát uložení texelů
gl.UNSIGNED_BYTE, // typ každé barvové složky texelu
texels // pole texelů
);
Nastavení způsobu nanášení textury na vykreslované povrchy
Texturu lze na objekt nanášet více způsoby. Nejjednodušší a nejpoužívanější způsob spočívá v nanášení barev jednotlivých texelů přímo na zobrazovanou plochu – přímo se tím mění barvy pixelů zobrazované plochy. Druhý způsob moduluje barvu texelu barvou fragmentu, čehož lze použít například při kombinaci světelných efektů s texturou. Třetím způsobem je využití blendingu, tj. kombinace původní barvy pixelu s barvou texelu.
Texturování musíme nejprve zapnout, jinak se nebude provádět (v tomto případě by se při vykreslování plošek použily buď barvy jednotlivých vertexů, nebo by se spočítala globální barva). Současně se zapnutím texturování specifikujeme, kolik dimenzí budou mít vykreslované textury.
Texturování lze zapnout příkazem gl.Enable(), kterému předáme jako jediný parametr konstantu určující dimenzi textury, tedy GL_TEXTURE_1D, GL_TEXTURE_2D nebo GL_TEXTURE_3D. Opětovné vypnutí texturování lze provést příkazem glDisable() se stejnými parametry jako u předchozího příkazu.
Další parametry ovlivňující způsob nanášení textur
V demonstračních příkladech nastavujeme ještě čtyři další parametry, které ovlivňují způsob vykreslení jednotlivých fragmentů. Především lze specifikovat filtr (resp. algoritmus) použitý při přiblížení či oddálení od textury. Jedná se o stav, kdy je texel větší než pixel na obrazovce či naopak, kdy je texel menší. A taktéž lze určit, jak se změní způsob nanesení textury v případě, že uv-souřadnice nebudou spadat do intervalu 0 až 1. Prozatím ponecháme následující nastavení, ovšem v navazujícím článku si ukážeme vliv dalších voleb:
// způsob vykreslení textury gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
Vykreslení scény se zadanými texturovacími souřadnicemi pro každý vrchol
Vykreslení scény s texturovanými ploškami probíhá podobně jako u barevných plošek (trojúhelníků). Největší změnou je, že pro každý vrchol musíme k jeho 2D/3D/4D souřadnicím navíc specifikovat souřadnice v textuře. Každá textura má nezávisle na své velikosti souřadnice v rozsahu od 0.0 do 1.0, tj. 2D textura je chápána jako čtverec o straně délky 1. Souřadnice do textury však můžeme zadávat libovolně, protože díky opakování (pokud je zapnuto) se obrázek textury může na zobrazované ploše šachovnicově skládat, podobně jako například obrázky na pozadí HTML stránek:
// souřadnice vrcholů trojúhelníku + souřadnice v prostoru textury const vertexes = [ // x, y, u, v 0.0, height, 0.5, 0.0, min_x, min_y, 0.0, 1.0, max_x, min_y, 1.0, 1.0, ];
Vertexy a uv-souřadnice se předávají zvlášť (i když jsou uloženy v jediném poli). Povšimněte si roli parametru offset:
// seznam předávaný do vertex shaderu
gl.vertexAttribPointer(p,
2, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu+texturovacích souřadnic v bajtech
0 // offset
);
gl.enableVertexAttribArray(p);
a:
// seznam předávaný do vertex shaderu
gl.vertexAttribPointer(texLoc,
2, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu+texturovacích souřadnic v bajtech
2*Float32Array.BYTES_PER_ELEMENT // offset
);
gl.enableVertexAttribArray(texLoc);
Navíc je ovšem nutné pozměnit i shadery. Ty nyní nebudou pracovat s předávanou barvou, ale s uv-souřadnicemi v prostoru textury.
Vertex shader pouze předá uv-souřadnice v textuře pro další zpracování:
attribute vec2 position;
attribute vec2 texcoord;
varying vec2 vTexcoord;
void main() {
gl_Position = vec4(position, 0, 1);
vTexcoord = texcoord;
}
Fragment shader namísto nastavení konstantní barvy fragmentu tuto barvu získá z textury:
precision mediump float;
varying vec2 vTexcoord;
uniform sampler2D texture;
void main() {
gl_FragColor = texture2D(texture, vTexcoord);
}
Druhý demonstrační příklad: vykreslení trojúhelníku s nanesenou texturou
Úplný zdrojový kód dnešního druhého demonstračního příkladu, který je možné spustit z této adresy, vypadá následovně:
const vertexShaderSource = `
attribute vec2 position;
attribute vec2 texcoord;
varying vec2 vTexcoord;
void main() {
gl_Position = vec4(position, 0, 1);
vTexcoord = texcoord;
}
`
const fragmentShaderSource = `
precision mediump float;
varying vec2 vTexcoord;
uniform sampler2D texture;
void main() {
gl_FragColor = texture2D(texture, vTexcoord);
}
`
function print(message) {
console.log(message);
}
// reference na objekt představující kreslicí plátno umístěné na webové stránce
const canvas = document.getElementById("web_gl_canvas");
if (!canvas) {
throw new Error("Canvas with id 'web_gl_canvas' not found");
}
print("Canvas object retrieved");
// vytvoření a získání kontextu WebGL (3D grafika)
const gl = canvas.getContext("webgl");
if (!gl) {
throw new Error("WebGL is not supported or context creation failed");
}
print("WebGL context created");
// vytvoření vertex shaderu
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
if (!vertexShader) {
throw new Error("Failed to create vertex shader.");
}
print("Vertex shader created");
// překlad vertex shaderu
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
const log = gl.getShaderInfoLog(vertexShader);
gl.deleteShader(vertexShader);
throw new Error(`Vertex shader compile failed: ${log}`);
}
print("Vertex shader compiled");
// vytvoření fragment shaderu
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
if (!fragmentShader) {
throw new Error("Failed to create fragment shader.");
}
print("Fragment shader created");
// překlad fragment shaderu
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
const log = gl.getShaderInfoLog(fragmentShader);
gl.deleteShader(fragmentShader);
throw new Error(`fragment shader compile failed: ${log}`);
}
print("Fragment shader compiled");
// registrace obou shaderů
const program = gl.createProgram();
if (!program) {
throw new Error("Failed to create program.");
}
print("Program created");
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
print("Shaders attached to the program");
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const log = gl.getProgramInfoLog(program);
gl.deleteProgram(program);
throw new Error(`Program link failed: ${log}`);
}
print("Program linked");
const min_x = -0.9
const max_x = 0.9
const min_y = min_x
const max_y = max_x
const height = Math.sqrt(3) / 2
// souřadnice vrcholů trojúhelníku + souřadnice v prostoru textury
const vertexes = [
// x, y, u, v
0.0, height, 0.5, 0.0,
min_x, min_y, 0.0, 1.0,
max_x, min_y, 1.0, 1.0,
];
// buffer, do kterého se překopírují souřadnice bodu
const vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
throw new Error("Failed to create buffer.");
}
print("Vertex buffer created");
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
const data = new Float32Array(vertexes);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
print("Vertex buffer filled-in by vertexes");
// navázání na parametr předávaný do vertex shaderu
const p = gl.getAttribLocation(program, "position");
if (p === -1) {
throw new Error("Attribute 'position' not found in shader program");
}
print("Attribute 'position' found in shader program")
// navázání na parametr předávaný do vertex shaderu
const texLoc = gl.getAttribLocation(program, "texcoord");
if (p === -1) {
throw new Error("Attribute 'texcoord' not found in shader program");
}
print("Attribute 'texcoord' found in shader program")
// seznam předávaný do vertex shaderu
gl.vertexAttribPointer(p,
2, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu+texturovacích souřadnic v bajtech
0 // offset
);
gl.enableVertexAttribArray(p);
// seznam předávaný do vertex shaderu
gl.vertexAttribPointer(texLoc,
2, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu+texturovacích souřadnic v bajtech
2*Float32Array.BYTES_PER_ELEMENT // offset
);
gl.enableVertexAttribArray(texLoc);
// textura (prozatím primitivní)
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
// primitivní textura 2x2 texely
const texels = new Uint8Array([
255, 0, 0, 255, // red
0, 255, 0, 255, // green
0, 0, 255, 255, // blue
255, 255, 0, 255, // yellow
]);
gl.texImage2D(gl.TEXTURE_2D, // typ textury: zde 2D textura
0, // úroveň detailu
gl.RGBA, // počet barvových komponent (zde 4)
2, // horizontální rozlišení textury
2, // vertikální rozlišení textury
0, // šířka okraje textury
gl.RGBA, // formát uložení texelů
gl.UNSIGNED_BYTE, // typ každé barvové složky texelu
texels // pole texelů
);
// způsob vykreslení textury
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// navázání na atribut ve shaderech
const samplerLoc = gl.getUniformLocation(program, "texture");
gl.uniform1i(samplerLoc, 0);
// transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
gl.viewport(0, 0, canvas.width, canvas.height);
// nastavení barvy pro vymazání barvového bufferu
gl.clearColor(0, 0, 0, 1);
// vymazání barvového bufferu
gl.clear(gl.COLOR_BUFFER_BIT);
// spuštění programu
gl.useProgram(program);
// vykreslení trojúhelníku
gl.drawArrays(gl.TRIANGLES, 0, 3);
Vykreslení čtvercové plochy s nanesenou texturou
Pro úplnost se ještě pokusme vykreslit čtvercovou plochu s nanesenou texturou. Čtvercové plochy jsou z pohledu texturování ideální, protože i samotná 2D textura je reprezentována čtvercovým rastrovým obrázkem. Čtverec vznikne složením dvou trojúhelníků, takže si jen musíme dát pozor na pořadí souřadnic těchto trojúhelníků a na odpovídající souřadnice v prostoru textury (uv-souřadnice). Jedno z možných uspořádání dvojice trojúhelníků může vypadat následovně:
// souřadnice vrcholů trojúhelníku + souřadnice v prostoru textury const vertexes = [ // x, y, u, v min_x, min_y, 0.0, 0.0, min_x, max_y, 0.0, 1.0, max_x, max_y, 1.0, 1.0, min_x, min_y, 0.0, 0.0, max_x, max_y, 1.0, 1.0, max_x, min_y, 1.0, 0.0, ];
Textura bude pořád obsahovat pouze 2×2=4 texely:
// primitivní textura 2x2 texely
const texels = new Uint8Array([
255, 0, 0, 255, // red
0, 255, 0, 255, // green
0, 0, 255, 255, // blue
255, 255, 0, 255, // yellow
]);
Poslední úprava spočívá v tom, že do grafické pipeline musíme přenést šestici vrcholů (tvořících dva samostatné trojúhelníky):
// vykreslení dvojice trojúhelníků gl.drawArrays(gl.TRIANGLES, 0, 6);
Třetí demonstrační příklad: vykreslení čtvercové plochy s nanesenou texturou
Dnešní třetí demonstrační příklad vykreslí čtverec složený ze dvou trojúhelníků. Jeho chování si lze ověřit na adrese https://tisnik.github.io/temp/textures2.html:
const vertexShaderSource = `
attribute vec2 position;
attribute vec2 texcoord;
varying vec2 vTexcoord;
void main() {
gl_Position = vec4(position, 0, 1);
vTexcoord = texcoord;
}
`
const fragmentShaderSource = `
precision mediump float;
varying vec2 vTexcoord;
uniform sampler2D texture;
void main() {
gl_FragColor = texture2D(texture, vTexcoord);
}
`
function print(message) {
console.log(message);
}
// reference na objekt představující kreslicí plátno umístěné na webové stránce
const canvas = document.getElementById("web_gl_canvas");
if (!canvas) {
throw new Error("Canvas with id 'web_gl_canvas' not found");
}
print("Canvas object retrieved");
// vytvoření a získání kontextu WebGL (3D grafika)
const gl = canvas.getContext("webgl");
if (!gl) {
throw new Error("WebGL is not supported or context creation failed");
}
print("WebGL context created");
// vytvoření vertex shaderu
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
if (!vertexShader) {
throw new Error("Failed to create vertex shader.");
}
print("Vertex shader created");
// překlad vertex shaderu
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
const log = gl.getShaderInfoLog(vertexShader);
gl.deleteShader(vertexShader);
throw new Error(`Vertex shader compile failed: ${log}`);
}
print("Vertex shader compiled");
// vytvoření fragment shaderu
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
if (!fragmentShader) {
throw new Error("Failed to create fragment shader.");
}
print("Fragment shader created");
// překlad fragment shaderu
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
const log = gl.getShaderInfoLog(fragmentShader);
gl.deleteShader(fragmentShader);
throw new Error(`fragment shader compile failed: ${log}`);
}
print("Fragment shader compiled");
// registrace obou shaderů
const program = gl.createProgram();
if (!program) {
throw new Error("Failed to create program.");
}
print("Program created");
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
print("Shaders attached to the program");
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const log = gl.getProgramInfoLog(program);
gl.deleteProgram(program);
throw new Error(`Program link failed: ${log}`);
}
print("Program linked");
const min_x = -0.9
const max_x = 0.9
const min_y = min_x
const max_y = max_x
// souřadnice vrcholů trojúhelníku + souřadnice v prostoru textury
const vertexes = [
// x, y, u, v
min_x, min_y, 0.0, 0.0,
min_x, max_y, 0.0, 1.0,
max_x, max_y, 1.0, 1.0,
min_x, min_y, 0.0, 0.0,
max_x, max_y, 1.0, 1.0,
max_x, min_y, 1.0, 0.0,
];
// buffer, do kterého se překopírují souřadnice bodu
const vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
throw new Error("Failed to create buffer.");
}
print("Vertex buffer created");
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
const data = new Float32Array(vertexes);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
print("Vertex buffer filled-in by vertexes");
// navázání na parametr předávaný do vertex shaderu
const p = gl.getAttribLocation(program, "position");
if (p === -1) {
throw new Error("Attribute 'position' not found in shader program");
}
print("Attribute 'position' found in shader program")
// navázání na parametr předávaný do vertex shaderu
const texLoc = gl.getAttribLocation(program, "texcoord");
if (p === -1) {
throw new Error("Attribute 'texcoord' not found in shader program");
}
print("Attribute 'texcoord' found in shader program")
// seznam předávaný do vertex shaderu
gl.vertexAttribPointer(p,
2, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu+texturovacích souřadnic v bajtech
0 // offset
);
gl.enableVertexAttribArray(p);
// seznam předávaný do vertex shaderu
gl.vertexAttribPointer(texLoc,
2, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu+texturovacích souřadnic v bajtech
2*Float32Array.BYTES_PER_ELEMENT // offset
);
gl.enableVertexAttribArray(texLoc);
// textura (prozatím primitivní)
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
// primitivní textura 2x2 texely
const texels = new Uint8Array([
255, 0, 0, 255, // red
0, 255, 0, 255, // green
0, 0, 255, 255, // blue
255, 255, 0, 255, // yellow
]);
gl.texImage2D(gl.TEXTURE_2D, // typ textury: zde 2D textura
0, // úroveň detailu
gl.RGBA, // počet barvových komponent (zde 4)
2, // horizontální rozlišení textury
2, // vertikální rozlišení textury
0, // šířka okraje textury
gl.RGBA, // formát uložení texelů
gl.UNSIGNED_BYTE, // typ každé barvové složky texelu
texels // pole texelů
);
// způsob vykreslení textury
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// navázání na atribut ve shaderech
const samplerLoc = gl.getUniformLocation(program, "texture");
gl.uniform1i(samplerLoc, 0);
// transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
gl.viewport(0, 0, canvas.width, canvas.height);
// nastavení barvy pro vymazání barvového bufferu
gl.clearColor(0, 0, 0, 1);
// vymazání barvového bufferu
gl.clear(gl.COLOR_BUFFER_BIT);
// spuštění programu
gl.useProgram(program);
// vykreslení dvojice trojúhelníků
gl.drawArrays(gl.TRIANGLES, 0, 6);
Vygenerování procedurální textury
Textura, kterou jsme až doposud používali, měla rozlišení pouze 2×2 texely. Reálné textury mají pochopitelně mnohem větší rozlišení. V dalším demonstračním příkladu z tohoto důvodu vytvoříme procedurální texturu, která bude obsahovat šachovnici zkombinovanou s barvovým přechodem (gradientem). Texely budou mít formát RGBA (tedy včetně alfa kanálu), i když prozatím nastavíme hodnotu alfa na konstantu 255 pro všechny texely. Funkce, která vytvoří jednorozměrné (!) pole se všemi texely, bude vypadat následovně:
function makeTexture(width, height) {
const array = new Array(width*height*4);
let index = 0;
for (j=0; j<height; j++) {
for (i=0; i<width; i++) {
const c = ((i&0x20)==0^(j&0x20)==0) * 255;
array[index++] = c;
array[index++] = i;
array[index++] = j;
array[index++] = 255; // alfa složka
}
}
return array;
}
Využití procedurální textury
Výše uvedenou funkci využijeme pro konstrukci pole s barvami texelů:
// textura textureArray = makeTexture(textureWidth, textureHeight);
Toto obecně heterogenní pole převedeme na pole hodnot typu byte (bez znaménka):
const texels = new Uint8Array(textureArray);
Výsledné pole typu Uint8Array lze přímo použít pro vytvoření textury, což se provede následovně:
gl.texImage2D(gl.TEXTURE_2D, // typ textury: zde 2D textura
0, // úroveň detailu
gl.RGBA, // počet barvových komponent (zde 4)
textureWidth, // horizontální rozlišení textury
textureHeight, // vertikální rozlišení textury
0, // šířka okraje textury
gl.RGBA, // formát uložení texelů
gl.UNSIGNED_BYTE, // typ každé barvové složky texelu
texels // pole texelů
);
// souřadnice vrcholů trojúhelníku + souřadnice v prostoru textury const vertexes = [ // x, y, u, v min_x, min_y, 0.0, 0.0, min_x, max_y, 0.0, 1.0, max_x, max_y, 1.0, 1.0, min_x, min_y, 0.0, 0.0, max_x, max_y, 1.0, 1.0, max_x, min_y, 1.0, 0.0, ];
Čtvrtý demonstrační příklad: nanesení procedurální textury na plochu
Procedurální textura s obarvenou šachovnicí je použita v dnešním čtvrtém demonstračním příkladu, který si můžete odzkoušet na stránce https://tisnik.github.io/temp/textures3.html. Následuje výpis úplného zdrojového kódu tohoto příkladu:
const vertexShaderSource = `
attribute vec2 position;
attribute vec2 texcoord;
varying vec2 vTexcoord;
void main() {
gl_Position = vec4(position, 0, 1);
vTexcoord = texcoord;
}
`
const fragmentShaderSource = `
precision mediump float;
varying vec2 vTexcoord;
uniform sampler2D texture;
void main() {
gl_FragColor = texture2D(texture, vTexcoord);
}
`
function print(message) {
console.log(message);
}
function makeTexture(width, height) {
const array = new Array(width*height*4);
let index = 0;
for (j=0; j<height; j++) {
for (i=0; i<width; i++) {
const c = ((i&0x20)==0^(j&0x20)==0) * 255;
array[index++] = c;
array[index++] = i;
array[index++] = j;
array[index++] = 255; // alfa slozka
}
}
return array;
}
// reference na objekt představující kreslicí plátno umístěné na webové stránce
const canvas = document.getElementById("web_gl_canvas");
if (!canvas) {
throw new Error("Canvas with id 'web_gl_canvas' not found");
}
print("Canvas object retrieved");
// vytvoření a získání kontextu WebGL (3D grafika)
const gl = canvas.getContext("webgl");
if (!gl) {
throw new Error("WebGL is not supported or context creation failed");
}
print("WebGL context created");
// vytvoření vertex shaderu
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
if (!vertexShader) {
throw new Error("Failed to create vertex shader.");
}
print("Vertex shader created");
// překlad vertex shaderu
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
const log = gl.getShaderInfoLog(vertexShader);
gl.deleteShader(vertexShader);
throw new Error(`Vertex shader compile failed: ${log}`);
}
print("Vertex shader compiled");
// vytvoření fragment shaderu
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
if (!fragmentShader) {
throw new Error("Failed to create fragment shader.");
}
print("Fragment shader created");
// překlad fragment shaderu
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
const log = gl.getShaderInfoLog(fragmentShader);
gl.deleteShader(fragmentShader);
throw new Error(`fragment shader compile failed: ${log}`);
}
print("Fragment shader compiled");
// registrace obou shaderů
const program = gl.createProgram();
if (!program) {
throw new Error("Failed to create program.");
}
print("Program created");
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
print("Shaders attached to the program");
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const log = gl.getProgramInfoLog(program);
gl.deleteProgram(program);
throw new Error(`Program link failed: ${log}`);
}
print("Program linked");
const min_x = -0.9
const max_x = 0.9
const min_y = min_x
const max_y = max_x
// souřadnice vrcholů trojúhelníku + souřadnice v prostoru textury
const vertexes = [
// x, y, u, v
min_x, min_y, 0.0, 0.0,
min_x, max_y, 0.0, 1.0,
max_x, max_y, 1.0, 1.0,
min_x, min_y, 0.0, 0.0,
max_x, max_y, 1.0, 1.0,
max_x, min_y, 1.0, 0.0,
];
// buffer, do kterého se překopírují souřadnice bodu
const vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
throw new Error("Failed to create buffer.");
}
print("Vertex buffer created");
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
const data = new Float32Array(vertexes);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
print("Vertex buffer filled-in by vertexes");
// navázání na parametr předávaný do vertex shaderu
const p = gl.getAttribLocation(program, "position");
if (p === -1) {
throw new Error("Attribute 'position' not found in shader program");
}
print("Attribute 'position' found in shader program")
// navázání na parametr předávaný do vertex shaderu
const texLoc = gl.getAttribLocation(program, "texcoord");
if (p === -1) {
throw new Error("Attribute 'texcoord' not found in shader program");
}
print("Attribute 'texcoord' found in shader program")
// seznam předávaný do vertex shaderu
gl.vertexAttribPointer(p,
2, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu+texturovacích souřadnic v bajtech
0 // offset
);
gl.enableVertexAttribArray(p);
// seznam předávaný do vertex shaderu
gl.vertexAttribPointer(texLoc,
2, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu+texturovacích souřadnic v bajtech
2*Float32Array.BYTES_PER_ELEMENT // offset
);
gl.enableVertexAttribArray(texLoc);
// textura (prozatím primitivní)
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
const textureWidth = 256
const textureHeight = 256
// textura
textureArray = makeTexture(textureWidth, textureHeight);
const texels = new Uint8Array(textureArray);
gl.texImage2D(gl.TEXTURE_2D, // typ textury: zde 2D textura
0, // úroveň detailu
gl.RGBA, // počet barvových komponent (zde 4)
textureWidth, // horizontální rozlišení textury
textureHeight, // vertikální rozlišení textury
0, // šířka okraje textury
gl.RGBA, // formát uložení texelů
gl.UNSIGNED_BYTE, // typ každé barvové složky texelu
texels // pole texelů
);
// způsob vykreslení textury
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// navázání na atribut ve shaderech
const samplerLoc = gl.getUniformLocation(program, "texture");
gl.uniform1i(samplerLoc, 0);
// transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
gl.viewport(0, 0, canvas.width, canvas.height);
// nastavení barvy pro vymazání barvového bufferu
gl.clearColor(0, 0, 0, 1);
// vymazání barvového bufferu
gl.clear(gl.COLOR_BUFFER_BIT);
// spuštění programu
gl.useProgram(program);
// vykreslení dvojice trojúhelníků
gl.drawArrays(gl.TRIANGLES, 0, 6);
Modifikace uv-souřadnic v prostoru textury
V předchozím textu jsme si řekli, že souřadnice texelů v textuře se značí písmeny u a v a že tyto souřadnice leží v rozsahu 0,0 až 1,1 (včetně obou mezních hodnot). To konkrétně znamená, že texel ležící v levém horním rohu bude mít souřadnice [0, 0], texel v pravém horním rohu souřadnice [1, 0] a texel v pravém dolním rohu pak souřadnice [1, 1]. Co se však stane v případě, že při definici vrcholů trojúhelníků uvedeme větší hodnotu těchto souřadnic? To si ostatně můžeme velmi snadno otestovat:
// souřadnice vrcholů trojúhelníku + souřadnice v prostoru textury const vertexes = [ // x, y, u, v min_x, min_y, 0.0, 0.0, min_x, max_y, 0.0, 1.0, max_x, max_y, 1.0, 1.0, min_x, min_y, 0.0, 0.0, max_x, max_y, 2.0, 2.0, max_x, min_y, 2.0, 0.0, ];
V takovém případě se projeví vliv nastavení TEXTURE_WRAP_S a TEXTURE_WRAP_T (označení S a T může být matoucí, to je však v OpenGL použito z historických důvodů, kdy se používal čtyřrozměrný prostor STRQ). Prozatím máme „wrapping“ texelů de facto zakázaný – namísto toho se použijí texely, které se nachází na hranicích textury. V našem konkrétním případě texely z pravé hrany, z dolní hrany a speciálně pak texel umístěný zcela vpravo dole:
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
Výsledek můžete vidět zde. Povšimněte si, že se skutečně texely z pravé hrany textury roztáhnou až na hranici obrázku. A pravý dolní texel vyplní celou horní část jednoho z trojúhelníků. Další možnosti budou ukázány v navazujícím článku.
Pátý demonstrační příklad: zobrazení otexturovaného čtverce s modifikovanými uv-souřadnicemi
Dnešní pátý demonstrační příklad si můžete otestovat po zobrazení stránky dostupné na adrese https://tisnik.github.io/temp/textures4.html. Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
const vertexShaderSource = `
attribute vec2 position;
attribute vec2 texcoord;
varying vec2 vTexcoord;
void main() {
gl_Position = vec4(position, 0, 1);
vTexcoord = texcoord;
}
`
const fragmentShaderSource = `
precision mediump float;
varying vec2 vTexcoord;
uniform sampler2D texture;
void main() {
gl_FragColor = texture2D(texture, vTexcoord);
}
`
function print(message) {
console.log(message);
}
function makeTexture(width, height) {
const array = new Array(width*height*4);
let index = 0;
for (j=0; j<height; j++) {
for (i=0; i<width; i++) {
const c = ((i&0x20)==0^(j&0x20)==0) * 255;
array[index++] = c;
array[index++] = i;
array[index++] = j;
array[index++] = 255; // alfa slozka
}
}
return array;
}
// reference na objekt představující kreslicí plátno umístěné na webové stránce
const canvas = document.getElementById("web_gl_canvas");
if (!canvas) {
throw new Error("Canvas with id 'web_gl_canvas' not found");
}
print("Canvas object retrieved");
// vytvoření a získání kontextu WebGL (3D grafika)
const gl = canvas.getContext("webgl");
if (!gl) {
throw new Error("WebGL is not supported or context creation failed");
}
print("WebGL context created");
// vytvoření vertex shaderu
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
if (!vertexShader) {
throw new Error("Failed to create vertex shader.");
}
print("Vertex shader created");
// překlad vertex shaderu
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
const log = gl.getShaderInfoLog(vertexShader);
gl.deleteShader(vertexShader);
throw new Error(`Vertex shader compile failed: ${log}`);
}
print("Vertex shader compiled");
// vytvoření fragment shaderu
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
if (!fragmentShader) {
throw new Error("Failed to create fragment shader.");
}
print("Fragment shader created");
// překlad fragment shaderu
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
const log = gl.getShaderInfoLog(fragmentShader);
gl.deleteShader(fragmentShader);
throw new Error(`fragment shader compile failed: ${log}`);
}
print("Fragment shader compiled");
// registrace obou shaderů
const program = gl.createProgram();
if (!program) {
throw new Error("Failed to create program.");
}
print("Program created");
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
print("Shaders attached to the program");
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const log = gl.getProgramInfoLog(program);
gl.deleteProgram(program);
throw new Error(`Program link failed: ${log}`);
}
print("Program linked");
const min_x = -0.9
const max_x = 0.9
const min_y = min_x
const max_y = max_x
// souřadnice vrcholů trojúhelníku + souřadnice v prostoru textury
const vertexes = [
// x, y, u, v
min_x, min_y, 0.0, 0.0,
min_x, max_y, 0.0, 1.0,
max_x, max_y, 1.0, 1.0,
min_x, min_y, 0.0, 0.0,
max_x, max_y, 2.0, 2.0,
max_x, min_y, 2.0, 0.0,
];
// buffer, do kterého se překopírují souřadnice bodu
const vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
throw new Error("Failed to create buffer.");
}
print("Vertex buffer created");
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
const data = new Float32Array(vertexes);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
print("Vertex buffer filled-in by vertexes");
// navázání na parametr předávaný do vertex shaderu
const p = gl.getAttribLocation(program, "position");
if (p === -1) {
throw new Error("Attribute 'position' not found in shader program");
}
print("Attribute 'position' found in shader program")
// navázání na parametr předávaný do vertex shaderu
const texLoc = gl.getAttribLocation(program, "texcoord");
if (p === -1) {
throw new Error("Attribute 'texcoord' not found in shader program");
}
print("Attribute 'texcoord' found in shader program")
// seznam předávaný do vertex shaderu
gl.vertexAttribPointer(p,
2, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu+texturovacích souřadnic v bajtech
0 // offset
);
gl.enableVertexAttribArray(p);
// seznam předávaný do vertex shaderu
gl.vertexAttribPointer(texLoc,
2, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu+texturovacích souřadnic v bajtech
2*Float32Array.BYTES_PER_ELEMENT // offset
);
gl.enableVertexAttribArray(texLoc);
// textura (prozatím primitivní)
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
const textureWidth = 256
const textureHeight = 256
// textura
textureArray = makeTexture(textureWidth, textureHeight);
const texels = new Uint8Array(textureArray);
gl.texImage2D(gl.TEXTURE_2D, // typ textury: zde 2D textura
0, // úroveň detailu
gl.RGBA, // počet barvových komponent (zde 4)
textureWidth, // horizontální rozlišení textury
textureHeight, // vertikální rozlišení textury
0, // šířka okraje textury
gl.RGBA, // formát uložení texelů
gl.UNSIGNED_BYTE, // typ každé barvové složky texelu
texels // pole texelů
);
// způsob vykreslení textury
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// navázání na atribut ve shaderech
const samplerLoc = gl.getUniformLocation(program, "texture");
gl.uniform1i(samplerLoc, 0);
// transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
gl.viewport(0, 0, canvas.width, canvas.height);
// nastavení barvy pro vymazání barvového bufferu
gl.clearColor(0, 0, 0, 1);
// vymazání barvového bufferu
gl.clear(gl.COLOR_BUFFER_BIT);
// spuštění programu
gl.useProgram(program);
// vykreslení dvojice trojúhelníků
gl.drawArrays(gl.TRIANGLES, 0, 6);
Textury uložené v externích souborech (PNG, JPEG)
V praxi se kromě různých procedurálních textur setkáme i s požadavkem na použití textur, které vzniknou například z fotografií, v grafických editorech atd. Příkladem může být tato textura, která reprezentuje část zdi (a jak uvidíme dále, lze tuto texturu opakovat). Jak se však rastrový obrázek s touto texturou načte do skriptu, který běží v prostředí webového prohlížeče? Ve skutečnosti to není příliš složité, ovšem za předpokladu, že je obrázek dostupný přes HTTP či HTTPS (a navíc je dostupný ze stejné domény jako skript – zde ovšem již záleží na konkrétním nastavení webového prohlížeče). Obecný postup při načtení obrázku s texturou vypadá následovně:
- Zkonstruuje se objekt typu Image
- Do tohoto objektu se načte rastrový obrázek přes HTTP či HTTPS
- Zkonstruuje se textura zavoláním metody gl.texImage2D s mírně upravenými parametry
- Poté je již možné nastavit další parametry textury a dokončit vykreslení scény
Realizace načtení rastrového obrázku
Konstrukce prázdného objektu typu Image je triviální:
// obrázek s texturou const img = new Image();
Načtení rastrového obrázku následované konstrukcí textury lze realizovat například přes tuto anonymní funkci, která se provede po načtení dat s obrázkem. Povšimněte si, že do metody gl.texImage2D se nepředává rozlišení textury, ale přímo objekt img (to klasické OpenGL ES nepodporuje):
img.onload = () => {
print("Loading texture");
print(img);
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(
gl.TEXTURE_2D, // typ textury: zde 2D textura
0, // úroveň detailu
gl.RGBA, // interní formát uložení texelů
gl.RGBA, // formát uložení texelů
gl.UNSIGNED_BYTE, // typ každé barvové složky texelu
img // objekt s obrázkem
);
// způsob vykreslení textury
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
print("Texture loaded");
draw();
};
Výše uvedená anonymní funkce zavolala funkci draw, která provede dokončení vykreslení celé scény:
function draw() {
// navázání na atribut ve shaderech
const samplerLoc = gl.getUniformLocation(program, "texture");
gl.uniform1i(samplerLoc, 0);
// transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
gl.viewport(0, 0, canvas.width, canvas.height);
// nastavení barvy pro vymazání barvového bufferu
gl.clearColor(0, 0, 0, 1);
// vymazání barvového bufferu
gl.clear(gl.COLOR_BUFFER_BIT);
// spuštění programu
gl.useProgram(program);
// vykreslení dvojice trojúhelníků
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
Šestý demonstrační příklad: čtverec s texturou načtenou z externího obrázku
Šestý a současně dnes již poslední demonstrační příklad je spustitelný z adresy https://tisnik.github.io/temp/textures5.html. Jeho úplný zdrojový kód vypadá následovně:
const vertexShaderSource = `
attribute vec2 position;
attribute vec2 texcoord;
varying vec2 vTexcoord;
void main() {
gl_Position = vec4(position, 0, 1);
vTexcoord = texcoord;
}
`
const fragmentShaderSource = `
precision mediump float;
varying vec2 vTexcoord;
uniform sampler2D texture;
void main() {
gl_FragColor = texture2D(texture, vTexcoord);
}
`
function print(message) {
console.log(message);
}
// reference na objekt představující kreslicí plátno umístěné na webové stránce
const canvas = document.getElementById("web_gl_canvas");
if (!canvas) {
throw new Error("Canvas with id 'web_gl_canvas' not found");
}
print("Canvas object retrieved");
// vytvoření a získání kontextu WebGL (3D grafika)
const gl = canvas.getContext("webgl");
if (!gl) {
throw new Error("WebGL is not supported or context creation failed");
}
print("WebGL context created");
// vytvoření vertex shaderu
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
if (!vertexShader) {
throw new Error("Failed to create vertex shader.");
}
print("Vertex shader created");
// překlad vertex shaderu
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
const log = gl.getShaderInfoLog(vertexShader);
gl.deleteShader(vertexShader);
throw new Error(`Vertex shader compile failed: ${log}`);
}
print("Vertex shader compiled");
// vytvoření fragment shaderu
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
if (!fragmentShader) {
throw new Error("Failed to create fragment shader.");
}
print("Fragment shader created");
// překlad fragment shaderu
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
const log = gl.getShaderInfoLog(fragmentShader);
gl.deleteShader(fragmentShader);
throw new Error(`fragment shader compile failed: ${log}`);
}
print("Fragment shader compiled");
// registrace obou shaderů
const program = gl.createProgram();
if (!program) {
throw new Error("Failed to create program.");
}
print("Program created");
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
print("Shaders attached to the program");
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const log = gl.getProgramInfoLog(program);
gl.deleteProgram(program);
throw new Error(`Program link failed: ${log}`);
}
print("Program linked");
const min_x = -0.9
const max_x = 0.9
const min_y = min_x
const max_y = max_x
// souřadnice vrcholů trojúhelníku + souřadnice v prostoru textury
const vertexes = [
// x, y, u, v
min_x, min_y, 0.0, 0.0,
min_x, max_y, 0.0, 1.0,
max_x, max_y, 1.0, 1.0,
min_x, min_y, 0.0, 0.0,
max_x, max_y, 1.0, 1.0,
max_x, min_y, 1.0, 0.0,
];
// buffer, do kterého se překopírují souřadnice bodu
const vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
throw new Error("Failed to create buffer.");
}
print("Vertex buffer created");
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
const data = new Float32Array(vertexes);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
print("Vertex buffer filled-in by vertexes");
// navázání na parametr předávaný do vertex shaderu
const p = gl.getAttribLocation(program, "position");
if (p === -1) {
throw new Error("Attribute 'position' not found in shader program");
}
print("Attribute 'position' found in shader program")
// navázání na parametr předávaný do vertex shaderu
const texLoc = gl.getAttribLocation(program, "texcoord");
if (p === -1) {
throw new Error("Attribute 'texcoord' not found in shader program");
}
print("Attribute 'texcoord' found in shader program")
// seznam předávaný do vertex shaderu
gl.vertexAttribPointer(p,
2, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu+texturovacích souřadnic v bajtech
0 // offset
);
gl.enableVertexAttribArray(p);
// seznam předávaný do vertex shaderu
gl.vertexAttribPointer(texLoc,
2, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu+texturovacích souřadnic v bajtech
2*Float32Array.BYTES_PER_ELEMENT // offset
);
gl.enableVertexAttribArray(texLoc);
// obrázek s texturou
const img = new Image();
img.onload = () => {
print("Loading texture");
print(img);
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(
gl.TEXTURE_2D, // typ textury: zde 2D textura
0, // úroveň detailu
gl.RGBA, // interní formát uložení texelů
gl.RGBA, // formát uložení texelů
gl.UNSIGNED_BYTE, // typ každé barvové složky texelu
img // objekt s obrázkem
);
// způsob vykreslení textury
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
print("Texture loaded");
draw();
};
img.src = "texture.png";
function draw() {
// navázání na atribut ve shaderech
const samplerLoc = gl.getUniformLocation(program, "texture");
gl.uniform1i(samplerLoc, 0);
// transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
gl.viewport(0, 0, canvas.width, canvas.height);
// nastavení barvy pro vymazání barvového bufferu
gl.clearColor(0, 0, 0, 1);
// vymazání barvového bufferu
gl.clear(gl.COLOR_BUFFER_BIT);
// spuštění programu
gl.useProgram(program);
// vykreslení dvojice trojúhelníků
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
Repositář s demonstračními příklady
Všechny demonstrační příklady, které jsme si v předchozí pětici článků i v dnešním článku ukázali, je možné spustit přímo ve webovém prohlížeči přes odkazy uvedené v tabulce pod tímto odstavcem. Doplněny jsou i odkazy na zdrojové kódy psané v JavaScriptu (ty se automaticky načítají do HTML stránek):
| # | Příklad | Stručný popis | Adresa příkladu |
|---|---|---|---|
| 1 | step1.html | webová stránka s kreslicím plátnem (canvasem) | https://tisnik.github.io/temp/step1.html |
| 2 | step1.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step1.js |
| 3 | step2.html | využití kreslicího plátna s 2D kontextem | https://tisnik.github.io/temp/step2.html |
| 4 | step2.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step2.js |
| 5 | step3.html | vymazání barvového bufferu pro kontext WebGL | https://tisnik.github.io/temp/step3.html |
| 6 | step3.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step3.js |
| 7 | step4.html | transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu | https://tisnik.github.io/temp/step4.html |
| 8 | step4.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step4.js |
| 9 | step5.html | vykreslení jediného bodu na plátno v kontextu WebGL | https://tisnik.github.io/temp/step5.html |
| 10 | step5.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step5.js |
| 11 | step6.html | vykreslení bodu s určením jeho souřadnic ve scéně | https://tisnik.github.io/temp/step6.html |
| 12 | step6.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step6.js |
| 13 | step7.html | vykreslení většího množství bodů do scény | https://tisnik.github.io/temp/step7.html |
| 14 | step7.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step7.js |
| 15 | step8.html | kontrola provedení jednotlivých operací | https://tisnik.github.io/temp/step8.html |
| 16 | step8.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step8.js |
| 17 | step9.html | vykreslení trojice úseček na plochu kreslicího plátna | https://tisnik.github.io/temp/step9.html |
| 18 | step9.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step9.js |
| 19 | stepA.html | nastavení konstantní barvy úseček | https://tisnik.github.io/temp/stepA.html |
| 20 | stepA.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/stepA.js |
| 21 | stepB.html | vykreslení na sebe navazujících úseček s předáním souřadnic | https://tisnik.github.io/temp/stepB.html |
| 22 | stepB.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/stepB.js |
| 23 | stepC.html | vykreslení uzavřené sekvence úseček s předáním souřadnic | https://tisnik.github.io/temp/stepC.html |
| 24 | stepC.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/stepC.js |
| 25 | stepD.html | vykreslení bodů, každý bod může mít libovolnou barvu | https://tisnik.github.io/temp/stepD.html |
| 26 | stepD.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/stepD.js |
| 27 | stepE.html | vykreslení úseček, barva je vypočtena interpolací barev | https://tisnik.github.io/temp/stepE.html |
| 28 | stepE.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/stepE.js |
| 29 | triangle1.html | vykreslení trojúhelníku konstantní barvou | https://tisnik.github.io/temp/triangle1.html |
| 30 | triangle1.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle1.js |
| 31 | triangle2.html | interpolace barev při vyplňování trojúhelníku | https://tisnik.github.io/temp/triangle2.html |
| 32 | triangle2.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle2.js |
| 33 | triangle3.html | vykreslení pásu trojúhelníků | https://tisnik.github.io/temp/triangle3.html |
| 34 | triangle3.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle3.js |
| 35 | triangle4.html | vykreslení trsu trojúhelníků – neúplná varianta | https://tisnik.github.io/temp/triangle4.html |
| 36 | triangle4.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle4.js |
| 37 | triangle5.html | vykreslení trsu trojúhelníků – úplná varianta | https://tisnik.github.io/temp/triangle5.html |
| 38 | triangle5.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle5.js |
| 39 | triangle6.html | překrývající se trojúhelníky (Z-buffer není použit) | https://tisnik.github.io/temp/triangle6.html |
| 40 | triangle6.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle6.js |
| 41 | triangle7.html | překrývající se trojúhelníky (Z-buffer je použit) | https://tisnik.github.io/temp/triangle7.html |
| 42 | triangle7.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle7.js |
| 43 | cube1.html | 3D krychle vykreslená do 2D plochy bez aplikace transformací | https://tisnik.github.io/temp/cube1.html |
| 44 | cube1.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube1.js |
| 45 | cube2.html | transformace reprezentovaná jednotkovou maticí | https://tisnik.github.io/temp/cube2.html |
| 46 | cube2.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube2.js |
| 47 | cube3.html | zobrazení 3D kostky z různých úhlů pohledu | https://tisnik.github.io/temp/cube3.html |
| 48 | cube3.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube3.js |
| 49 | cube4.html | zobrazení 3D kostky s využitím základní perspektivní projekce | https://tisnik.github.io/temp/cube4.html |
| 50 | cube4.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube4.js |
| 51 | cube5.html | krychle v 3D prostoru | https://tisnik.github.io/temp/cube5.html |
| 52 | cube5.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube5.js |
| 53 | cube6.html | rotace krychle v 3D prostoru s perspektivní projekcí | https://tisnik.github.io/temp/cube6.html |
| 54 | cube6.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube6.js |
| 55 | cube7.html | animace rotace krychle složené z hran (úseček) v 3D prostoru s perspektivní projekcí | https://tisnik.github.io/temp/cube7.html |
| 56 | cube7.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube7.js |
| 57 | cube8.html | animace rotace krychle složené z trojúhelníků v 3D prostoru s perspektivní projekcí | https://tisnik.github.io/temp/cube8.html |
| 58 | cube8.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube8.js |
| 59 | cube9.html | animace rotace krychle složené z trojúhelníků v 3D prostoru s perspektivní projekcí a povolením paměti hloubky | https://tisnik.github.io/temp/cube9.html |
| 60 | cube9.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube9.js |
| 60 | cubeA.html | změna vzdálenosti bližší pohledové roviny | https://tisnik.github.io/temp/cubeA.html |
| 61 | cubeA.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cubeA.js |
| 62 | cubeB.html | doplnění rotující krychle o obě chybějící stěny | https://tisnik.github.io/temp/cubeB.html |
| 63 | cubeB.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cubeB.js |
| 64 | texsize.html | zjištění maximálního rozlišení textury | https://tisnik.github.io/temp/texsize.html |
| 65 | texsize.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/texsize.js |
| 66 | texture1.html | textura namapovaná na jediný trojúhelník | https://tisnik.github.io/temp/texture1.html |
| 67 | texture1.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/texture1.js |
| 68 | texture2.html | textura namapovaná na dvojici trojúhelníků tvořících čtvercovou stěnu | https://tisnik.github.io/temp/texture2.html |
| 69 | texture2.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/texture2.js |
| 70 | texture3.html | jednoduchá procedurální textura | https://tisnik.github.io/temp/texture3.html |
| 71 | texture3.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/texture3.js |
| 72 | texture4.html | modifikace UV souřadnic textury | https://tisnik.github.io/temp/texture4.html |
| 73 | texture4.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/texture4.js |
| 74 | texture5.html | textura načtená z rastrového obrázku | https://tisnik.github.io/temp/texture5.html |
| 75 | texture5.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/texture5.js |
Odkazy na Internetu
- WebGL: 3D Graphics for the Web
https://www.khronos.org/webgl/ - OpenGL Shading Language
https://en.wikipedia.org/wiki/OpenGL_Shading_Language - WebGL (Wikipedia)
https://en.wikipedia.org/wiki/WebGL - WebGL
https://wikis.khronos.org/webgl/Main_Page - OpenGL ES (Wikipedia)
https://en.wikipedia.org/wiki/OpenGL_ES - Float32Array
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array - ECMAScript compatibility table
https://compat-table.github.io/compat-table/es6/ - Learn-OpenGLES-Tutorials
https://github.com/learnopengles/Learn-OpenGLES-Tutorials - Implement OpenGL ES and EGL
https://source.android.com/docs/core/graphics/implement-opengl-es - Khronos Group
https://www.khronos.org/ - Khronos Group (Wikipedia)
https://en.wikipedia.org/wiki/Khronos_Group - Raspberry Pi VideoCore APIs
http://elinux.org/Raspberry_Pi_VideoCore_APIs - EGL quick reference card
https://www.khronos.org/files/egl-1–4-quick-reference-card.pdf - EGL Reference Pages Index
https://www.khronos.org/registry/egl/sdk/docs/man/html/indexflat.php - GLSL
https://glsl.app/ - Shader Tutorials by Ronja
https://www.ronja-tutorials.com/ - CanvasRenderingContext2D
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D - WebGL2RenderingContext
https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext - WebGLRenderingContext
https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext - Matrix Transformations
https://vitaminac.github.io/Matrices-in-Computer-Graphics/ - Homogeneous coordinates
https://en.wikipedia.org/wiki/Homogeneous_coordinates - Transformation matrix
https://en.wikipedia.org/wiki/Transformation_matrix - Affine transformation
https://en.wikipedia.org/wiki/Affine_transformation - OpenGL Projection Matrix
https://songho.ca/opengl/gl_projectionmatrix.html - Texture mapping
https://en.wikipedia.org/wiki/Texture_mapping - UV mapping
https://en.wikipedia.org/wiki/UV_mapping - UV Coordinates explained
https://blog.nobel-joergensen.com/2011/04/05/procedural-generated-mesh-in-unity-part-2-with-uv-mapping/ - Introduction to texture mapping
http://www.decew.net/OSS/References/chapter_2_texture_mapping.pdf
