Už jsme si vysvětlili všechny důležité koncepty. Nastává tedy ideální čas pro to, abychom vykreslili první skutečnou 3D scénu. Začneme jednoduchým příkladem, konkrétně zobrazením krychle reprezentované drátovým modelem (wireframe).
Začneme jednoduchým příkladem – zobrazením krychle reprezentované drátovým modelem (wireframe), tj. dvanácti úsečkami, které tvoří hrany krychle. 3D zobrazení bude realizováno v několika krocích:
Prozatím jsme do vertex shaderu předávali koncové body úseček atd., které byly definovány v 2D ploše, tj. jednalo se o dvojici souřadnic (typ vec2). Vertex shader k těmto dvěma souřadnicím přidal nulovou z-ovou souřadnici a jedničkovou váhu (W):
attribute vec2 position;
attribute vec4 aColor;
varying vec4 vColor;
void main() {
vColor = aColor;
gl_Position = vec4(position, 0, 1);
}Nyní budeme muset do shaderu předávat pro každý koncový bod tři souřadnice (typ vec3), ke kterým shader přidá pouze váhu:
attribute vec3 position;
attribute vec4 aColor;
varying vec4 vColor;
void main() {
vColor = aColor;
gl_Position = vec4(position, 1);
}V praxi tak můžeme nadeklarovat pole vrcholů dvanácti úseček, které tvoří hrany 3D krychle:
// omezeno tak, aby bylo možné později rotovat
const min_x = -0.5
const max_x = 0.5
const min_y = min_x
const max_y = max_x
const min_z = min_x
const max_z = max_x
// souřadnice úseček
const vertexes = [
// přední stěna
// x y z x y z
min_x, min_y, min_z, max_x, min_y, min_z,
min_x, max_y, min_z, max_x, max_y, min_z,
min_x, min_y, min_z, min_x, max_y, min_z,
max_x, min_y, min_z, max_x, max_y, min_z,
// zadní stěna
// x y z x y z
min_x, min_y, max_z, max_x, min_y, max_z,
min_x, max_y, max_z, max_x, max_y, max_z,
min_x, min_y, max_z, min_x, max_y, max_z,
max_x, min_y, max_z, max_x, max_y, max_z,
// propojení přední a zadní stěny
// x y z x y z
min_x, min_y, min_z, min_x, min_y, max_z,
min_x, max_y, min_z, min_x, max_y, max_z,
max_x, min_y, min_z, max_x, min_y, max_z,
max_x, max_x, min_z, max_x, max_x, max_z,
];Nadeklarujeme i pole s barvami úseček (bude konstantní):
// barvy koncových bodů úseček
const colors = [
// přední stěna
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
// zadní stěna
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
// propojení přední a zadní stěny
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1,
];Při specifikaci, jaké vrcholy se budou předávat do vertex shaderu, nesmíme zapomenout na to, že druhý parametr musí obsahovat hodnotu 3 a nikoli 2 (dtto pátý parametr):
// 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);Samotné vykreslení dvanácti úseček vypadá takto (hodnota 24 odpovídá 24 vrcholům):
// vykreslení dvanácti úseček s předáním souřadnic a barev gl.drawArrays(gl.LINES, 0, 24);
Obrázek 1: Scéna s krychlí, ovšem zobrazená v půdorysu.
Autor: tisnik, podle licence: Rights Managed
První demonstrační příklad: 3D krychle vykreslená do 2D plochy bez aplikace transformacíÚplný zdrojový kód dnešního prvního demonstračního příkladu, který po svém spuštění ve webovém prohlížeči (otestovat je možné zde) vykreslí 3D krychli do 2D plochy (prozatím bez aplikace transformací typu rotace či aplikace perspektivy), 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");
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");
// omezeno tak, aby bylo možné později rotovat
const min_x = -0.5
const max_x = 0.5
const min_y = min_x
const max_y = max_x
const min_z = min_x
const max_z = max_x
// souřadnice úseček
const vertexes = [
// přední stěna
// x y z x y z
min_x, min_y, min_z, max_x, min_y, min_z,
min_x, max_y, min_z, max_x, max_y, min_z,
min_x, min_y, min_z, min_x, max_y, min_z,
max_x, min_y, min_z, max_x, max_y, min_z,
// zadní stěna
// x y z x y z
min_x, min_y, max_z, max_x, min_y, max_z,
min_x, max_y, max_z, max_x, max_y, max_z,
min_x, min_y, max_z, min_x, max_y, max_z,
max_x, min_y, max_z, max_x, max_y, max_z,
// propojení přední a zadní stěny
// x y z x y z
min_x, min_y, min_z, min_x, min_y, max_z,
min_x, max_y, min_z, min_x, max_y, max_z,
max_x, min_y, min_z, max_x, min_y, max_z,
max_x, max_x, min_z, max_x, max_x, max_z,
];
// barvy koncových bodů úseček
const colors = [
// přední stěna
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
// zadní stěna
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
// propojení přední a zadní stěny
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 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);
// 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, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
0,
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í dvanácti úseček s předáním souřadnic a barev
gl.drawArrays(gl.LINES, 0, 24);Transformační matice, homogenní souřadnice a lineární transformace v 3D graficeTransformační matice představují v počítačové grafice prostředek pro vyjádření lineárních transformací. Mezi základní lineární transformace patří posun, otáčení, změna měřítka a zkosení. Jestliže máme tři body, které leží na jedné přímce, tak po aplikaci libovolné lineární transformace získáme nové tři body, které také leží na přímce (ovšem jejich vzdálenost může být odlišná). Všechny lineární transformace lze vyjádřit maticí o rozměrech n x n, kde n je dimenze vektoru či bodu, který transformujeme.
V případě, že provádíme lineární transformace v ploše, vyjadřujeme souřadnici dvěma složkami – x a y. Lineární transformaci lze zapsat jako výpočet nových souřadnic x' a y':
x'=x*a11+y*a12,
y'=x*a21+y*a22.
Členy a11, a12, a21 a a22 představují parametry lineární transformace.
Přehlednější je však vektorově/maticový zápis, kdy dvojici [x, y] označíme jako v, dvojici [x', y'] jako v' a hodnoty a11, a12, a21 a a22 zapíšeme jako prvky do matice M o rozměrech 2x2. Potom lze celou lineární transformaci zapsat jedním vztahem:
v'=M*v – v tomto případě jsou v a v' sloupcové vektory.
Poznámka: můžeme se setkat i s alternativním „otočeným“ zápisem se řádkovými vektory, ovšem v grafice je transformační matice většinou zapsána před vektorem, tedy tak, jako v předchozím odstavci.
Pro zadání lineárních transformací v trojrozměrném prostoru by nám měla stačit transformační matice o rozměrech 3×3 prvky. To je skutečně pravda, ale v počítačové grafice často potřebujeme kromě lineárních transformací vyjádřit i perspektivní projekci. Proto se ke třem souřadnicím [x, y, z] přidává ještě čtvrtá souřadnice označovaná w ze slova weight – váha. Bod je potom vyjádřen čtveřicí [x, y, z, 1] a vektor [x, y, z, 0]. Transformační matice je poté zvětšena na rozměry 4×4 prvky (a mluvíme o homogenních souřadnicích).
Po provedení transformace získáme souřadnice nového bodu, které mají obecně tvar [x', y', z', w']. My však víme, že pro body platí, že čtvrtá souřadnice je rovna jedničce. Z tohoto důvodu musíme ostatní tři souřadnice vydělit souřadnicí poslední (w'), čímž získáme normovaný bod: [x'/w', y'/w', z'/w', w'/w']=[x'/w', y'/w', z'/w', 1]. Toto vydělení poslední souřadnicí se v počítačové grafice někdy poněkud nesprávně nazývá perspektivní korekce.
Důležitou vlastností lineárních transformací je, že je lze skládat. Složením dvou transformací vyjádřených maticemi M1 a M2 vznikne transformace Mc=M2x M1, kde operátor x značí maticový součin. Takto lze skládat libovolné množství transformací, což přináší značné urychlení při vlastním vykreslování, neboť složení transformací se provede pouze jednou a teprve výsledná transformace se aplikuje na body v rovině nebo prostoru. Je pouze zapotřebí zajistit správné pořadí provádění transformací, protože maticový součin není komutativní operace (což si ostatně můžeme snadno ověřit v praxi).
Vertex shader provádějící lineární transformaciV klasickém OpenGL existují tři transformační matice, které se postupně aplikují na body (vrcholy, vertexy) popř. i na normály vrcholů. První transformační matice se jmenuje ModelView matrix. Na tuto matici se můžeme dívat jako na spojení modelové matice a pohledové matice, protože se používá jak pro nastavení pozice kamery, tak i pro manipulaci s objekty (modely) ve scéně. Druhá transformační matice se jmenuje Projection matrix a používá se pro nastavení perspektivní projekce kamery. Třetí transformační matice se jmenuje Viewport matrix a používá se po provedení perspektivní projekce k mapování objektů z abstraktních souřadnic do souřadnic okna. Tato poslední matice ve skutečnosti pouze provádí transformaci v dvojrozměrné ploše, proto se s ní v OpenGL nepracuje jako s „plnohodnotnou maticí“. Kromě těchto tří matic můžeme měnit matici, která se používá při mapování textur na povrch objektů. Tato matice se nazývá Texture matrix.
V případě OpenGL ES je provedení transformací nutně explicitně naprogramovat (až na viewport matrix). Celý problém se rozděluje na dvě části:
Druhá část je realizována ve vertex shaderu. Příkladem může být následující vertex shader, kterému se předávají trojrozměrné souřadnice vrcholů v parametru position (to již známe, pouze je position rozšířen o třetí dimenzi). Z předaného vrcholu se vytvoří 4D vektor přidáním čtvrté souřadnice s hodnotou 1 (1=bod, 0=vektor). Vertex shader vypočte souřadnice v prostoru kamery tak, že provede součin matice transformace (umatrix) s právě vypočteným 4D vektorem. Výsledek je uložen do interní stavové proměnné gl_Position (což již známe):
attribute vec3 position;
uniform mat4 umatrix;
attribute vec4 aColor;
varying vec4 vColor;
void main() {
vColor = aColor;
gl_Position = umatrix * vec4(position, 1);
}Poznámka: operátor * zde znamená násobení matice se sloupcovým vektorem.
Výpočet transformační matice: základní variantaZbývá nám dopočítat transformační matici a předat ji vykreslovacímu řetězci v parametru umatrix. Vše si otestujeme na jednotkové matici. Vynásobení této matice s nějakým 4D vektorem vrací stejný vektor, jaký vstupoval do výpočtu:
function matrix_identity() {
return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
}Způsob předání této matice do vykreslovacího řetězce může vypadat následovně:
// jednotková matice const matrix = matrix_identity(); // navázání na proměnnou dostupnou ve vertex shaderu const umatrix = gl.getUniformLocation(program, "umatrix"); gl.uniformMatrix4fv(umatrix, false, matrix); // vykreslení dvanácti úseček s předáním souřadnic a barev gl.drawArrays(gl.LINES, 0, 24);
Povšimněte si, že skutečně používáme identifikátor umatrix. Výsledkem bude scéna se stejnou krychlí, jako tomu bylo v předchozím příkladu (ovšem souřadnice se skutečně transformují):
Obrázek 2: Scéna s krychlí, ovšem zobrazená v půdorysu po aplikaci transformační matice.
Autor: tisnik, podle licence: Rights Managed
Druhý demonstrační příklad: transformace reprezentovaná jednotkovou maticíNásleduje výpis celého zdrojového kódu dnešního druhého demonstračního příkladu, který po svém otevření ve webovém prohlížeči opět vykreslí přední stěnu 3D krychle, protože aplikace jednotkové matice při provádění transformací nemá vliv na pozici vrcholů v prostoru. Důležité ovšem je, že se tato transformace provádí, čehož využijeme v dalších čtyřech příkladech:
const vertexShaderSource = `
attribute vec3 position;
uniform mat4 umatrix;
attribute vec4 aColor;
varying vec4 vColor;
void main() {
vColor = aColor;
gl_Position = umatrix * vec4(position, 1);
}
`
const fragmentShaderSource = `
precision mediump float;
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}
`
function print(message) {
console.log(message);
}
function matrix_identity() {
return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 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");
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");
// omezeno tak, aby bylo možné později rotovat
const min_x = -0.5
const max_x = 0.5
const min_y = min_x
const max_y = max_x
const min_z = min_x
const max_z = max_x
// souřadnice úseček
const vertexes = [
// přední stěna
// x y z x y z
min_x, min_y, min_z, max_x, min_y, min_z,
min_x, max_y, min_z, max_x, max_y, min_z,
min_x, min_y, min_z, min_x, max_y, min_z,
max_x, min_y, min_z, max_x, max_y, min_z,
// zadní stěna
// x y z x y z
min_x, min_y, max_z, max_x, min_y, max_z,
min_x, max_y, max_z, max_x, max_y, max_z,
min_x, min_y, max_z, min_x, max_y, max_z,
max_x, min_y, max_z, max_x, max_y, max_z,
// propojení přední a zadní stěny
// x y z x y z
min_x, min_y, min_z, min_x, min_y, max_z,
min_x, max_y, min_z, min_x, max_y, max_z,
max_x, min_y, min_z, max_x, min_y, max_z,
max_x, max_x, min_z, max_x, max_x, max_z,
];
// barvy koncových bodů úseček
const colors = [
// přední stěna
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
// zadní stěna
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
// propojení přední a zadní stěny
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 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);
// 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, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
0,
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);
// jednotková matice
const matrix = matrix_identity();
// navázání na proměnnou dostupnou ve vertex shaderu
const umatrix = gl.getUniformLocation(program, "umatrix");
gl.uniformMatrix4fv(umatrix, false, matrix);
// vykreslení dvanácti úseček s předáním souřadnic a barev
gl.drawArrays(gl.LINES, 0, 24);Rotace kostky v 3D prostoruDalším krokem, který musíme udělat, je zajistit rotaci kostky (krychle) v 3D prostoru. Budeme tedy muset nadefinovat transformační matici s realizací rotace okolo osy x a nezávislou rotaci okolo osy y. Ve 2D ploše vypadá matice rotace takto:
| cos θ -sin θ | | sin θ cos θ |
Poznámka: to lze snadno odvodit, pokud vstupní vektor bude reprezentován v polárních souřadnicích a aplikujeme vzorce pro siny a cosiny součtu dvou úhlů (původního úhlu a úhlu rotace).
Výše uvedenou matici můžeme rozšířit do 3D a vytvořit tak matici reprezentující rotaci okolo osy x:
| 1 0 0 | | 0 cos θ -sin θ | | 0 sin θ cos θ |
A po rozšíření na matici 4×4 (váha se transformuje na stejnou váhu):
| 1 0 0 0| | 0 cos θ -sin θ 0| | 0 sin θ cos θ 0| | 0 0 0 1|
Uložení matice v paměti je ovšem realizováno jednorozměrným polem a prvky jsou z pohledu WebGL uloženy po sloupcích (nejprve čtyři prvky prvního sloupce, poté čtyři prvky sloupce druhého atd.). Z tohoto důvodu při definici matice v JavaScriptu musíme matici transponovat:
function matrix_rotate_around_x(a) {
const c = Math.cos(a);
const s = Math.sin(a);
return new Float32Array([
1, 0, 0, 0,
0, c, s, 0,
0, -s, c, 0,
0, 0, 0, 1
]);
}Naprosto stejným způsobem lze realizovat matici pro otočení okolo y-ové osy:
function matrix_rotate_around_y(a) {
const c = Math.cos(a);
const s = Math.sin(a);
return new Float32Array([
c, 0, -s, 0,
0, 1, 0, 0,
s, 0, c, 0,
0, 0, 0, 1
]);
}Složení transformacíNyní již dokážeme vytvořit matici reprezentující transformaci rotace okolo osy x a jinou matici reprezentující rotaci okolo osy y. My ovšem potřebujeme, aby se na vrcholy úseček aplikovala jediná transformace. A zde přichází na řadu velmi elegantní vlastnost známá z lineární algebry: pokud provedeme maticový součin dvou matic představujících určité transformace, získáme novou matici, která reprezentuje složení obou transformací.
Poznámka: Maticový součin není komutativní operací (záleží na pořadí operandů); to ovšem není ani složení transformací: posun následovaný rotací je odlišný od rotace následované posunem.
Realizace maticového součinu v JavaScriptu může vypadat takto:
function matrix_multiply(a, b) {
const out = new Float32Array(16);
for (let col = 0; col <; col++) {
for (let row = 0; row < 4; row++) {
out[col * 4 + row] =
a[0 * 4 + row] * b[col * 4 + 0] +
a[1 * 4 + row] * b[col * 4 + 1] +
a[2 * 4 + row] * b[col * 4 + 2] +
a[3 * 4 + row] * b[col * 4 + 3];
}
}
return out;
}A složení transformací (rotace okolo dvou os) může vypadat následovně:
// matice rotace const rx = matrix_rotate_around_x(Math.PI / 6); const ry = matrix_rotate_around_y(Math.PI / 6); const matrix = matrix_multiply(rx, ry);
Výsledkem je již (konečně!) nějaká skutečná 3D scéna:
Obrázek 3:Rotace krychle okolo osy x a y.
Autor: tisnik, podle licence: Rights Managed
Třetí demonstrační příklad: zobrazení 3D kostky z různých úhlů pohleduOpět si ukažme úplný zdrojový kód demonstračního příkladu, který již dokáže vykreslit 3D kostku na webové stránce z libovolného úhlu, přičemž tento úhel je reprezentován dvojicí rotací: kolem x-ové osy a okolo osy y-ové:
const vertexShaderSource = `
attribute vec3 position;
uniform mat4 umatrix;
attribute vec4 aColor;
varying vec4 vColor;
void main() {
vColor = aColor;
gl_Position = umatrix * vec4(position, 1);
}
`
const fragmentShaderSource = `
precision mediump float;
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}
`
function print(message) {
console.log(message);
}
function matrix_identity() {
return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
}
function matrix_rotate_around_x(a) {
const c = Math.cos(a);
const s = Math.sin(a);
return new Float32Array([
1, 0, 0, 0,
0, c, s, 0,
0, -s, c, 0,
0, 0, 0, 1
]);
}
function matrix_rotate_around_y(a) {
const c = Math.cos(a);
const s = Math.sin(a);
return new Float32Array([
c, 0, -s, 0,
0, 1, 0, 0,
s, 0, c, 0,
0, 0, 0, 1
]);
}
function matrix_multiply(a, b) {
const out = new Float32Array(16);
for (let col = 0; col <; col++) {
for (let row = 0; row < 4; row++) {
out[col * 4 + row] =
a[0 * 4 + row] * b[col * 4 + 0] +
a[1 * 4 + row] * b[col * 4 + 1] +
a[2 * 4 + row] * b[col * 4 + 2] +
a[3 * 4 + row] * b[col * 4 + 3];
}
}
return out;
}
// 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");
// omezeno tak, aby bylo možné později rotovat
const min_x = -0.5
const max_x = 0.5
const min_y = min_x
const max_y = max_x
const min_z = min_x
const max_z = max_x
// souřadnice úseček
const vertexes = [
// přední stěna
// x y z x y z
min_x, min_y, min_z, max_x, min_y, min_z,
min_x, max_y, min_z, max_x, max_y, min_z,
min_x, min_y, min_z, min_x, max_y, min_z,
max_x, min_y, min_z, max_x, max_y, min_z,
// zadní stěna
// x y z x y z
min_x, min_y, max_z, max_x, min_y, max_z,
min_x, max_y, max_z, max_x, max_y, max_z,
min_x, min_y, max_z, min_x, max_y, max_z,
max_x, min_y, max_z, max_x, max_y, max_z,
// propojení přední a zadní stěny
// x y z x y z
min_x, min_y, min_z, min_x, min_y, max_z,
min_x, max_y, min_z, min_x, max_y, max_z,
max_x, min_y, min_z, max_x, min_y, max_z,
max_x, max_x, min_z, max_x, max_x, max_z,
];
// barvy koncových bodů úseček
const colors = [
// přední stěna
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
// zadní stěna
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
// propojení přední a zadní stěny
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 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);
// 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, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
0,
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);
// matice rotace
const rx = matrix_rotate_around_x(Math.PI / 6);
const ry = matrix_rotate_around_y(Math.PI / 6);
const matrix = matrix_multiply(rx, ry);
// navázání na proměnnou dostupnou ve vertex shaderu
const umatrix = gl.getUniformLocation(program, "umatrix");
gl.uniformMatrix4fv(umatrix, false, matrix);
// vykreslení dvanácti úseček s předáním souřadnic a barev
gl.drawArrays(gl.LINES, 0, 24);Perspektivní projekceDíky rotaci krychle jsme sice dosáhli určitého pokroku, ovšem výsledné 3D scéně stále něco chybí. Prozatím je totiž použita takzvaná ortogonální projekce (ovšem korektnější je mluvit o rovnoběžné projekci), která zachovává velikost objektů nezávisle na jejich vzdálenosti od kamery (pozorovatele). Taktéž jsou zachovány další vlastnosti: například dvě rovnoběžné hrany ve světovém prostoru zůstanou rovnoběžné i po jejich zobrazení do 2D roviny s využitím rovnoběžné projekce. V technické praxi se ortogonální projekce používá pro zobrazení nárysu, půdorysu a bokorysu, ovšem do této kategorie spadá i axonometrická a isometrická projekce, s nimiž se taktéž můžeme setkat (například i v některých počítačových hrách).
Našim cílem ovšem není tvorba technických výkresů, ale vizualizace 3D scény, která by měla vypadat podobně, jako při sledování reálné scény pozorovatelem. V takovém případě se musí namísto ortogonální (resp. rovnoběžné) projekce použít projekce perspektivní. Existuje více variant perspektivní projekce (jednobodová, dvoubodová, tříbodová), ovšem tyto varianty se využívají v případě, kdy jsou 3D objekty zobrazovány na výkres „ručně“ s využitím pravítka a kružítka.
My ovšem máme k dispozici odlišnou techniku: transformaci vektorů představujících souřadnice úseček nebo trojúhelníků, které se mají zobrazit. Vykreslovací pipeline po transformaci s využitím transformační matice (viz předchozí kapitoly) výsledné souřadnice [x, y, z, w] upraví. Konkrétně vydělí souřadnice x, y a z čtvrtou souřadnicí w, takže výsledkem bude vektor [x/w, y/w, z/w, 1]. A právě toho můžeme využít, protože ta nejjednodušší implementace perspektivní projekce spočívá v tom, že se souřadnice x a y vydělí souřadnicí z (tedy čím dále je bod od pozorovatele – tedy má vyšší hodnotu z – tím blíže se bude výsledná souřadnice nacházet ke středu [0, 0]. Takže vlastně postačuje zajistit maličkost: transformovat souřadnice takovým způsobem, aby se do w uložila původní hodnota z, vynásobená určitým koeficientem („mírou perspektivy“).
Matice reprezentující základní perspektivní projekciVe skutečnosti je základní matice, která reprezentuje perspektivní projekci ve směru osy z, velmi jednoduchá. Musíme si totiž uvědomit, že se musí modifikovat čtvrtá souřadnice w výsledného vektoru, a to na základě souřadnice z vektoru vstupního. Výsledkem aplikace transformace bude vektor, jehož čtvrtá souřadnice w už nebude rovna jedničce. Vykreslovací řetězec automaticky provede úpravu složek vektoru a právě v této chvíli vlastně dojde k výpočtu perspektivy.
Předchozí matice počítaly složku w takto:
w'= 0x + 0y + 0z + 1w
Proveďme úpravu do podoby:
w'= 0x + 0y + 1,5z + 0w
To znamená: čím dále se nachází souřadnice vrcholu úsečky od pozorovatele, tím větší bude hodnota w' a tím se do větší míry projeví perspektiva (vlastně se zmenší hodnoty x' a y' – souřadnice budou blíže středu).
Výsledná matice bude vypadat následovně:
function matrix_perspective() {
return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 1.5,
0, 0, 0, 0
]);
}Aplikace perspektivy:
// matice rotace const perspective = matrix_perspective(); const translate = matrix_translate(1.0); const matrix = matrix_multiply(perspective, translate);
Obrázek 4: Základní perspektivní projekce.
Autor: tisnik, podle licence: Rights Managed
Čtvrtý demonstrační příklad: zobrazení 3D kostky s využitím základní perspektivní projekceÚplný zdrojový kód příkladu, který po svém otevření ve webovém prohlížeči zobrazí 3D kostku s využitím základní perspektivní projekce (bez rotace atd), vypadá následovně:
const vertexShaderSource = `
attribute vec3 position;
uniform mat4 umatrix;
attribute vec4 aColor;
varying vec4 vColor;
void main() {
vColor = aColor;
gl_Position = umatrix * vec4(position, 1);
}
`
const fragmentShaderSource = `
precision mediump float;
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}
`
function print(message) {
console.log(message);
}
function matrix_identity() {
return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
}
function matrix_translate(z) {
return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, z, 1
]);
}
function matrix_rotate_around_x(a) {
const c = Math.cos(a);
const s = Math.sin(a);
return new Float32Array([
1, 0, 0, 0,
0, c, s, 0,
0, -s, c, 0,
0, 0, 0, 1
]);
}
function matrix_rotate_around_y(a) {
const c = Math.cos(a);
const s = Math.sin(a);
return new Float32Array([
c, 0, -s, 0,
0, 1, 0, 0,
s, 0, c, 0,
0, 0, 0, 1
]);
}
function matrix_perspective() {
return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 1.5,
0, 0, 0, 0
]);
}
function matrix_multiply(a, b) {
const out = new Float32Array(16);
for (let col = 0; col < 4; col++) {
for (let row = 0; row < 4; row++) {
out[col * 4 + row] =
a[0 * 4 + row] * b[col * 4 + 0] +
a[1 * 4 + row] * b[col * 4 + 1] +
a[2 * 4 + row] * b[col * 4 + 2] +
a[3 * 4 + row] * b[col * 4 + 3];
}
}
return out;
}
// 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");
// omezeno tak, aby bylo možné později rotovat
const min_x = -0.5
const max_x = 0.5
const min_y = min_x
const max_y = max_x
const min_z = min_x
const max_z = max_x
// souřadnice úseček
const vertexes = [
// přední stěna
// x y z x y z
min_x, min_y, min_z, max_x, min_y, min_z,
min_x, max_y, min_z, max_x, max_y, min_z,
min_x, min_y, min_z, min_x, max_y, min_z,
max_x, min_y, min_z, max_x, max_y, min_z,
// zadní stěna
// x y z x y z
min_x, min_y, max_z, max_x, min_y, max_z,
min_x, max_y, max_z, max_x, max_y, max_z,
min_x, min_y, max_z, min_x, max_y, max_z,
max_x, min_y, max_z, max_x, max_y, max_z,
// propojení přední a zadní stěny
// x y z x y z
min_x, min_y, min_z, min_x, min_y, max_z,
min_x, max_y, min_z, min_x, max_y, max_z,
max_x, min_y, min_z, max_x, min_y, max_z,
max_x, max_x, min_z, max_x, max_x, max_z,
];
// barvy koncových bodů úseček
const colors = [
// přední stěna
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
// zadní stěna
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
// propojení přední a zadní stěny
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 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);
// 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, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
0,
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);
// matice rotace
const perspective = matrix_perspective();
const translate = matrix_translate(1.0);
const matrix = matrix_multiply(perspective, translate);
// navázání na proměnnou dostupnou ve vertex shaderu
const umatrix = gl.getUniformLocation(program, "umatrix");
gl.uniformMatrix4fv(umatrix, false, matrix);
// vykreslení dvanácti úseček s předáním souřadnic a barev
gl.drawArrays(gl.LINES, 0, 24);Zorný úhel a přední a zadní rovinaV praxi se ovšem projekce nastavuje poněkud sofistikovanějším způsobem. Většinou je totiž nutné specifikovat tzv. view frustum, což je oblast ve tvaru komolého jehlanu, která je (po provedení projekce) viditelná ve výsledné scéně. Tělesa nebo jejich části, které se nachází mimo tuto oblast, jsou ořezány (pokud by se nepoužila perspektivní projekce, namísto jehlanu by se jednalo o kvádr):
Obrázek 5: Komolý jehlan obsahující tu část prostoru, která se promítne do výsledné scény.
Autor: MithrandirMage, podle licence: CC BY-SA 3.0
Výpočet projekce se může (minimálně ve výuce) rozdělit na dvě transformační matice. První matice transformuje pouze souřadnice x a y tak, že se započítá zorný úhel a poměr výšky a šířky. Jedná se vlastně o transformaci změny měřítka v osách x a y:
function matrix_fov(fovy, aspect) {
const f = 1.0 / Math.tan(fovy / 2);
return new Float32Array([
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
}Druhá matice je již složitější. Zatímco souřadnice x a y se zde nemění, modifikují se souřadnice z a w (což naznačuje, že se zde vlastně připravujeme na výpočet perspektivy):
function matrix_near_far(near, far) {
const nf = 1.0 / (near - far);
return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, (far + near) * nf, -1,
0, 0, (2 * far * near) * nf, 0,
]);
}Poznámka: pravděpodobně nejkomplexnější vysvětlení této matice naleznete na stránkách Matrix Transformations a OpenGL Projection Matrix. V případě zájmu se můžeme k této velmi zajímavé problematice vrátit.
Opět je důležité pořadí násobení matic, protože to není komutativní operace:
// transformační matice const aspect = canvas.width / canvas.height; const fov = matrix_fov(Math.PI / 3, aspect); const near_far = matrix_near_far(0.1, 100); const near_far_fov = matrix_multiply(near_far, fov); const translate = matrix_translate(-2.0); const rx = matrix_rotate_around_x(Math.PI / 6); const ry = matrix_rotate_around_y(Math.PI / 6); const matrix = matrix_multiply(near_far_fov, matrix_multiply(translate, matrix_multiply(rx, ry)));
Výsledkem aplikace této transformační matice by měl být trojrozměrný model krychle s korektně vypočtenou perspektivou:
Obrázek 6: Kombinace rotace a perspektivní projekce.
Autor: tisnik, podle licence: Rights Managed
Pátý demonstrační příklad: krychle v 3D prostoruV dnešním pátém demonstračním příkladu je ukázán praktický výpočet transformační matice, která kromě rotace krychle okolo dvou os umožňuje i výpočet perspektivní projekce. Tento příklad je možné otestovat na této stránce a jeho úplný zdrojový kód vypadá následovně:
const vertexShaderSource = `
attribute vec3 position;
uniform mat4 umatrix;
attribute vec4 aColor;
varying vec4 vColor;
void main() {
vColor = aColor;
gl_Position = umatrix * vec4(position, 1);
}
`
const fragmentShaderSource = `
precision mediump float;
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}
`
function print(message) {
console.log(message);
}
function matrix_identity() {
return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
}
function matrix_translate(z) {
return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, z, 1
]);
}
function matrix_rotate_around_x(a) {
const c = Math.cos(a);
const s = Math.sin(a);
return new Float32Array([
1, 0, 0, 0,
0, c, s, 0,
0, -s, c, 0,
0, 0, 0, 1
]);
}
function matrix_rotate_around_y(a) {
const c = Math.cos(a);
const s = Math.sin(a);
return new Float32Array([
c, 0, -s, 0,
0, 1, 0, 0,
s, 0, c, 0,
0, 0, 0, 1
]);
}
function matrix_fov(fovy, aspect) {
const f = 1.0 / Math.tan(fovy / 2);
return new Float32Array([
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
}
function matrix_near_far(near, far) {
const nf = 1.0 / (near - far);
return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, (far + near) * nf, -1,
0, 0, (2 * far * near) * nf, 0,
]);
}
function matrix_multiply(a, b) {
const out = new Float32Array(16);
for (let col = 0; col < 4; col++) {
for (let row = 0; row < 4; row++) {
out[col * 4 + row] =
a[0 * 4 + row] * b[col * 4 + 0] +
a[1 * 4 + row] * b[col * 4 + 1] +
a[2 * 4 + row] * b[col * 4 + 2] +
a[3 * 4 + row] * b[col * 4 + 3];
}
}
return out;
}
// 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");
// omezeno tak, aby bylo možné později rotovat
const min_x = -0.5
const max_x = 0.5
const min_y = min_x
const max_y = max_x
const min_z = min_x
const max_z = max_x
// souřadnice úseček
const vertexes = [
// přední stěna
// x y z x y z
min_x, min_y, min_z, max_x, min_y, min_z,
min_x, max_y, min_z, max_x, max_y, min_z,
min_x, min_y, min_z, min_x, max_y, min_z,
max_x, min_y, min_z, max_x, max_y, min_z,
// zadní stěna
// x y z x y z
min_x, min_y, max_z, max_x, min_y, max_z,
min_x, max_y, max_z, max_x, max_y, max_z,
min_x, min_y, max_z, min_x, max_y, max_z,
max_x, min_y, max_z, max_x, max_y, max_z,
// propojení přední a zadní stěny
// x y z x y z
min_x, min_y, min_z, min_x, min_y, max_z,
min_x, max_y, min_z, min_x, max_y, max_z,
max_x, min_y, min_z, max_x, min_y, max_z,
max_x, max_x, min_z, max_x, max_x, max_z,
];
// barvy koncových bodů úseček
const colors = [
// přední stěna
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
// zadní stěna
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
// propojení přední a zadní stěny
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 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);
// 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, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
0,
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);
// transformační matice
const aspect = canvas.width / canvas.height;
const fov = matrix_fov(Math.PI / 3, aspect);
const near_far = matrix_near_far(0.1, 100);
const near_far_fov = matrix_multiply(near_far, fov);
const translate = matrix_translate(-2.0);
const rx = matrix_rotate_around_x(Math.PI / 6);
const ry = matrix_rotate_around_y(Math.PI / 6);
const matrix = matrix_multiply(near_far_fov, matrix_multiply(translate, matrix_multiply(rx, ry)));
// navázání na proměnnou dostupnou ve vertex shaderu
const umatrix = gl.getUniformLocation(program, "umatrix");
gl.uniformMatrix4fv(umatrix, false, matrix);
// vykreslení dvanácti úseček s předáním souřadnic a barev
gl.drawArrays(gl.LINES, 0, 24);Jediná matice realizující perspektivní projekciV praxi, resp. možná přesněji řečeno v sociální bublině vývojářů používajících OpenGL, se většinou obě výše popsané matice spojují do matice jediné. V JavaScriptu lze tuto matici zkonstruovat s využitím následující funkce:
function matrix_perspective_fov(fovy, aspect, near, far) {
const f = 1.0 / Math.tan(fovy / 2);
const nf = 1 / (near - far);
return new Float32Array([
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, (far + near) * nf, -1,
0, 0, (2 * far * near) * nf, 0
]);
}Výpočet transformační matice předávané do vertex shaderu se změní na:
// transformační matice const aspect = canvas.width / canvas.height; const perspective_fov = matrix_perspective_fov(Math.PI / 3, aspect, 0.1, 100); const translate = matrix_translate(-2.0); const rx = matrix_rotate_around_x(Math.PI / 6); const ry = matrix_rotate_around_y(Math.PI / 6); const matrix = matrix_multiply(perspective_fov, matrix_multiply(translate, matrix_multiply(rx, ry)));
Výsledek transformace s využitím této matice by měl vypadat následovně:
Obrázek 7:Kombinace rotace a perspektivní projekce.
Autor: tisnik, podle licence: Rights Managed
Poznámka: modifikovat tedy lze tyto parametry: úhel pohledu, bližší a vzdálenější ořezovou rovinu, rotaci okolo osy x a rotaci okolo osy y.
Šestý demonstrační příklad: rotace krychle v 3D prostoru s perspektivní projekcíUpravený výpočet transformační matice je implementován v dnešním šestém a současně i posledním demonstračním příkladu, který si můžete otestovat na této stránce. Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
const vertexShaderSource = `
attribute vec3 position;
uniform mat4 umatrix;
attribute vec4 aColor;
varying vec4 vColor;
void main() {
vColor = aColor;
gl_Position = umatrix * vec4(position, 1);
}
`
const fragmentShaderSource = `
precision mediump float;
varying vec4 vColor;
void main() {
gl_FragColor = vColor;
}
`
function print(message) {
console.log(message);
}
function matrix_identity() {
return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
}
function matrix_translate(z) {
return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, z, 1
]);
}
function matrix_rotate_around_x(a) {
const c = Math.cos(a);
const s = Math.sin(a);
return new Float32Array([
1, 0, 0, 0,
0, c, s, 0,
0, -s, c, 0,
0, 0, 0, 1
]);
}
function matrix_rotate_around_y(a) {
const c = Math.cos(a);
const s = Math.sin(a);
return new Float32Array([
c, 0, -s, 0,
0, 1, 0, 0,
s, 0, c, 0,
0, 0, 0, 1
]);
}
function matrix_perspective_fov(fovy, aspect, near, far) {
const f = 1.0 / Math.tan(fovy / 2);
const nf = 1 / (near - far);
return new Float32Array([
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, (far + near) * nf, -1,
0, 0, (2 * far * near) * nf, 0
]);
}
function matrix_multiply(a, b) {
const out = new Float32Array(16);
for (let col = 0; col < 4; col++) {
for (let row = 0; row < 4; row++) {
out[col * 4 + row] =
a[0 * 4 + row] * b[col * 4 + 0] +
a[1 * 4 + row] * b[col * 4 + 1] +
a[2 * 4 + row] * b[col * 4 + 2] +
a[3 * 4 + row] * b[col * 4 + 3];
}
}
return out;
}
// 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");
// omezeno tak, aby bylo možné později rotovat
const min_x = -0.5
const max_x = 0.5
const min_y = min_x
const max_y = max_x
const min_z = min_x
const max_z = max_x
// souřadnice úseček
const vertexes = [
// přední stěna
// x y z x y z
min_x, min_y, min_z, max_x, min_y, min_z,
min_x, max_y, min_z, max_x, max_y, min_z,
min_x, min_y, min_z, min_x, max_y, min_z,
max_x, min_y, min_z, max_x, max_y, min_z,
// zadní stěna
// x y z x y z
min_x, min_y, max_z, max_x, min_y, max_z,
min_x, max_y, max_z, max_x, max_y, max_z,
min_x, min_y, max_z, min_x, max_y, max_z,
max_x, min_y, max_z, max_x, max_y, max_z,
// propojení přední a zadní stěny
// x y z x y z
min_x, min_y, min_z, min_x, min_y, max_z,
min_x, max_y, min_z, min_x, max_y, max_z,
max_x, min_y, min_z, max_x, min_y, max_z,
max_x, max_x, min_z, max_x, max_x, max_z,
];
// barvy koncových bodů úseček
const colors = [
// přední stěna
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1,
// zadní stěna
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
1, 0, 0, 1, 1, 0, 0, 1,
// propojení přední a zadní stěny
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 0, 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);
// 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, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
0,
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);
// transformační matice
const aspect = canvas.width / canvas.height;
const perspective_fov = matrix_perspective_fov(Math.PI / 3, aspect, 0.1, 100);
const translate = matrix_translate(-2.0);
const rx = matrix_rotate_around_x(Math.PI / 6);
const ry = matrix_rotate_around_y(Math.PI / 6);
const matrix = matrix_multiply(perspective_fov, matrix_multiply(translate, matrix_multiply(rx, ry)));
// navázání na proměnnou dostupnou ve vertex shaderu
const umatrix = gl.getUniformLocation(program, "umatrix");
gl.uniformMatrix4fv(umatrix, false, matrix);
// vykreslení dvanácti úseček s předáním souřadnic a barev
gl.drawArrays(gl.LINES, 0, 24);Repositář s demonstračními příkladyVš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 |
| 43 | cube1.html | 3D krychle vykreslená do 2D plochy bez aplikace transformací | https://tisnik.github.io/temp/cube1.html |
| 44 | cube1.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube1.js |
| 45 | cube2.html | transformace reprezentovaná jednotkovou maticí | https://tisnik.github.io/temp/cube2.html |
| 46 | cube2.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube2.js |
| 47 | cube3.html | zobrazení 3D kostky z různých úhlů pohledu | https://tisnik.github.io/temp/cube3.html |
| 48 | cube3.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube3.js |
| 49 | cube4.html | zobrazení 3D kostky s využitím základní perspektivní projekce | https://tisnik.github.io/temp/cube4.html |
| 50 | cube4.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube4.js |
| 51 | cube5.html | krychle v 3D prostoru | https://tisnik.github.io/temp/cube5.html |
| 52 | cube5.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube5.js |
| 53 | cube6.html | rotace krychle v 3D prostoru s perspektivní projekcí | https://tisnik.github.io/temp/cube6.html |
| 54 | cube6.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube6.js |
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í tři články o WebGL a OpenGL ES:
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | WebGL: tvorba 3D grafiky s využitím programovatelné vykreslovací pipeline | 0 | 6.48 | 21-09-2026 |
| 2 | Видеочат в 3D своими руками? Легко! | 5 | 3 | 29-06-2011 |
| 3 | Облачная фотограмметрия, или Делаем 3D-модель по фотографии | 0 | 5 | 08-02-2012 |
| 4 | Мій досвід використання 3D-принтера: від першої моделі до творчих проєктів | 0 | 10 | 13-07-2025 |
| 5 | Českobudějovická nemocnice jako první v republice operuje mozek novou technologií | 0 | 6.89 | 26-07-2026 |
| 6 | Когда читаешь про углеродное волокно в 3D-печати — сначала кажется, ... | 0 | 5.5 | 06-08-2026 |
| 7 | Следим за разработками в Бауманке — и вот это уже ... | 0 | 4.92 | 06-08-2026 |
| 8 | Can Autodesk Tools Be Used to Create 3D Models for a Website? | 0 | 4.49 | 03-08-2026 |
| 9 | The Last of Us po česku. Podhoubí prorůstá slámou, startup z něj chce vyrábět obaly ve velkém | 0 | 7.54 | 20-09-2026 |