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
- Skript, na kterém budou postaveny další demonstrační příklady
- Kontrola provedení jednotlivých operací s grafickou pipeline
- Kontrola provedení příkazů OpenGL ES
- Úprava skriptu tak, aby kontrolovala každá prováděná operace
- Typy grafických primitiv podporovaných v OpenGL ES
- Postup při vykreslování úseček
- Demonstrační příklad: vykreslení tří úseček konstantní barvou
- Nastavení barvy vykreslovaných úseček
- Demonstrační příklad: nastavení barvy vykreslovaných úseček
- Vykreslení polyčáry
- Vykreslení polygonu složeného z lineárních segmentů
- Předání barev jednotlivých vykreslovaných bodů do grafické pipeline
- Demonstrační příklad: vykreslení pěti bodů pěti různými barvami
- Lineární interpolace barev při vykreslování úseček
- Specifikace barev vrcholů vykreslovaných úseček
- Demonstrační příklad: vykreslení úseček s interpolací barvy
- Repositář s demonstračními příklady
- Seriály a články o OpenGL i přidružených technologiích
- 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:
Obrázek 1: Vykreslení pětice bodů na zvolených souřadnicích.
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");
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")
| 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);
Obrázek 2: Vykreslení pěti bodů konstantní barvou. Provádí se kontrola všech operací.
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ě) |
Obrázek 3: Grafické primitivum GL_POINTS - izolované body.
Obrázek 4: Grafické primitivum GL_LINES - izolované úsečky.
Obrázek 5: Grafické primitivum GL_LINE_STRIP - polyčára složená z lineárních segmentů.
Obrázek 6: Grafické primitivum GL_LINE_LOOP - polygon složený z lineárních segmentů.
Obrázek 7: Grafické primitivum GL_TRIANGLES - izolované trojúhelníky.
Obrázek 8: Grafické primitivum GL_TRIANGLE_FAN - trs trojúhelníků.
Obrázek 9: Grafické primitivum GL_TRIANGLE_STRIP - pás složený z trojúhelníků.
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:
Obrázek 10: Vykreslení tří úseček konstantní barvou.
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:
Obrázek 11: Vykreslení tří úseček barvou ovladatelnou z JavaScriptu
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:
Obrázek 12: Vykreslení polyčáry složené z lineárních segmentů.
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):
Obrázek 13: Vykreslení polygonu složeného z lineárních segmentů
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:
Obrázek 14: Pětice bodů, každý vykreslený odlišnou barvou.
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:
Obrázek 15: Trojice úseček, při jejich vykreslování byla využita lineární interpolace barvy.
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:
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/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 |
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:
- Seriál Grafická knihovna OpenGL
https://www.root.cz/serialy/graficka-knihovna-opengl/ - Seriál OpenGL a nadstavbová knihovna GLU
https://www.root.cz/serialy/opengl-a-nadstavbova-knihovna-glu/ - Seriál OpenGL evaluátory
https://www.root.cz/serialy/opengl-evaluatory/ - Seriál OpenGL Imaging Subset
https://www.root.cz/serialy/opengl-imaging-subset/ - 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/ - Stručný přehled grafických knihoven pro Raspberry Pi
https://www.root.cz/clanky/strucny-prehled-grafickych-knihoven-pro-raspberry-pi/ - Práce s rastrovými obrázky v knihovně OpenVG
https://www.root.cz/clanky/prace-s-rastrovymi-obrazky-v-knihovne-openvg/ - 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/ - 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/ - 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
- 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
