V předchozí dvojici článků o technologiích WebGL a OpenGL ES jsme si vysvětlili postup pro vykreslování bodů a úseček. To je ovšem při požadavcích na vykreslení plochy těles (a nikoli pouze na „drátový model“) nedostačující. Dnes si ukážeme řešení spočívající ve vykreslování vyplněných trojúhelníků.
Co se dozvíte v článku
- Postup při vykreslení trojúhelníka konstantní barvou
- První demonstrační příklad: vykreslení trojúhelníka konstantní barvou
- Interpolace barev při rasterizaci trojúhelníků
- Předání barev vrcholů trojúhelníka do vykreslovací pipeline
- Druhý demonstrační příklad: vykreslení trojúhelníka s interpolací barev
- Optimalizace reprezentace plochy vykreslovaných těles: pruhy a trsy trojúhelníků
- Realizace vykreslení pruhu trojúhelníků
- Třetí demonstrační příklad: vykreslení pruhu trojúhelníků
- Realizace vykreslení trsu trojúhelníků
- Čtvrtý demonstrační příklad: vykreslení trsu trojúhelníků: neúplná varianta
- Pátý demonstrační příklad: vykreslení trsu trojúhelníků: úplná varianta
- Základy vykreslování 3D scén
- Paměť hloubky
- Postup při použití paměti hloubky pro vykreslování překrývajících se ploch
- Demonstrační příklad: vykreslení trojúhelníků, které se překrývají, bez použití paměti hloubky
- Vykreslení překrývajících se trojúhelníků s využitím paměti hloubky
- Repositář s demonstračními příklady
- Seriály a články o OpenGL i o přidružených technologiích
- Odkazy na Internetu
OpenGL ES, na rozdíl od klasické knihovny OpenGL, nepodporuje přímé vykreslování čtyřúhelníků ani obecnějších polynomů. To má své dobré důvody, protože by bylo nutné řešit situace, kdy vrcholy neleží v jedné rovině atd. Prakticky jakýkoli povrch tělesa lze rozdělit na trojúhelníky, takže podpora pouze tohoto grafického primitiva by mohla být z tohoto pohledu dostačující. Na druhou stranu je však vhodné omezit množství dat, které se posílají do GPU, takže OpenGL ES podporuje i takzvané pruhy a trsy trojúhelníků, což jsou množiny trojúhelníků, které sdílí společné vrcholy.
Obrázek 1: 3D model čajové konvičky Martina Newella je ve světě 3D grafiky používán ve stejném duchu, jako „Hello, world“ pro aplikace psané pro terminál.
Ovšem ani možnost vykreslování trojúhelníků není plně postačující pro vizualizaci skutečných 3D scén. Musíme přidat ještě jednu technologii, konkrétně technologii pro skrývání těch ploch, které jsou (pro konkrétního pozorovatele) zakryty jinými plochami. V případě OpenGL ES je toto skrývání řešeno pamětí hloubky (depth buffer). Základní koncepty práce s pamětí hloubky si ukážeme v závěrečné části dnešního článku.
Postup při vykreslení trojúhelníka konstantní barvou
V dnešním prvním demonstračním příkladu je ukázáno, jak lze vykreslit trojúhelník, což je základní tvar, z něhož se mohou skládat plochy těles atd. Nejjednodušší budou (prozatím) oba shadery, tj. vertex shader i fragment shader, protože budou mít totožný tvar, jako tomu bylo u úvodních příkladů, které jsme si ukázali v prvním článku o WebGL (vykreslení bodů).
Vertex shader pouze zkonstruuje 4D vektor ze zadaných 3D souřadnic:
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0, 1);
}
Fragment shader nastaví konstantní barvu vykreslování všech fragmentů:
precision mediump float;
void main() {
gl_FragColor = vec4(0.7, 0.7, 0.0, 1);
}
Dále je pochopitelně nutné definovat souřadnice vrcholů, což je šestice hodnot s plovoucí řádovou čárkou:
const vertexes = [
// x y
-0.9, -0.8,
0.9, -0.8,
0.0, 0.7,
];
Vertexy předáme do bufferu, což je postup, který již známe (a tedy není nutné ho znovu opisovat). Lišit se bude pouze poslední příkaz posílaný do OpenGL ES. Vyžádáme si totiž vykreslení trojúhelníku (nebo většího množství trojúhelníků) a určíme, že se do vykreslovací pipeline předá trojice vrcholů:
// vykreslení trojúhelníku gl.drawArrays(gl.TRIANGLES, 0, 3);
Obrázek 2: Jednobarevný trojúhelník.
První demonstrační příklad: vykreslení trojúhelníka konstantní barvou
Úplný zdrojový kód dnešního prvního demonstračního příkladu, který na plátno (canvas) vykreslí trojúhelník konstantní barvou, vypadá následovně:
const vertexShaderSource = `
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0, 1);
}
`
const fragmentShaderSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(0.7, 0.7, 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 vrcholů trojúhelníku
const vertexes = [
// x y
-0.9, -0.8,
0.9, -0.8,
0.0, 0.7,
];
// 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);
// transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
gl.viewport(0, 0, canvas.width, canvas.height);
// nastavení barvy pro vymazání barvového bufferu
gl.clearColor(0, 0, 0, 1);
// vymazání barvového bufferu
gl.clear(gl.COLOR_BUFFER_BIT);
// spuštění programu
gl.useProgram(program);
// vykreslení trojúhelníku
gl.drawArrays(gl.TRIANGLES, 0, 3);
Interpolace barev při rasterizaci trojúhelníků
V předchozím článku jsme si ukázali, že grafická pipeline OpenGL ES při rasterizaci úseček automaticky provádí interpolaci barev. V případě úseček je to jednoduchá lineární interpolace mezi dvojicí barev, které jsou specifikovány pro oba vrcholy úsečky:
Obrázek 3: Trojice úseček, při jejich vykreslování byla využita lineární interpolace barvy.
Podobným způsobem, i když interně nepatrně komplikovanějším, je realizována i interpolace barev při vykreslování trojúhelníků. Zadány jsou tři barvy a výsledek může vypadat následovně (pro trojúhelník, k jehož vrcholům je přiřazena čistě červená, modrá resp. zelená barva):
Obrázek 4: Klasický RGB trojúhelník.
Předání barev vrcholů trojúhelníka do vykreslovací pipeline
Způsob předání barev vrcholů trojúhelníků do vykreslovací pipeline se nijak neliší od postupu, který byl použit při vykreslování úseček. Barvy všech tří vrcholů trojúhelníka mohou být (na straně JavaScriptu) reprezentovány například polem:
// barvy vrcholů trojúhelníku
const colors = [
// R G B A
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
];
Vertex shader předá načtenou barvu do dalších kroků pipeline:
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 barvu z vColor přečte a nastaví gl_FragColor:
precision mediump float;
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}
I transformaci barev z pole do bufferu OpenGL ES jsme si již popsali:
const colorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); const cdata = new Float32Array(colors); gl.bufferData(gl.ARRAY_BUFFER, cdata, gl.STATIC_DRAW);
Přenos bufferu do grafické pipeline:
const aColor = gl.getAttribLocation(program, "aColor");
gl.enableVertexAttribArray(aColor);
gl.vertexAttribPointer(aColor,
4, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
0 // offset
);
Druhý demonstrační příklad: vykreslení trojúhelníka s interpolací barev
Postup vysvětlený v předchozí kapitole je použit v dnešním druhém demonstračním příkladu. Ten by měl na plátno vykreslit klasický RGB trojúhelník, který lze najít v mnoha návodech k OpenGL (nebo i OpenGL ES). Není divu, protože se jedná o základ většiny složitějších scén:
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 vrcholů trojúhelníku
const vertexes = [
// x y
-0.9, -0.8,
0.9, -0.8,
0.0, 0.7,
];
// barvy vrcholů trojúhelníku
const colors = [
// R G B A
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
];
// 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);
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, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
0 // offset
);
// transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
gl.viewport(0, 0, canvas.width, canvas.height);
// nastavení barvy pro vymazání barvového bufferu
gl.clearColor(0, 0, 0, 1);
// vymazání barvového bufferu
gl.clear(gl.COLOR_BUFFER_BIT);
// spuštění programu
gl.useProgram(program);
// vykreslení trojúhelníku
gl.drawArrays(gl.TRIANGLES, 0, 3);
Optimalizace reprezentace plochy vykreslovaných těles: pruhy a trsy trojúhelníků
Základní grafickou primitivou používanou pro více či méně dokonalou reprezentaci plochy vykreslovaných těles jsou jednotlivé trojúhelníky. Povrch tělesa může být rozložen na jednotlivé trojúhelníky například takto:
Obrázek 5: Čajová konvička Martina Newella po triangulaci.
Pokud se nad předchozím modelem zamyslíme, zjistíme, že se sice skutečně jedná o trojúhelníky, které ovšem sdílí své vrcholy s trojúhelníky sousedními. Proto by bylo vhodné do grafické pipeline posílat každou souřadnici (pro společné vrcholy) jen jedenkrát, aby se ušetřila paměť i obsazení sběrnice/portu při přenosu dat. Proto bylo navrženo a implementováno několik technik, které počet přenášených vrcholů do jisté míry snižují. Konkrétně v OpenGL ES můžeme namísto jednotlivých trojúhelníků vykreslovat pruhy trojúhelníků a trsy trojúhelníků. Povšimněte si, v jakém pořadí jsou posílány vrcholy v případě, že tuto optimalizační techniku použijeme:
Obrázek 6: Grafické primitivum GL_TRIANGLE_FAN – trs trojúhelníků.
Obrázek 7: Grafické primitivum GL_TRIANGLE_STRIP – pás složený z trojúhelníků.
Realizace vykreslení pruhu trojúhelníků
Častěji než s trsem trojúhelníků se setkáme s využitím pruhu trojúhelníků (triangle strip) pro reprezentaci částí pláště těles v 3D modelech. Způsob uspořádání vrcholů popisujících pruh trojúhelníků byl ukázán na sedmém obrázku. Pokusme se tedy takový pruh vykreslit (a to i s interpolací barvy – ta je provedena pro každý trojúhelník zvlášť). Vrcholy trojúhelníků uspořádaných do pruhu:
// souřadnice vrcholů trojúhelníků v pruhu
const vertexes = [
// x y
-0.8, -0.8,
-0.4, 0.8,
0.0, -0.8,
0.4, 0.8,
0.8, -0.8,
];
V tomto poli jsou vlastně uloženy souřadnice tří trojúhelníků:
-0.8, -0.8,
-0.4, 0.8,
0.0, -0.8,
-0.4, 0.8,
0.0, -0.8,
0.4, 0.8,
0.0, -0.8,
0.4, 0.8,
0.8, -0.8,
Totéž platí pro barvy:
// barvy vrcholů trojúhelníku
const colors = [
// R G B A
1, 0, 0, 1, // červená
1, 1, 0, 1, // žlutá
0, 1, 0, 1, // zelená
0, 1, 1, 1, // zelenomodrá
0, 0, 1, 1, // modrá
];
Což jsou ve skutečnosti barvy tří trojic vrcholů:
1, 0, 0, 1, // červená
1, 1, 0, 1, // žlutá
0, 1, 0, 1, // zelená
1, 1, 0, 1, // žlutá
0, 1, 0, 1, // zelená
0, 1, 1, 1, // zelenomodrá
0, 1, 0, 1, // zelená
0, 1, 1, 1, // zelenomodrá
0, 0, 1, 1, // modrá
Samotné vykreslení se odlišuje pouze parametrem předaným do operace drawArrays:
// vykreslení pruhu trojúhelníků gl.drawArrays(gl.TRIANGLE_STRIP, 0, 5);
Výsledek by měl vypadat následovně:
Obrázek 8: Pruh složený z trojice trojúhelníků.
Třetí demonstrační příklad: vykreslení pruhu trojúhelníků
Úplný zdrojový kód demonstračního příkladu, který vykreslí pruh trojúhelníků (pochopitelně i s korektní interpolací barev) vypadá následovně:
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 vrcholů trojúhelníku
const vertexes = [
// x y
-0.8, -0.8,
-0.4, 0.8,
0.0, -0.8,
0.4, 0.8,
0.8, -0.8,
];
// barvy vrcholů trojúhelníku
const colors = [
// R G B A
1, 0, 0, 1, // červená
1, 1, 0, 1, // žlutá
0, 1, 0, 1, // zelená
0, 1, 1, 1, // zelenomodrá
0, 0, 1, 1, // modrá
];
// 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);
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, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
0 // offset
);
// 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í pruhu trojúhelníků
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 5);
Realizace vykreslení trsu trojúhelníků
Trs trojúhelníků (triangle fan) se může v 3D modelech uplatnit v případě, že je zapotřebí „zalátat díry“ v plášti, který má složitou topologii (póly na kouli, držátko pokličky výše zobrazené čajové konvičky atd.). V poli vrcholů se nejdříve uvádí souřadnice vrcholu, který je společný všem trojúhelníkům v trsu. Další vrcholy se poté mohou zapisovat v různém pořadí (platí, že třetí až předposlední vrchol je vždy sdílený dvěma trojúhelníky). Pokud nebude druhý vrchol zopakován na konci celého seznamu, nebude trs úplný. Rozdíly budou ukázány na dvojici demonstračních příkladů uvedených i s výslednými obrázky v následujících kapitolách.
Čtvrtý demonstrační příklad: vykreslení trsu trojúhelníků: neúplná varianta
Pokusme se vykreslit šestiúhelník s nabídkou barevné palety. Uprostřed šestiúhelníku bude bílá barva a jeho jednotlivé vrcholy budou postupně vybarveny červenou, žlutou, zelenou, zelenomodrou, modrou a fialovou barvou. Nejprve si necháme vypočítat souřadnice jednotlivých vrcholů:
// souřadnice vrcholů trojúhelníků
const vertexes = [
// x y
0.0, 0.0,
Math.cos(0*Math.PI/3), Math.sin(0*Math.PI/3),
Math.cos(1*Math.PI/3), Math.sin(1*Math.PI/3),
Math.cos(2*Math.PI/3), Math.sin(2*Math.PI/3),
Math.cos(3*Math.PI/3), Math.sin(3*Math.PI/3),
Math.cos(4*Math.PI/3), Math.sin(4*Math.PI/3),
Math.cos(5*Math.PI/3), Math.sin(5*Math.PI/3),
];
Pole s barvami je vyplněno explicitně:
// barvy vrcholů trojúhelníku
const colors = [
// R G B A
1, 1, 1, 1, // bílá
1, 0, 0, 1, // červená
1, 1, 0, 1, // žlutá
0, 1, 0, 1, // zelená
0, 1, 1, 1, // zelenomodrá
0, 0, 1, 1, // modrá
1, 0, 1, 1, // fialová
];
Pokud se pokusíme tato dvě pole převést na bufferu a předat do vykreslovací pipeliny, získáme po spuštění příkazu:
// vykreslení trsu trojúhelníků gl.drawArrays(gl.TRIANGLE_FAN, 0, 7);
…tento obrázek:
Obrázek 9: Trs složený z pěti trojúhelníků.
Následuje výpis celého zdrojového kódu příkladu, který předchozí obrázek vykreslil:
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 vrcholů trojúhelníku
const vertexes = [
// x y
0.0, 0.0,
Math.cos(0*Math.PI/3), Math.sin(0*Math.PI/3),
Math.cos(1*Math.PI/3), Math.sin(1*Math.PI/3),
Math.cos(2*Math.PI/3), Math.sin(2*Math.PI/3),
Math.cos(3*Math.PI/3), Math.sin(3*Math.PI/3),
Math.cos(4*Math.PI/3), Math.sin(4*Math.PI/3),
Math.cos(5*Math.PI/3), Math.sin(5*Math.PI/3),
];
// barvy vrcholů trojúhelníku
const colors = [
// R G B A
1, 1, 1, 1, // bílá
1, 0, 0, 1, // červená
1, 1, 0, 1, // žlutá
0, 1, 0, 1, // zelená
0, 1, 1, 1, // zelenomodrá
0, 0, 1, 1, // modrá
1, 0, 1, 1, // fialová
];
// 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);
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, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
0 // offset
);
// 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í trsu trojúhelníků
gl.drawArrays(gl.TRIANGLE_FAN, 0, 7);
Pátý demonstrační příklad: vykreslení trsu trojúhelníků: úplná varianta
Nyní je pravděpodobně zřejmé, jak se musí postupovat, abychom získali plný šestiúhelník. Na konec seznamu vrcholů přidáme druhý vrchol (zapsaný ovšem tak, aby byla zachována posloupnost výpočtů):
// souřadnice vrcholů trojúhelníků
const vertexes = [
// x y
0.0, 0.0,
Math.cos(0*Math.PI/3), Math.sin(0*Math.PI/3),
Math.cos(1*Math.PI/3), Math.sin(1*Math.PI/3),
Math.cos(2*Math.PI/3), Math.sin(2*Math.PI/3),
Math.cos(3*Math.PI/3), Math.sin(3*Math.PI/3),
Math.cos(4*Math.PI/3), Math.sin(4*Math.PI/3),
Math.cos(5*Math.PI/3), Math.sin(5*Math.PI/3),
Math.cos(6*Math.PI/3), Math.sin(6*Math.PI/3),
];
Kvůli tomu, že druhý a poslední vrchol jsou totožné, je nutné upravit i barvy. Konkrétně mají oba zmíněné vrcholy totožnou (červenou) barvu:
// barvy vrcholů trojúhelníku
const colors = [
// R G B A
1, 1, 1, 1, // bílá
1, 0, 0, 1, // červená
1, 1, 0, 1, // žlutá
0, 1, 0, 1, // zelená
0, 1, 1, 1, // zelenomodrá
0, 0, 1, 1, // modrá
1, 0, 1, 1, // fialová
1, 0, 0, 1, // červená
];
Po takto provedených úpravách bude výsledek po spuštění demonstračního příkladu vypadat takto:
Obrázek 10: Trs složený ze šesti trojúhelníků.
A jak je již dobrým zvykem, uvedeme si i úplný zdrojový kód takto upraveného demonstračního příkladu:
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 vrcholů trojúhelníku
const vertexes = [
// x y
0.0, 0.0,
Math.cos(0*Math.PI/3), Math.sin(0*Math.PI/3),
Math.cos(1*Math.PI/3), Math.sin(1*Math.PI/3),
Math.cos(2*Math.PI/3), Math.sin(2*Math.PI/3),
Math.cos(3*Math.PI/3), Math.sin(3*Math.PI/3),
Math.cos(4*Math.PI/3), Math.sin(4*Math.PI/3),
Math.cos(5*Math.PI/3), Math.sin(5*Math.PI/3),
Math.cos(6*Math.PI/3), Math.sin(6*Math.PI/3),
];
// barvy vrcholů trojúhelníku
const colors = [
// R G B A
1, 1, 1, 1, // bílá
1, 0, 0, 1, // červená
1, 1, 0, 1, // žlutá
0, 1, 0, 1, // zelená
0, 1, 1, 1, // zelenomodrá
0, 0, 1, 1, // modrá
1, 0, 1, 1, // fialová
1, 0, 0, 1, // červená
];
// 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);
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, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
0 // offset
);
// 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í trsu trojúhelníků
gl.drawArrays(gl.TRIANGLE_FAN, 0, 8);
Základy vykreslování 3D scén
V dalším textu se již budeme zabývat vykreslováním 3D scén. Základ již máme připravený, protože dokážeme vykreslovat trojúhelníky i větší celky složené z trojúhelníků. To je ovšem skutečně jen základ, protože budeme muset vyřešit další problémy, zejména pak:
- Nastavení pozorovatele (kamery) do scény
- Vykreslení skutečně viditelných ploch, nikoli těch částí scény, které jsou zakryty či leží zcela mimo zorné pole pozorovatele
- Nastavení osvětlení
- Výpočet osvětlení ploch (osvětlovací model)
- Namapování textur na trojúhelníky tvořící povrch zobrazovaných těles
- Výpočet mlhy a podobných efektů
- Získání efektu pohybu v případě, že se zobrazují animace (motion blur atd.)
Paměť hloubky
Jak v klasické variantě OpenGL, tak i v OpenGL ES se způsob vykreslování překrývajících se ploch na obrazovce většinou řídí paměti hloubky (depth buffer), která je někdy nazývána Z-buffer (což sice nemusí být příliš přesné, ovšem tento termín je mezi programátory zažit). Jedná se o paměť, ve které jsou většinou uloženy informace zajišťující vykreslení pouze viditelných částí těles, což konkrétně znamená, že vzdálenější plošky (přesněji fragmenty) jsou překryty ploškami (fragmenty) bližšími. Funkci a význam paměti hloubky lze však při vykreslování průběžně měnit, takže je možné naprogramovat některé zajímavé efekty, například zobrazení stínů.
V naprosté většině případů je funkce paměti hloubky nastavena tak, že se při rasterizaci testuje Z-ová hloubka (vzdálenost od stínítka obrazovky, ve skutečnosti se většinou testuje převrácená hodnota Z-ové hloubky, ale to je již interní věcí GPU) vytvořeného fragmentu s hloubkou uloženou ve framebufferu na pozici vkládaného fragmentu. V případě, že je Z-ová hloubka fragmentu menší než Z-ová hodnota uložená na příslušném místě ve framebufferu, fragment se do framebufferu uloží a samozřejmě přepíše i Z-ovou pozici. Pokud je Z-ová hloubka fragmentu naopak větší, je jasné, že je fragment ve výsledné scéně neviditelný a proto je zahozen (není zapsán ani do barvového bufferu ani do paměti hloubky).
U paměti hloubky je velmi důležitá hodnota počtu bitů pro uložení hloubky. Pokud je například depth buffer pouze osmibitový (tak ho měly implementovány první grafické akcelerátory na PC), je možné rozlišit pouze 256 vzdáleností, což může vést k vytvoření vizuálních artefaktů ve scéně. V dnešní době se používají 16ti bitové, 24bitové hloubky i hodnoty reprezentované s plovoucí řádovou čárkou (navíc se nemusí ukládat přímo Z-ová souřadnice, ale její převrácená hodnota). V takovém případě se množství nežádoucích artefaktů snižuje (avšak nastat mohou, typicky pokud vytvoříme dva trojúhelníky, které se částečně překrývají).
Postup při použití paměti hloubky pro vykreslování překrývajících se ploch
Aby bylo možné využít paměť hloubky pro korektní vykreslování těch ploch, které jsou (i když někdy jen částečně) zakryty jinými plochami, je nutné provést několik operací:
- Vyžádat si paměť hloubky (depth buffer) při získávání kontextu WebGL
- Vertex shader musí zpracovávat 3D souřadnice
- Vymazat paměť hloubky zápisem zvolené hloubky do všech prvků
- Nastavit operaci prováděnou při zjišťování, zda je zpracovávaný fragment blíže k pozorovateli, než již zapsaný fragment
- A samozřejmě je nutné předávat 3D souřadnice vrcholů, nejenom 2D souřadnice
Aby bylo možné paměť hloubky povolit, je nutné si nejdříve vyžádat, aby vůbec byla v paměti dostupné GPU alokována. To se ve WebGL provádí následujícím způsobem:
// vytvoření a získání kontextu WebGL (3D grafika)
const gl = canvas.getContext("webgl", {
depth: true
});
if (!gl) {
throw new Error("WebGL is not supported or context creation failed");
}
print("WebGL context created");
Následně se upraví vertex shader. Ten bude akceptovat atribut position, který ale už nebude typu vec2 (dvourozměrný vektor), ale vec3 (vektor se třemi prvky). A to vede k nutnosti úpravy řádku, ve kterém se nastavuje stavová proměnná gl_Position:
attribute vec3 position;
attribute vec4 aColor;
varying vec4 vColor;
void main() {
vColor = aColor;
gl_Position = vec4(position, 1);
}
V dalším kroku se bude mazat obsah paměti hloubky. Provádí se to podobným způsobem, jako mazání barvového bufferu. Pro připomenutí:
// 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);
Smazání paměti hloubky se provede totožným příkazem gl.clear, ovšem nejdříve musíme nastavit hodnotu, která se do paměti hloubky zapíše:
// hodnota zapisovaná do paměti hloubky gl.clearDepth(1.0); // vymazání barvového bufferu i paměti hloubky gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
Dále je nutné paměť hloubky povolit a nastavit podmínku, která se provádí s každým fragmentem v grafické pipelině. Konkrétně budeme požadovat, aby se zapsaly (zobrazily) ty fragmenty, které jsou blíže k pozorovateli:
// povolení Z-bufferu gl.enable(gl.DEPTH_TEST); // podmínka prováděná s fragmenty gl.depthFunc(gl.LESS);
Demonstrační příklad: vykreslení trojúhelníků, které se překrývají, bez použití paměti hloubky
Nejprve si ukažme, jakým způsobem se vykreslí scéna se třemi trojúhelníky definovanými v ploše, které se částečně překrývají. Souřadnice těchto trojúhelníků jsou skutečně jen dvourozměrné a vertex shader doplňuje za souřadnice z a w výchozí hodnoty:
// souřadnice vrcholů trojúhelníku
const vertexes = [
// x y
-0.8, -0.5,
0.2, -0.5,
-0.3, 0.4,
-0.2, -0.5,
0.8, -0.5,
0.3, 0.4,
-0.5, 0.0,
0.5, 0.0,
0.0, 0.9,
];
Paměť hloubky je zakázána (resp. přesněji řečeno není povolena) a trojúhelníky se vykreslí běžným způsobem příkazem:
// vykreslení trojúhelníků gl.drawArrays(gl.TRIANGLES, 0, 9);
V tomto případě se trojúhelníky vykreslují (rasterizují) v takovém pořadí, v jakém jsou uloženy do bufferu, jenž je posílán do GPU. Druhý trojúhelník tedy částečně překreslí trojúhelník první a třetí trojúhelník částečně překreslí první dva trojúhelníky:
Obrázek 11: Překrývající se trojúhelníky.
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
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 vrcholů trojúhelníku
const vertexes = [
// x y
-0.8, -0.5,
0.2, -0.5,
-0.3, 0.4,
-0.2, -0.5,
0.8, -0.5,
0.3, 0.4,
-0.5, 0.0,
0.5, 0.0,
0.0, 0.9,
];
// barvy vrcholů trojúhelníku
const colors = [
// R G B A
1, 0, 0, 1,
1, 0, 0, 1,
1, 0, 0, 1,
0, 1, 0, 1,
0, 1, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
0, 0, 1, 1,
0, 0, 1, 1,
];
// 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);
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, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
0 // offset
);
// transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
gl.viewport(0, 0, canvas.width, canvas.height);
// nastavení barvy pro vymazání barvového bufferu
gl.clearColor(0, 0, 0, 1);
// vymazání barvového bufferu
gl.clear(gl.COLOR_BUFFER_BIT);
// spuštění programu
gl.useProgram(program);
// vykreslení trojúhelníku
gl.drawArrays(gl.TRIANGLES, 0, 9);
Vykreslení překrývajících se trojúhelníků s využitím paměti hloubky
V dnešním posledním demonstračním příkladu se opět vykreslí trojice trojúhelníků, které se budou na obrazovce překrývat. Ovšem nyní je u trojúhelníků specifikována i vzdálenost vrcholů od projekční roviny (třetí souřadnice):
// souřadnice vrcholů trojúhelníku
const vertexes = [
// x y
-0.8, -0.5, -0.5,
0.2, -0.5, -0.5,
-0.3, 0.4, -0.5,
-0.2, -0.5, 0.0,
0.8, -0.5, 0.0,
0.3, 0.4, 0.0,
-0.5, 0.0, 0.5,
0.5, 0.0, 0.5,
0.0, 0.9, 0.5,
];
První trojúhelník (červený) je nejblíže pozorovateli a z tohoto důvodu překrývá ostatní dva trojúhelníky. A to přesto, že je vykreslen jako první. V grafické pipeline se tedy při vykreslování zeleného a modrého trojúhelníku některé fragmenty vůbec neuloží do barvového bufferu a ani do paměti hloubky:
Obrázek 12: Trojúhelníky, které mají odlišnou vzdálenost od pozorovatele.
Úplný zdrojový kód dnešního posledního demonstračního příkladu vypadá následovně:
const vertexShaderSource = `
attribute vec3 position;
attribute vec4 aColor;
varying vec4 vColor;
void main() {
vColor = aColor;
gl_Position = vec4(position, 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", {
depth: true
});
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 vrcholů trojúhelníku
const vertexes = [
// x y
-0.8, -0.5, -0.5,
0.2, -0.5, -0.5,
-0.3, 0.4, -0.5,
-0.2, -0.5, 0.0,
0.8, -0.5, 0.0,
0.3, 0.4, 0.0,
-0.5, 0.0, 0.5,
0.5, 0.0, 0.5,
0.0, 0.9, 0.5,
];
// barvy vrcholů trojúhelníku
const colors = [
// R G B A
1, 0, 0, 1,
1, 0, 0, 1,
1, 0, 0, 1,
0, 1, 0, 1,
0, 1, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
0, 0, 1, 1,
0, 0, 1, 1,
];
// 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,
3, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
3*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, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
4*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
0 // offset
);
// 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);
// povolení Z-bufferu
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LESS);
gl.clearDepth(1.0);
// vymazání barvového bufferu
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// spuštění programu
gl.useProgram(program);
// vykreslení trojúhelníku
gl.drawArrays(gl.TRIANGLES, 0, 9);
Repositář s demonstračními příklady
Všechny demonstrační příklady, které jsme si v předminulém, minulém i v dnešním článku ukázali, je možné spustit přímo ve webovém prohlížeči přes odkazy uvedené v tabulce pod tímto odstavcem. Doplněny jsou i odkazy na zdrojové kódy psané v JavaScriptu (ty se automaticky načítají do HTML stránek):
| # | Příklad | Stručný popis | Adresa příkladu |
|---|---|---|---|
| 1 | step1.html | webová stránka s kreslicím plátnem (canvasem) | https://tisnik.github.io/temp/step1.html |
| 2 | step1.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step1.js |
| 3 | step2.html | využití kreslicího plátna s 2D kontextem | https://tisnik.github.io/temp/step2.html |
| 4 | step2.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step2.js |
| 5 | step3.html | vymazání barvového bufferu pro kontext WebGL | https://tisnik.github.io/temp/step3.html |
| 6 | step3.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step3.js |
| 7 | step4.html | transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu | https://tisnik.github.io/temp/step4.html |
| 8 | step4.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step4.js |
| 9 | step5.html | vykreslení jediného bodu na plátno v kontextu WebGL | https://tisnik.github.io/temp/step5.html |
| 10 | step5.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step5.js |
| 11 | step6.html | vykreslení bodu s určením jeho souřadnic ve scéně | https://tisnik.github.io/temp/step6.html |
| 12 | step6.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step6.js |
| 13 | step7.html | vykreslení většího množství bodů do scény | https://tisnik.github.io/temp/step7.html |
| 14 | step7.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step7.js |
| 15 | step8.html | kontrola provedení jednotlivých operací | https://tisnik.github.io/temp/step8.html |
| 16 | step8.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step8.js |
| 17 | step9.html | vykreslení trojice úseček na plochu kreslicího plátna | https://tisnik.github.io/temp/step9.html |
| 18 | step9.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step9.js |
| 19 | stepA.html | nastavení konstantní barvy úseček | https://tisnik.github.io/temp/stepA.html |
| 20 | stepA.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/stepA.js |
| 21 | stepB.html | vykreslení na sebe navazujících úseček s předáním souřadnic | https://tisnik.github.io/temp/stepB.html |
| 22 | stepB.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/stepB.js |
| 23 | stepC.html | vykreslení uzavřené sekvence úseček s předáním souřadnic | https://tisnik.github.io/temp/stepC.html |
| 24 | stepC.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/stepC.js |
| 25 | stepD.html | vykreslení bodů, každý bod může mít libovolnou barvu | https://tisnik.github.io/temp/stepD.html |
| 26 | stepD.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/stepD.js |
| 27 | stepE.html | vykreslení úseček, barva je vypočtena interpolací barev | https://tisnik.github.io/temp/stepE.html |
| 28 | stepE.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/stepE.js |
| 29 | triangle1.html | vykreslení trojúhelníku konstantní barvou | https://tisnik.github.io/temp/triangle1.html |
| 30 | triangle1.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle1.js |
| 31 | triangle2.html | interpolace barev při vyplňování trojúhelníku | https://tisnik.github.io/temp/triangle2.html |
| 32 | triangle2.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle2.js |
| 33 | triangle3.html | vykreslení pásu trojúhelníků | https://tisnik.github.io/temp/triangle3.html |
| 34 | triangle3.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle3.js |
| 35 | triangle4.html | vykreslení trsu trojúhelníků – neúplná varianta | https://tisnik.github.io/temp/triangle4.html |
| 36 | triangle4.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle4.js |
| 37 | triangle5.html | vykreslení trsu trojúhelníků – úplná varianta | https://tisnik.github.io/temp/triangle5.html |
| 38 | triangle5.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle5.js |
| 39 | triangle6.html | překrývající se trojúhelníky (Z-buffer není použit) | https://tisnik.github.io/temp/triangle6.html |
| 40 | triangle6.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle6.js |
| 41 | triangle7.html | překrývající se trojúhelníky (Z-buffer je použit) | https://tisnik.github.io/temp/triangle7.html |
| 42 | triangle7.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle7.js |
Seriály a články o OpenGL i o 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. Pro úplnost byly přidány i předchozí dva články o WebGL a OpenGL ES:
- 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/ - WebGL: tvorba 3D grafiky s využitím programovatelné vykreslovací pipeline
https://www.root.cz/clanky/webgl-tvorba-3d-grafiky-s-vyuzitim-programovatelne-vykreslovaci-pipeline/ - WebGL: tvorba 3D grafiky s využitím programovatelné vykreslovací pipeline (2. část)
https://www.root.cz/clanky/webgl-tvorba-3d-grafiky-s-vyuzitim-programovatelne-vykreslovaci-pipeline-2-cast/
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
