Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

WebGL: tvorba 3D grafiky a zobrazení 3D scén

Дата публикации: 12-10-2026 22:00:05

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:

  1. Úprava vertex shaderu tak, aby akceptoval 3D souřadnice vrcholů úseček
  2. Úprava polí/bufferů s koncovými body úseček: nyní budou obsahovat 3D souřadnice
  3. Vylepšení vertex shaderu o vynásobení vstupního vektoru transformační maticí
  4. Výpočet transformační matice umožňující rotaci krychle okolo dvou os
  5. Úprava transformační matice: přidání výpočtu perspektivy
Vertex shader akceptující souřadnice koncových bodů reprezentované 3D vektorem

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);
Scéna s krychlí, ovšem zobrazená v půdorysu.

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 grafice

Transformač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í transformaci

V 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:

  1. Výpočet matice transformace – lze provést pro celý snímek jen jedenkrát
  2. Transformace všech vrcholů těles (bodů, úseček, trojúhelníků) ze světových souřadnic do „prostoru kamery“ (camera space, eye space).

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í varianta

Zbý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í):

Scéna s krychlí, ovšem zobrazená v půdorysu po aplikaci transformační matice.

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 prostoru

Další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:

Rotace krychle okolo osy x a y.

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ů pohledu

Opě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í projekce

Dí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í projekci

Ve 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);
Základní perspektivní projekce.

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í rovina

V 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):

Komolý jehlan obsahující tu část prostoru, která se promítne do výsledné scény.

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:

Kombinace rotace a perspektivní projekce.

Obrázek 6: Kombinace rotace a perspektivní projekce.

Autor: tisnik, podle licence: Rights Managed

Pátý demonstrační příklad: krychle v 3D prostoru

V 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í projekci

V 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ě:

Kombinace rotace a perspektivní projekce.

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ě:

Školení Hacking

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ří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/tem­p/step1.html
2 step1.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step1.js
     
3 step2.html využití kreslicího plátna s 2D kontextem https://tisnik.github.io/tem­p/step2.html
4 step2.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step2.js
     
5 step3.html vymazání barvového bufferu pro kontext WebGL https://tisnik.github.io/tem­p/step3.html
6 step3.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step3.js
     
7 step4.html transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu https://tisnik.github.io/tem­p/step4.html
8 step4.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step4.js
     
9 step5.html vykreslení jediného bodu na plátno v kontextu WebGL https://tisnik.github.io/tem­p/step5.html
10 step5.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step5.js
     
11 step6.html vykreslení bodu s určením jeho souřadnic ve scéně https://tisnik.github.io/tem­p/step6.html
12 step6.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step6.js
     
13 step7.html vykreslení většího množství bodů do scény https://tisnik.github.io/tem­p/step7.html
14 step7.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step7.js
     
15 step8.html kontrola provedení jednotlivých operací https://tisnik.github.io/tem­p/step8.html
16 step8.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step8.js
     
17 step9.html vykreslení trojice úseček na plochu kreslicího plátna https://tisnik.github.io/tem­p/step9.html
18 step9.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step9.js
     
19 stepA.html nastavení konstantní barvy úseček https://tisnik.github.io/tem­p/stepA.html
20 stepA.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/stepA.js
     
21 stepB.html vykreslení na sebe navazujících úseček s předáním souřadnic https://tisnik.github.io/tem­p/stepB.html
22 stepB.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/stepB.js
     
23 stepC.html vykreslení uzavřené sekvence úseček s předáním souřadnic https://tisnik.github.io/tem­p/stepC.html
24 stepC.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/stepC.js
     
25 stepD.html vykreslení bodů, každý bod může mít libovolnou barvu https://tisnik.github.io/tem­p/stepD.html
26 stepD.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/stepD.js
     
27 stepE.html vykreslení úseček, barva je vypočtena interpolací barev https://tisnik.github.io/tem­p/stepE.html
28 stepE.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/stepE.js
     
29 triangle1.html vykreslení trojúhelníku konstantní barvou https://tisnik.github.io/tem­p/triangle1.html
30 triangle1.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/triangle1.js
     
31 triangle2.html interpolace barev při vyplňování trojúhelníku https://tisnik.github.io/tem­p/triangle2.html
32 triangle2.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/triangle2.js
     
33 triangle3.html vykreslení pásu trojúhelníků https://tisnik.github.io/tem­p/triangle3.html
34 triangle3.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/triangle3.js
     
35 triangle4.html vykreslení trsu trojúhelníků – neúplná varianta https://tisnik.github.io/tem­p/triangle4.html
36 triangle4.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/triangle4.js
     
37 triangle5.html vykreslení trsu trojúhelníků – úplná varianta https://tisnik.github.io/tem­p/triangle5.html
38 triangle5.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/triangle5.js
     
39 triangle6.html překrývající se trojúhelníky (Z-buffer není použit) https://tisnik.github.io/tem­p/triangle6.html
40 triangle6.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/triangle6.js
     
41 triangle7.html překrývající se trojúhelníky (Z-buffer je použit) https://tisnik.github.io/tem­p/triangle7.html
42 triangle7.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/triangle7.js
     
43 cube1.html 3D krychle vykreslená do 2D plochy bez aplikace transformací https://tisnik.github.io/tem­p/cube1.html
44 cube1.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/cube1.js
     
45 cube2.html transformace reprezentovaná jednotkovou maticí https://tisnik.github.io/tem­p/cube2.html
46 cube2.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/cube2.js
     
47 cube3.html zobrazení 3D kostky z různých úhlů pohledu https://tisnik.github.io/tem­p/cube3.html
48 cube3.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/cube3.js
     
49 cube4.html zobrazení 3D kostky s využitím základní perspektivní projekce https://tisnik.github.io/tem­p/cube4.html
50 cube4.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/cube4.js
     
51 cube5.html krychle v 3D prostoru https://tisnik.github.io/tem­p/cube5.html
52 cube5.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/cube5.js
     
53 cube6.html rotace krychle v 3D prostoru s perspektivní projekcí https://tisnik.github.io/tem­p/cube6.html
54 cube6.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/cube6.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í tři články o WebGL a OpenGL ES:

  1. Seriál Grafická knihovna OpenGL
    https://www.root.cz/serialy/graficka-knihovna-opengl/
  2. Seriál OpenGL a nadstavbová knihovna GLU
    https://www.root.cz/serialy/opengl-a-nadstavbova-knihovna-glu/
  3. Seriál OpenGL evaluátory
    https://www.root.cz/serialy/opengl-evaluatory/
  4. Seriál OpenGL Imaging Subset
    https://www.root.cz/serialy/opengl-imaging-subset/
  5. Seriál Tvorba přenositelných grafických aplikací využívajících knihovnu GLUT
    https://www.root.cz/serialy/tvorba-prenositelnych-grafickych-aplikaci-vyuzivajicich-knihovnu-glut/
  6. Stručný přehled grafických knihoven pro Raspberry Pi
    https://www.root.cz/clanky/strucny-prehled-grafickych-knihoven-pro-raspberry-pi/
  7. Práce s rastrovými obrázky v knihovně OpenVG
    https://www.root.cz/clanky/prace-s-rastrovymi-obrazky-v-knihovne-openvg/
  8. Práce s rastrovými obrázky v knihovně OpenVG (pokračování)
    https://www.root.cz/clanky/prace-s-nbsp-rastrovymi-obrazky-v-nbsp-knihovne-openvg-pokracovani/
  9. Práce s rastrovými obrázky v knihovně OpenVG (okomentované demonstrační příklady)
    https://www.root.cz/clanky/prace-s-rastrovymi-obrazky-v-knihovne-openvg-okomentovane-demonstracni-priklady/
  10. Použití knihoven OpenVG a EGL (nejenom) na Raspberry Pi
    https://www.root.cz/clanky/pouziti-knihoven-openvg-a-egl-nejenom-na-raspberry-pi/
  11. 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/
  12. 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
  1. WebGL: 3D Graphics for the Web
    https://www.khronos.org/webgl/
  2. OpenGL Shading Language
    https://en.wikipedia.org/wi­ki/OpenGL_Shading_Language
  3. WebGL (Wikipedia)
    https://en.wikipedia.org/wiki/WebGL
  4. WebGL
    https://wikis.khronos.org/web­gl/Main_Page
  5. OpenGL ES (Wikipedia)
    https://en.wikipedia.org/wi­ki/OpenGL_ES
  6. Float32Array
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Re­ference/Global_Objects/Flo­at32Array
  7. ECMAScript compatibility table
    https://compat-table.github.io/compat-table/es6/
  8. Learn-OpenGLES-Tutorials
    https://github.com/learnopengles/Learn-OpenGLES-Tutorials
  9. Implement OpenGL ES and EGL
    https://source.android.com/doc­s/core/graphics/implement-opengl-es
  10. Khronos Group
    https://www.khronos.org/
  11. Khronos Group (Wikipedia)
    https://en.wikipedia.org/wi­ki/Khronos_Group
  12. Raspberry Pi VideoCore APIs
    http://elinux.org/Raspberry_Pi_Vi­deoCore_APIs
  13. EGL quick reference card
    https://www.khronos.org/files/egl-1–4-quick-reference-card.pdf
  14. EGL Reference Pages Index
    https://www.khronos.org/re­gistry/egl/sdk/docs/man/html/in­dexflat.php
  15. GLSL
    https://glsl.app/
  16. Shader Tutorials by Ronja
    https://www.ronja-tutorials.com/
  17. CanvasRenderingContext2D
    https://developer.mozilla.org/en-US/docs/Web/API/CanvasRen­deringContext2D
  18. WebGL2RenderingContext
    https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext
  19. WebGLRenderingContext
    https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext
  20. Matrix Transformations
    https://vitaminac.github.io/Matrices-in-Computer-Graphics/
  21. Homogeneous coordinates
    https://en.wikipedia.org/wi­ki/Homogeneous_coordinates
  22. Transformation matrix
    https://en.wikipedia.org/wi­ki/Transformation_matrix
  23. Affine transformation
    https://en.wikipedia.org/wi­ki/Affine_transformation
  24. OpenGL Projection Matrix
    https://songho.ca/opengl/gl_pro­jectionmatrix.html

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1WebGL: tvorba 3D grafiky s využitím programovatelné vykreslovací pipeline06.4821-09-2026
2Видеочат в 3D своими руками? Легко!5329-06-2011
3Облачная фотограмметрия, или Делаем 3D-модель по фотографии0508-02-2012
4Мій досвід використання 3D-принтера: від першої моделі до творчих проєктів01013-07-2025
5Českobudějovická nemocnice jako první v republice operuje mozek novou technologií06.8926-07-2026
6Когда читаешь про углеродное волокно в 3D-печати — сначала кажется, ...05.506-08-2026
7Следим за разработками в Бауманке — и вот это уже ...04.9206-08-2026
8Can Autodesk Tools Be Used to Create 3D Models for a Website?04.4903-08-2026
9The Last of Us po česku. Podhoubí prorůstá slámou, startup z něj chce vyrábět obaly ve velkém07.5420-09-2026

Классификация: . Схожих патентов: 0. Схожих новостей: 9. Тональность: 0. Информативность: 8.95. Источник: www.root.cz.