WebGL: tvorba 3D grafiky s využitím programovatelné vykreslovací pipeline (2. část)

1. 10. 2026
Doba čtení: 35 minut

Sdílet

3D konvička wireframe
Autor: Hay Kranen, Wikimedia, podle licence: CC-BY
Ve druhém článku o technologii WebGL si ukážeme způsob kontroly operací grafické pipeline. Dále si popíšeme podporovaná grafická primitiva OpenGL ES a ukážeme si vykreslení úseček, polyčar a polygonů.

Na úvodní článek o technologii WebGL a o knihovně OpenGL ES dnes navážeme. Prozatím jsme si popsali základní vlastnosti vykreslovací pipeline a taktéž jsme si ukázali dvojici jednoduchých shaderů určených pro ten nejjednodušší úkol: vykreslení bodu či bodů s neměnnou barvou a velikostí na kreslicí plochu. To je však pochopitelně pouze začátek, takže si dnes popíšeme další důležité vlastnosti OpenGL ES – jaká grafická primitiva lze při vykreslování použít, jak vypadají komplikovanější shadery atd.

Co se dozvíte v článku
  1. Skript, na kterém budou postaveny další demonstrační příklady
  2. Kontrola provedení jednotlivých operací s grafickou pipeline
  3. Kontrola provedení příkazů OpenGL ES
  4. Úprava skriptu tak, aby kontrolovala každá prováděná operace
  5. Typy grafických primitiv podporovaných v OpenGL ES
  6. Postup při vykreslování úseček
  7. Demonstrační příklad: vykreslení tří úseček konstantní barvou
  8. Nastavení barvy vykreslovaných úseček
  9. Demonstrační příklad: nastavení barvy vykreslovaných úseček
  10. Vykreslení polyčáry
  11. Vykreslení polygonu složeného z lineárních segmentů
  12. Předání barev jednotlivých vykreslovaných bodů do grafické pipeline
  13. Demonstrační příklad: vykreslení pěti bodů pěti různými barvami
  14. Lineární interpolace barev při vykreslování úseček
  15. Specifikace barev vrcholů vykreslovaných úseček
  16. Demonstrační příklad: vykreslení úseček s interpolací barvy
  17. Repositář s demonstračními příklady
  18. Seriály a články o OpenGL i přidružených technologiích
  19. Odkazy na Internetu

Pochopitelně nesmíme zapomenout ani na další „maličkost“, která je však v praxi velmi důležitá. Jedná se o kontroly, zda všechny operace proběhly korektně (což ovšem ve skutečnosti nemusí být úplně triviální). Dnešní článek je opět zaměřen prakticky, takže si vše ověříme na několika demonstračních příkladech spustitelných přímo ve webovém prohlížeči.

Skript, na kterém budou postaveny další demonstrační příklady

Připomeňme si, jak vypadal skript uvedený v závěrečné části úvodního článku. Tento skript vykreslil do kreslicí plochy (canvasu) pětici bodů stejné velikosti i barvy:

Vykreslení pětice bodů na zvolených souřadnicích.

Obrázek 1: Vykreslení pětice bodů na zvolených souřadnicích.

Autor: tisnik, podle licence: Rights Managed

OpenGL ES vyžaduje „úplné“ naprogramování vykreslovací pipeline, takže se ve skriptu nachází zdrojové kódy vertex shaderu i fragment shaderu. Tyto zdrojové kódy jsou přeloženy, je sestaven program celé vykreslovací pipeline a následně se do této pipeline pošlou souřadnice pětice bodů v rovině, čímž se spustí proces jejich rasterizace do barvového bufferu (color buffer). Povšimněte si, že souřadnice bodů se předávají vertex shaderu v parametru nazvaném position, jenž je typu vec2 neboli dvourozměrný vektor. Fragment shader prozatím žádné parametry nezpracovává – pouze za všech okolností nastavuje červenou barvu vykreslování:

const vertexShaderSource = `
    attribute vec2 position;
 
    void main() {
        gl_Position = vec4(position, 0, 1);
        gl_PointSize = 20.0;
    }
`
 
const fragmentShaderSource = `
    precision mediump float;
 
    void main() {
        gl_FragColor = vec4(1, 0, 0, 1);
    }
`
 
// reference na objekt představující kreslicí plátno umístěné na webové stránce
const canvas = document.getElementById("web_gl_canvas");
 
// vytvoření a získání kontextu WebGL (3D grafika)
const gl = canvas.getContext("webgl");
 
// překlad vertex shaderu
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
 
// překlad fragment shaderu
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
 
// registrace obou shaderů
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
 
// souřadnice bodů
vertexes = [
   // x   y
    0.0, 0.0,
    0.9, 0.9,
    0.9, -0.9,
   -0.9, 0.9,
   -0.9, -0.9,
];
 
// buffer, do kterého se překopírují souřadnice bodu
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexes), gl.STATIC_DRAW);
 
// navázání na parametr předávaný do vertex shaderu
const p = gl.getAttribLocation(program, "position");
 
// 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
                       2*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
                       0                                 // offset
);
gl.enableVertexAttribArray(p);
 
// 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í pěti bodů s předáním souřadnic
gl.drawArrays(gl.POINTS, 0, 5);

Kontrola provedení jednotlivých operací s grafickou pipeline

Skript uvedený v úvodní části dnešního článku je možné upravit do takové podoby, aby kontroloval každou prováděnou operaci. Na konzoli (dostupnou z webového prohlížeče) se bude vypisovat buď informace o korektně provedené operaci, nebo se v případě chyby vyvolá výjimka. V této kapitole si ukážeme kontroly operací, které souvisí se získáním reference na kreslicí plochu.

Nejprve si uvedeme pomocnou funkci pro výpis zprávy na konzoli webového prohlížeče:

function print(message) {
    console.log(message);
}

Získání reference na objekt představující kreslicí plátno může skončit chybou v případě, že tento objekt na stránce chybí:

// 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");

Podobně vytvoření kontextu WebGL může skončit chybou, v tomto případě z mnoha důvodů (technologie vůbec nemusí být podporována, je zakázána, prohlížeč pracuje v headless režimu atd.):

// 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");

Kontrola provedení příkazů OpenGL ES

Poněkud složitější je kontrola, zda jsou provedeny všechny OpenGL ES příkazy. Některé z nich totiž přímo nevrací hodnoty, ale nastavují interní stav vykreslovacího řetězce. Příkladem je překlad shaderu – pokusíme se provést tuto operaci a následně přes getShaderParameter získáme stav shaderu:

// 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");
Poznámka: u fragment shaderu se postupuje naprosto stejným způsobem.

Podobný test lze provést po překladu a slinkování celého programu, který bude ovládat grafickou pipeline:

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");

Ještě si uveďme dva kroky. Prvním z nich je kontrola, zda byl vytvořen buffer OpenGL:

const buffer = gl.createBuffer();
if (!buffer) {
    throw new Error("Failed to create buffer.");
}
print("Vertex buffer created");

Ve druhém kroku zjistíme, zda je přístupný parametr position použitý pro předávání dat z JavaScriptu do vertex shaderu:

// 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")
Poznámka: obecnou chybu (prakticky) jakékoli operace OpenGL ES je možné získat metodou gl.GetError(). Ta vrátí jednu z následujících konstant:
INVALID_ENUM
INVALID_FRAMEBUFFER_OPERATION
INVALID_VALUE
INVALID_OPERATION
OUT_OF_MEMORY
NO_ERROR

Úprava skriptu tak, aby kontrolovala každá prováděná operace

Podívejme se nyní na celkovou úpravu skriptu z úvodní části dnešního článku. Každá prováděná operace je kontrolována a buď dojde k vyhození výjimky (a ukončení skriptu) nebo k výpisu informace o provedené operaci na konzoli prohlížeče. Úspěšné provedení skriptu by kromě vykreslení pětice bodů na kreslicí plochu mělo na konzoli vypadat následovně:

Canvas object retrieved
WebGL context created
Vertex shader created
Vertex shader compiled
Fragment shader created
Fragment shader compiled
Program created
Shaders attached to the program
Program linked
Vertex buffer created
Vertex buffer filled-in by vertexes
Attribute 'position' found in shader program

Celý skript je nyní méně přehledný a delší – namísto 81 řádků je zapsán na 142 řádcích:

const vertexShaderSource = `
    attribute vec2 position;
 
    void main() {
        gl_Position = vec4(position, 0, 1);
        gl_PointSize = 20.0;
    }
`
 
const fragmentShaderSource = `
    precision mediump float;
 
    void main() {
        gl_FragColor = vec4(1, 0, 0, 1);
    }
`
 
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");
 
// souřadnice bodů
vertexes = [
   // x   y
    0.0, 0.0,
    0.9, 0.9,
    0.9, -0.9,
   -0.9, 0.9,
   -0.9, -0.9,
];
 
// buffer, do kterého se překopírují souřadnice bodu
const buffer = gl.createBuffer();
if (!buffer) {
    throw new Error("Failed to create buffer.");
}
print("Vertex buffer created");
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
 
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")
 
 
// 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
                       2*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
                       0                                 // offset
);
gl.enableVertexAttribArray(p);
 
// 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í pěti bodů s předáním souřadnic
gl.drawArrays(gl.POINTS, 0, 5);
Vykreslení pěti bodů konstantní barvou. Provádí se kontrola všech operací.

Obrázek 2: Vykreslení pěti bodů konstantní barvou. Provádí se kontrola všech operací.

Autor: tisnik, podle licence: Rights Managed

Typy grafických primitiv podporovaných v OpenGL ES

Prozatím jsme vykreslovali pouze body (POINTS), ovšem v OpenGL (a konkrétně v OpenGL ES) jsou podporována i další grafická primitiva. Oproti původnímu klasickému OpenGL je nabídka primitiv menší, ovšem na druhou stranu mohly být odstraněny všechny další podmínky (původně bylo podporováno například vykreslení vyplněných polygonů, ovšem těžko se u nich zajišťovala jejich rovinnost atd.). V OpenGL ES nalezneme tato primitiva:

Primitivum Stručný popis
GL_POINTS jednotlivé body
   
GL_LINES jednotlivé úsečky
GL_LINE_STRIP polyčára (sekvence na sebe navazujících úseček)
GL_LINE_LOOP polygon, ovšem vykreslený pouze úsečkami
   
GL_TRIANGLES jednotlivé trojúhelníky
GL_TRIANGLE_STRIP pás sestavený s trojúhelníků (popíšeme příště)
GL_TRIANGLE_FAN trs trojúhelníků (popíšeme příště)
Grafické primitivum GL_POINTS - izolované body.

Obrázek 3: Grafické primitivum GL_POINTS - izolované body.

Autor: tisnik, podle licence: Rights Managed

Grafické primitivum GL_LINES - izolované úsečky.

Obrázek 4: Grafické primitivum GL_LINES - izolované úsečky.

Autor: tisnik, podle licence: Rights Managed

Grafické primitivum GL_LINE_STRIP - polyčára složená z lineárních segmentů.

Obrázek 5: Grafické primitivum GL_LINE_STRIP - polyčára složená z lineárních segmentů.

Autor: tisnik, podle licence: Rights Managed

Grafické primitivum GL_LINE_LOOP - polygon složený z lineárních segmentů.

Obrázek 6: Grafické primitivum GL_LINE_LOOP - polygon složený z lineárních segmentů.

Autor: tisnik, podle licence: Rights Managed

Grafické primitivum GL_TRIANGLES - izolované trojúhelníky.

Obrázek 7: Grafické primitivum GL_TRIANGLES - izolované trojúhelníky.

Autor: tisnik, podle licence: Rights Managed

Grafické primitivum GL_TRIANGLE_FAN - trs trojúhelníků.

Obrázek 8: Grafické primitivum GL_TRIANGLE_FAN - trs trojúhelníků.

Autor: tisnik, podle licence: Rights Managed

Grafické primitivum GL_TRIANGLE_STRIP - pás složený z trojúhelníků.

Obrázek 9: Grafické primitivum GL_TRIANGLE_STRIP - pás složený z trojúhelníků.

Autor: tisnik, podle licence: Rights Managed

Postup při vykreslování úseček

V mnoha aplikacích (typicky v různých CADech) se setkáme s potřebou vykreslování úseček do výsledné scény. Tato grafická primitiva jsou, jak již víme, knihovnou OpenGL ES podporována, ovšem na rozdíl od klasického OpenGL například není možné měnit tloušťku stopy nebo styl úsečky (čárkovaná, čerchovaná atd.) – pochopitelně pokud si k tomuto účelu nenaprogramujeme vlastní specializovaný shader. Ovšem začneme tím nejjednodušším příkladem, kterým je vykreslení několika úseček o standardní šířce konstantní barvou. Koncové body úseček budou zadány v rovině.

V takovém případě může vertex shader i fragment shader zůstat prakticky totožný, jako tomu bylo při vykreslování bodů (ostatně vertex shader se volá pro každý vertex, takže zde ani ke změně nemá dojít). Pochopitelně ale nemá smysl nastavovat velikost bodů (to je ignorovaný atribut):

const vertexShaderSource = `
    attribute vec2 position;
 
    void main() {
        gl_Position = vec4(position, 0, 1);
    }
`
 
 
 
const fragmentShaderSource = `
    precision mediump float;
 
    void main() {
        gl_FragColor = vec4(1, 0, 1, 0);
    }
`

Překlad shaderů, překlad programu pro grafickou pipeline atd., se nijak neliší od postupu, který již známe.

Odlišné ovšem bude pole se souřadnicemi, které bude obsahovat koncové body úseček (tedy dva body pro každou úsečku):

// souřadnice úseček
vertexes = [
   // x   y    x    y
   -0.9,-0.9, 0.9,-0.9,  // horizontální úsečka
   -0.9, 0.9, 0.9, 0.9,  // horizontální úsečka
    0.0,-0.8, 0.0, 0.8,  // vertikální úsečka
];

Další kroky opět zůstanou prakticky stejné, ovšem nesmíme zapomenout na to, že se změní typ grafického primitiva předávaného do metody drawArrays. Povšimněte si, že se v posledním parametru ve skutečnosti nepředává počet grafických primitiv, ale počet vrcholů. Tedy pro tři úsečky se jedná o 3×2=6 vrcholů:

// vykreslení tří úseček bodů s předáním souřadnic
gl.drawArrays(gl.LINES, 0, 6);

Žádné další změny není zapotřebí provádět. Výsledek by měl vypadat takto:

Vykreslení tří úseček konstantní barvou.

Obrázek 10: Vykreslení tří úseček konstantní barvou.

Autor: tisnik, podle licence: Rights Managed

Demonstrační příklad: vykreslení tří úseček konstantní barvou

Podívejme se nyní na úplný zdrojový kód příkladu, který na kreslicí plochu vykreslí tři úsečky. I když je program poměrně dlouhý, příliš se neliší od původního skriptu, který vykresloval jednotlivé body:

const vertexShaderSource = `
    attribute vec2 position;
 
    void main() {
        gl_Position = vec4(position, 0, 1);
    }
`
 
const fragmentShaderSource = `
    precision mediump float;
 
    void main() {
        gl_FragColor = vec4(1, 0, 1, 0);
    }
`
 
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");
 
// souřadnice úseček
vertexes = [
   // x   y   x  y
   -0.9,-0.9, 0.9,-0.9,  // horizontální úsečka
   -0.9, 0.9, 0.9, 0.9,  // horizontální úsečka
    0.0,-0.8, 0.0, 0.8,  // vertikální úsečka
];
 
// buffer, do kterého se překopírují souřadnice bodu
const buffer = gl.createBuffer();
if (!buffer) {
    throw new Error("Failed to create buffer.");
}
print("Vertex buffer created");
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
 
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")
 
 
// 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
                       2*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
                       0                                 // offset
);
gl.enableVertexAttribArray(p);
 
// 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í tří úseček bodů s předáním souřadnic
gl.drawArrays(gl.LINES, 0, 6);

Nastavení barvy vykreslovaných úseček

V navazujících kapitolách si řekneme, jakým způsobem se budou do grafické pipeline posílat barvy bodů nebo úseček. To ovšem vyžaduje další buffer atd. Ovšem již nyní si můžeme ukázat způsob modifikace „globální“ barvy použité fragment shaderem. Nejprve je nutné upravit fragment shader tak, aby barvu přečetl z proměnné (dostupné v pipeline) nazvané například uColor (to je často používané jméno, ale může být jakékoli). Barva je reprezentována čtveřicí hodnot a při každém volání fragment shaderu se získá hodnota z proměnné uColor a nastaví se návratová hodnota fragment shaderu (gl_FragColor je naopak hodnota rozpoznávaná v GLSL):

precision mediump float;
 
uniform vec4 uColor;
 
void main() {
    gl_FragColor = uColor;
}

Ovšem před vykreslením úseček či bodů musíme nastavit hodnotu proměnné uColor. Nejedná se pochopitelně přímo o hodnotu dostupnou z JavaScriptu, proto je nutné použít následující postup:

// nastavení barvy vykreslovaných úseček
const colorLoc = gl.getUniformLocation(program, "uColor");
gl.uniform4f(colorLoc, 0.6, 1.0, 0.6, 1.0); // green

S výsledkem:

Vykreslení tří úseček barvou ovladatelnou z JavaScriptu.

Obrázek 11: Vykreslení tří úseček barvou ovladatelnou z JavaScriptu

Autor: tisnik, podle licence: Rights Managed

Poznámka: kód používající OpenGL ES je poměrně často plný podobných nastavení. Je tomu tak z toho důvodu, že musíme spojovat svět JavaScriptu (či céčka atd.) na straně jedné a GLSL (OpenGL Shading Language) na straně druhé. Především práce s proměnnými a parametry na straně GLSL se tedy provádí takto „přes ruku“.

Demonstrační příklad: nastavení barvy vykreslovaných úseček

Opět si ukažme úplný zdrojový kód demonstračního příkladu, který po svém spuštění vykreslí trojici úseček zvolenou barvou:

const vertexShaderSource = `
    attribute vec2 position;
 
    void main() {
        gl_Position = vec4(position, 0, 1);
    }
`
 
const fragmentShaderSource = `
    precision mediump float;
 
    uniform vec4 uColor;
 
    void main() {
        gl_FragColor = uColor;
    }
`
 
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");
 
// souřadnice úseček
vertexes = [
   // x   y   x  y
   -0.9,-0.9, 0.9,-0.9,  // horizontální úsečka
   -0.9, 0.9, 0.9, 0.9,  // horizontální úsečka
    0.0,-0.8, 0.0, 0.8,  // vertikální úsečka
];
 
// buffer, do kterého se překopírují souřadnice bodu
const buffer = gl.createBuffer();
if (!buffer) {
    throw new Error("Failed to create buffer.");
}
print("Vertex buffer created");
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
 
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")
 
 
// 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
                       2*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
                       0                                 // offset
);
gl.enableVertexAttribArray(p);
 
// 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);
 
// nastavení barvy vykreslovaných úseček
const colorLoc = gl.getUniformLocation(program, "uColor");
gl.uniform4f(colorLoc, 0.6, 1.0, 0.6, 1.0); // green
 
// vykreslení tří úseček bodů s předáním souřadnic
gl.drawArrays(gl.LINES, 0, 6);

Vykreslení polyčáry

Vykreslení lomené čáry (polyčáry) je v OpenGL snadné, ovšem přesto si ukážeme, jak se provádí a jak se bude výsledek odlišovat od kreslení izolovaných úseček. Ponecháme pole se souřadnicemi koncových úseček tak, jako tomu bylo při kresbě jednotlivých úseček. Bude se tedy jednat o celkem šest vrcholů:

// souřadnice úseček
vertexes = [
   // x   y   x  y
   -0.9,-0.9, 0.9,-0.9,  // horizontální úsečka
   -0.9, 0.9, 0.9, 0.9,  // horizontální úsečka
    0.0,-0.8, 0.0, 0.8,  // vertikální úsečka
];

Pokud ovšem namísto příkazu:

// vykreslení tří úseček bodů s předáním souřadnic
gl.drawArrays(gl.LINES, 0, 6);

zavoláme příkaz:

// vykreslení na sebe navazujících úseček s předáním souřadnic
gl.drawArrays(gl.LINE_STRIP, 0, 6);

…budou všechny vrcholy posílané do grafické pipeline spojeny jedinou polyčárou. Nejprve se vykreslí spodní horizontální úsečka, na ni naváže horní horizontální úsečka a posléze se připojí vertikální úsečka:

Vykreslení polyčáry složené z lineárních segmentů.

Obrázek 12: Vykreslení polyčáry složené z lineárních segmentů.

Autor: tisnik, podle licence: Rights Managed

Pro úplnost si uveďme celý zdrojový kód takto upraveného demonstračního příkladu:

const vertexShaderSource = `
    attribute vec2 position;
 
    void main() {
        gl_Position = vec4(position, 0, 1);
    }
`
 
const fragmentShaderSource = `
    precision mediump float;
 
    uniform vec4 uColor;
 
    void main() {
        gl_FragColor = uColor;
    }
`
 
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");
 
// souřadnice úseček
vertexes = [
   // x   y   x  y
   -0.9,-0.9, 0.9,-0.9,  // horizontální úsečka
   -0.9, 0.9, 0.9, 0.9,  // horizontální úsečka
    0.0,-0.8, 0.0, 0.8,  // vertikální úsečka
];
 
// buffer, do kterého se překopírují souřadnice bodu
const buffer = gl.createBuffer();
if (!buffer) {
    throw new Error("Failed to create buffer.");
}
print("Vertex buffer created");
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
 
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")
 
 
// 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
                       2*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
                       0                                 // offset
);
gl.enableVertexAttribArray(p);
 
// 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);
 
// nastavení barvy vykreslovaných úseček
const colorLoc = gl.getUniformLocation(program, "uColor");
gl.uniform4f(colorLoc, 0.6, 1.0, 0.6, 1.0); // green
 
// vykreslení na sebe navazujících úseček s předáním souřadnic
gl.drawArrays(gl.LINE_STRIP, 0, 6);

Vykreslení polygonu složeného z lineárních segmentů

Poslední variantou kreslení úseček je vykreslení polygonu, který není vyplněn (tato možnost totiž byla v OpenGL ES odstraněna), ale je „pouze“ složen z lineárních segmentů. Jedná se vlastně o variantu výše ukázané polyčáry, ovšem na konci je celý tvar uzavřen úsečkou vedoucí z posledního vrcholu do vrcholu prvního. Postačuje, aby se namísto příkazu:

// vykreslení na sebe navazujících úseček s předáním souřadnic
gl.drawArrays(gl.LINE_STRIP, 0, 6);

…použít příkaz:

// vykreslení polygonu předáním souřadnic
gl.drawArrays(gl.LINE_LOOP, 0, 6);

Z výsledného obrázku je patrné, že byl přidán poslední lineární segment a tvar byl tak uzavřen (ovšem nebyl vyplněn):

Vykreslení polygonu složeného z lineárních segmentů.

Obrázek 13: Vykreslení polygonu složeného z lineárních segmentů

Autor: tisnik, podle licence: Rights Managed

Opět – pouze pro úplnost – bude uveden celý zdrojový kód příkladu, který předchozí obrázek vykreslil:

const vertexShaderSource = `
    attribute vec2 position;
 
    void main() {
        gl_Position = vec4(position, 0, 1);
    }
`
 
const fragmentShaderSource = `
    precision mediump float;
 
    uniform vec4 uColor;
 
    void main() {
        gl_FragColor = uColor;
    }
`
 
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");
 
// souřadnice úseček
vertexes = [
   // x   y   x  y
   -0.9,-0.9, 0.9,-0.9,  // horizontální úsečka
   -0.9, 0.9, 0.9, 0.9,  // horizontální úsečka
    0.0,-0.8, 0.0, 0.8,  // vertikální úsečka
];
 
// buffer, do kterého se překopírují souřadnice bodu
const buffer = gl.createBuffer();
if (!buffer) {
    throw new Error("Failed to create buffer.");
}
print("Vertex buffer created");
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
 
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")
 
 
// 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
                       2*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
                       0                                 // offset
);
gl.enableVertexAttribArray(p);
 
// 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);
 
// nastavení barvy vykreslovaných úseček
const colorLoc = gl.getUniformLocation(program, "uColor");
gl.uniform4f(colorLoc, 0.6, 1.0, 0.6, 1.0); // green
 
// vykreslení polygonu předáním souřadnic
gl.drawArrays(gl.LINE_LOOP, 0, 6);

Předání barev jednotlivých vykreslovaných bodů do grafické pipeline

Prozatím byly všechny body či úsečky vykresleny totožnou barvou, což ovšem není příliš praktické. Ukažme si tedy, jakým způsobem lze zajistit, aby bylo možné pro každý vykreslený bod nastavit libovolnou barvu (z prostoru RGBA). V první řadě musíme upravit vertex shader. Ten je zavolán pro každý vykreslovaný bod, což nám umožňuje předat shaderu jak souřadnice bodu (atribut position), tak i barvu bodu (atribut aColor). Vertex shader tuto barvu uloží do interní proměnné grafické pipeliny vColor, ke které má přístup fragment shader:

attribute vec2 position;
attribute vec4 aColor;
varying vec4 vColor;
 
void main() {
    vColor = aColor;
    gl_Position = vec4(position, 0, 1);
    gl_PointSize = 20.0;
}

Fragment shader pouze barvu z vColor přečte a nastaví gl_FragColor (předtím jsme ho nastavovali na konstantní hodnotu platnou pro všechna vykreslovaná primitiva):

precision mediump float;
varying vec4 vColor;
 
void main() {
    gl_FragColor = vColor;
}

Nyní nám zbývá definovat barvy pěti bodů v prostoru RGBA:

// barvy bodů
const colors = [
  1, 0, 0, 1,
  1, 1, 0, 1,
  0, 1, 0, 1,
  0, 1, 1, 1,
  0, 0, 1, 1,
];

Obsah JavaScriptového pole přetransformujeme do formátu Float32Array a uložíme do bufferu OpenGL ES. Jedná se o totožný postup, který byl použitý u vertexů:

const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
 
const cdata = new Float32Array(colors);
gl.bufferData(gl.ARRAY_BUFFER, cdata, gl.STATIC_DRAW);

Následně celý buffer pošleme do grafické pipeline a navážeme jednotlivé hodnoty (čtyřprvkový vektor) na atribut nazvaný aColor (ten je přečten vertex shaderem, viz výše):

const aColor = gl.getAttribLocation(program, "aColor");
gl.enableVertexAttribArray(aColor);
gl.vertexAttribPointer(aColor,
                       4,
                       gl.FLOAT,
                       gl.FALSE,
                       4*Float32Array.BYTES_PER_ELEMENT,
                       0);

Na kreslicí plochu by měla být tímto postupem vykreslena pětice bodů, každý odlišnou barvou:

Pětice bodů, každý s jinou barvou.

Obrázek 14: Pětice bodů, každý vykreslený odlišnou barvou.

Autor: tisnik, podle licence: Rights Managed

Demonstrační příklad: vykreslení pěti bodů pěti různými barvami

V dnešním předposledním demonstračním příkladu je ukázán způsob vykreslení pěti bodů do kreslicí plochy, přičemž je důležité, že každý z těchto bodů je vybarven odlišnou barvou. Barvy jsou přitom zapsány v poli colors, takže je možné je kdykoli modifikovat bez nutnosti přeprogramování grafické pipeline:

const vertexShaderSource = `
    attribute vec2 position;
    attribute vec4 aColor;
    varying vec4 vColor;
 
    void main() {
        vColor = aColor;
        gl_Position = vec4(position, 0, 1);
        gl_PointSize = 20.0;
    }
`
 
const fragmentShaderSource = `
    precision mediump float;
    varying vec4 vColor;
 
    void main() {
        gl_FragColor = vColor;
    }
`
 
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");
 
// souřadnice bodů
vertexes = [
   // x   y
    0.0, 0.0,
    0.9, 0.9,
    0.9, -0.9,
   -0.9, 0.9,
   -0.9, -0.9,
];
 
// barvy bodů
const colors = [
  1, 0, 0, 1,
  1, 1, 0, 1,
  0, 1, 0, 1,
  0, 1, 1, 1,
  0, 0, 1, 1,
];
 
// buffer, do kterého se překopírují souřadnice bodu
const buffer = gl.createBuffer();
if (!buffer) {
    throw new Error("Failed to create buffer.");
}
print("Vertex buffer created");
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
 
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")
 
 
// 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
                       2*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
                       0                                 // offset
);
gl.enableVertexAttribArray(p);
 
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
 
const cdata = new Float32Array(colors);
gl.bufferData(gl.ARRAY_BUFFER, cdata, gl.STATIC_DRAW);
 
const aColor = gl.getAttribLocation(program, "aColor");
gl.enableVertexAttribArray(aColor);
gl.vertexAttribPointer(aColor,
                       4,
                       gl.FLOAT,
                       gl.FALSE,
                       4*Float32Array.BYTES_PER_ELEMENT,
                       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í pěti bodů s předáním souřadnic
gl.drawArrays(gl.POINTS, 0, 5);

Lineární interpolace barev při vykreslování úseček

V klasickém OpenGL se při vykreslování úseček provádí lineární interpolace barvy. To znamená, že každý vrchol úsečky může mít nastavenou odlišnou barvu a při výpočtu fragmentů jednotlivých pixelů zapisovaných do barvového bufferu se vypočte barva na základě vzdálenosti tohoto pixelu od obou vrcholů (typicky zde nemusíme řešit perspektivu). V OpenGL ES pracuje podobným způsobem fragment shader, samozřejmě za předpokladu, že ho nepřeprogramujeme. Můžeme si to otestovat, a to konkrétně s využitím následujícího fragment shaderu:

precision mediump float;
varying vec4 vColor;
 
void main() {
    gl_FragColor = vColor;
}

Důležité je, že vektor vColor je postupně dopočítán lineární interpolací. Konkrétně se dvakrát zavolá vertex shader (pro každý vrchol úsečky vlastní volání), ve kterém se nastaví barva vrcholů:

attribute vec2 position;
attribute vec4 aColor;
varying vec4 vColor;
 
void main() {
    vColor = aColor;
    gl_Position = vec4(position, 0, 1);
}

Grafická pipeline následně provádí interpolaci a pro každý vykreslovaný fragment zavolá fragment shader. Jinými slovy: pokud neurčíme jinak, interpolace se provádí na pozadí, což není z kódu shaderů patrné.

Specifikace barev vrcholů vykreslovaných úseček

Aby bylo možné provádět interpolaci barvy fragmentů úsečky, je pochopitelně nutné předat barvy vrcholů úseček do vertex shaderu. Ten je musí následně předat do pipeline, která vypočte barvy fragmentů a pro každý fragment zavolá fragment shader. I když to může vypadat zvláštně, jsou zdrojové kódy obou shaderů totožné, jako při vykreslování bodů (různými barvami).

attribute vec2 position;
attribute vec4 aColor;
varying vec4 vColor;
 
void main() {
    vColor = aColor;
    gl_Position = vec4(position, 0, 1);
}
 
 
 
precision mediump float;
varying vec4 vColor;
 
void main() {
    gl_FragColor = vColor;
}

Na straně JavaScriptu se pochopitelně změní obsah pole s barvami. Musíme do něj totiž uložit 3×2×4 prvky (tři úsečky, dva vrcholy každé úsečky, čtyři prvky představující barvu):

// barvy koncových bodů úseček
const colors = [
  1, 0, 0, 1,   0, 1, 0, 1,  // první úsečka
  0, 0, 1, 1,   1, 1, 0, 1,  // druhá úsečka
  0, 0, 0, 1,   1, 1, 1, 1,  // třetí úsečka
];

Další postup je totožný, pochopitelně až na poslední příkaz, který upravíme do podoby:

// vykreslení tří úseček s předáním souřadnic a barev
gl.drawArrays(gl.LINES, 0, 6);

Na kreslicí plochu by měla být vykreslena trojice úseček s gradientním barvovým přechodem:

Trojice úseček, při jejich vykreslování byla využita lineární interpolace barvy.

Obrázek 15: Trojice úseček, při jejich vykreslování byla využita lineární interpolace barvy.

Autor: tisnik, podle licence: Rights Managed

Demonstrační příklad: vykreslení úseček s interpolací barvy

Dnešní poslední demonstrační příklad je (alespoň prozatím) nejvíce komplikovaný. Po jeho spuštění se vykreslí trojice úseček a vzhledem k tomu, že je pro každý vrchol definována odlišná barva, provede grafická pipeline automatickou lineární interpolaci barev:

Školení Zabbix

const vertexShaderSource = `
    attribute vec2 position;
    attribute vec4 aColor;
    varying vec4 vColor;
 
    void main() {
        vColor = aColor;
        gl_Position = vec4(position, 0, 1);
    }
`
 
const fragmentShaderSource = `
    precision mediump float;
    varying vec4 vColor;
 
    void main() {
        gl_FragColor = vColor;
    }
`
 
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");
 
// souřadnice úseček
vertexes = [
   // x   y   x  y
   -0.9,-0.9, 0.9,-0.9,  // horizontální úsečka
   -0.9, 0.9, 0.9, 0.9,  // horizontální úsečka
    0.0,-0.8, 0.0, 0.8,  // vertikální úsečka
];
 
// barvy koncových bodů úseček
const colors = [
  1, 0, 0, 1,   0, 1, 0, 1,  // první úsečka
  0, 0, 1, 1,   1, 1, 0, 1,  // druhá úsečka
  0, 0, 0, 1,   1, 1, 1, 1,  // třetí úsečka
];
 
// 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")
 
 
// 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
                       2*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
                       0                                 // offset
);
gl.enableVertexAttribArray(p);
 
// barvový buffer
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
 
const cdata = new Float32Array(colors);
gl.bufferData(gl.ARRAY_BUFFER, cdata, gl.STATIC_DRAW);
 
const aColor = gl.getAttribLocation(program, "aColor");
gl.enableVertexAttribArray(aColor);
 
// seznam předávaný do vertex shaderu
gl.vertexAttribPointer(aColor, 4, gl.FLOAT, false, 0, 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í tří úseček s předáním souřadnic a barev
gl.drawArrays(gl.LINES, 0, 6);

Repositář s demonstračními příklady

Všechny demonstrační příklady, které jsme si v minulém i v dnešním článku ukázali, je možné spustit přímo z odkazů uvedených v tabulce pod tímto odstavcem. Doplněny jsou i odkazy na zdrojové kódy psané v JavaScriptu:

# Příklad Stručný popis Adresa příkladu
1 step1.html webová stránka s kreslicím plátnem (canvasem) https://tisnik.github.io/tem­p/step1.html
2 step1.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step1.js
     
3 step2.html využití kreslicího plátna s 2D kontextem https://tisnik.github.io/tem­p/step2.html
4 step2.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step2.js
     
5 step3.html vymazání barvového bufferu pro kontext WebGL https://tisnik.github.io/tem­p/step3.html
6 step3.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step3.js
     
7 step4.html transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu https://tisnik.github.io/tem­p/step4.html
8 step4.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step4.js
     
9 step5.html vykreslení jediného bodu na plátno v kontextu WebGL https://tisnik.github.io/tem­p/step5.html
10 step5.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step5.js
     
11 step6.html vykreslení bodu s určením jeho souřadnic ve scéně https://tisnik.github.io/tem­p/step6.html
12 step6.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step6.js
     
13 step7.html vykreslení většího množství bodů do scény https://tisnik.github.io/tem­p/step7.html
14 step7.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step7.js
     
15 step8.html kontrola provedení jednotlivých operací https://tisnik.github.io/tem­p/step8.html
16 step8.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step8.js
     
17 step9.html vykreslení trojice úseček na plochu kreslicího plátna https://tisnik.github.io/tem­p/step9.html
18 step9.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step9.js
     
19 stepA.html nastavení konstantní barvy úseček https://tisnik.github.io/tem­p/stepA.html
20 stepA.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/stepA.js
     
21 stepB.html vykreslení na sebe navazujících úseček s předáním souřadnic https://tisnik.github.io/tem­p/stepB.html
22 stepB.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/stepB.js
     
23 stepC.html vykreslení uzavřené sekvence úseček s předáním souřadnic https://tisnik.github.io/tem­p/stepC.html
24 stepC.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/stepC.js
     
25 stepD.html vykreslení bodů, každý bod může mít libovolnou barvu https://tisnik.github.io/tem­p/stepD.html
26 stepD.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/stepD.js
     
27 stepE.html vykreslení úseček, barva je vypočtena interpolací barev https://tisnik.github.io/tem­p/stepE.html
28 stepE.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/stepE.js

Seriály a články o OpenGL i přidružených technologiích

OpenGL ES a shadery jsou sice nové téma, ovšem na stránkách Roota jsme se již několikrát setkali s podobnými technologiemi. Především byla (a nutno přiznat, že to bylo hodně dávno) popsána původní knihovna OpenGL (pro pamětníky: tehdy testovaná přímo na pracovní stanici SGI), dále OpenGL evaluátory, OpenGL IS (Imaging Subset) a taktéž knihovna GLUT a OpenVG. Odkazy na tyto seriály a články jsou uvedeny pod odstavcem:

  1. Seriál Grafická knihovna OpenGL
    https://www.root.cz/serialy/graficka-knihovna-opengl/
  2. Seriál OpenGL a nadstavbová knihovna GLU
    https://www.root.cz/serialy/opengl-a-nadstavbova-knihovna-glu/
  3. Seriál OpenGL evaluátory
    https://www.root.cz/serialy/opengl-evaluatory/
  4. Seriál OpenGL Imaging Subset
    https://www.root.cz/serialy/opengl-imaging-subset/
  5. Seriál Tvorba přenositelných grafických aplikací využívajících knihovnu GLUT
    https://www.root.cz/serialy/tvorba-prenositelnych-grafickych-aplikaci-vyuzivajicich-knihovnu-glut/
  6. Stručný přehled grafických knihoven pro Raspberry Pi
    https://www.root.cz/clanky/strucny-prehled-grafickych-knihoven-pro-raspberry-pi/
  7. Práce s rastrovými obrázky v knihovně OpenVG
    https://www.root.cz/clanky/prace-s-rastrovymi-obrazky-v-knihovne-openvg/
  8. Práce s rastrovými obrázky v knihovně OpenVG (pokračování)
    https://www.root.cz/clanky/prace-s-nbsp-rastrovymi-obrazky-v-nbsp-knihovne-openvg-pokracovani/
  9. Práce s rastrovými obrázky v knihovně OpenVG (okomentované demonstrační příklady)
    https://www.root.cz/clanky/prace-s-rastrovymi-obrazky-v-knihovne-openvg-okomentovane-demonstracni-priklady/
  10. Použití knihoven OpenVG a EGL (nejenom) na Raspberry Pi
    https://www.root.cz/clanky/pouziti-knihoven-openvg-a-egl-nejenom-na-raspberry-pi/

Odkazy na Internetu

  1. WebGL: 3D Graphics for the Web
    https://www.khronos.org/webgl/
  2. OpenGL Shading Language
    https://en.wikipedia.org/wi­ki/OpenGL_Shading_Language
  3. WebGL (Wikipedia)
    https://en.wikipedia.org/wiki/WebGL
  4. WebGL
    https://wikis.khronos.org/web­gl/Main_Page
  5. OpenGL ES (Wikipedia)
    https://en.wikipedia.org/wi­ki/OpenGL_ES
  6. Float32Array
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Re­ference/Global_Objects/Flo­at32Array
  7. ECMAScript compatibility table
    https://compat-table.github.io/compat-table/es6/
  8. Learn-OpenGLES-Tutorials
    https://github.com/learnopengles/Learn-OpenGLES-Tutorials
  9. Implement OpenGL ES and EGL
    https://source.android.com/doc­s/core/graphics/implement-opengl-es
  10. Khronos Group
    https://www.khronos.org/
  11. Khronos Group (Wikipedia)
    https://en.wikipedia.org/wi­ki/Khronos_Group
  12. Raspberry Pi VideoCore APIs
    http://elinux.org/Raspberry_Pi_Vi­deoCore_APIs
  13. EGL quick reference card
    https://www.khronos.org/files/egl-1–4-quick-reference-card.pdf
  14. EGL Reference Pages Index
    https://www.khronos.org/re­gistry/egl/sdk/docs/man/html/in­dexflat.php
  15. GLSL
    https://glsl.app/
  16. Shader Tutorials by Ronja
    https://www.ronja-tutorials.com/
  17. CanvasRenderingContext2D
    https://developer.mozilla.org/en-US/docs/Web/API/CanvasRen­deringContext2D
  18. WebGL2RenderingContext
    https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext
  19. WebGLRenderingContext
    https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.