Tvorba 3D grafiky s WebGL: skutečné 3D scény

3D konvička wireframe
Autor: Hay Kranen, Wikimedia, podle licence: CC-BY
Vykreslování 3D scén s takzvanými drátovými modely (wireframe) není pro skutečné 3D scény dostačující. Potřebujeme totiž vykreslovat vyplněné stěny těles, nanést na tyto stěny textury a spočítat jejich osvětlení.

V předchozí čtveřici článků o technologiích WebGL a OpenGL ES jsme si ukázali způsob vykreslení 3D scén, ve kterých jsou použity takzvané drátové modely (wireframe). Ovšem jak již bylo naznačeno v perexu, je nutné pro skutečné 3D scény využít i další techniky. V první řadě je nutné umět vykreslit stěny těles, které jsou složeny z trojúhelníků. Dále je (typicky) nutné na tyto trojúhelníky nanést vhodnou texturu a v mnoha případech ještě musíme nějakým způsobem spočítat osvětlení scény.

Co se dozvíte v článku
  1. Animace rotace krychle v 3D prostoru
  2. Úplný zdrojový kód prvního demonstračního příkladu
  3. Stěny krychle složené z trojúhelníků
  4. Nastavení barev trojúhelníků tvořících stěny krychle
  5. Úplný zdrojový kód druhého demonstračního příkladu
  6. Vykreslení 3D scény s povolenou pamětí hloubky
  7. Nastavení testu fragmentů oproti paměti hloubky a upravená vykreslovací smyčka
  8. Úplný zdrojový kód třetího demonstračního příkladu
  9. Změna vzdálenosti bližší pohledové roviny
  10. Úplný zdrojový kód čtvrtého demonstračního příkladu
  11. Doplnění krychle o dvě chybějící stěny
  12. Úplný zdrojový kód pátého demonstračního příkladu
  13. Podpora pro zobrazení textur
  14. Nanášení textur na povrchy těles
  15. Textury reprezentované rastrovými obrázky
  16. Výhody použití rastrových textur
  17. Nevýhody rastrových textur
  18. Repositář s demonstračními příklady
  19. Odkazy na Internetu

Vzhledem k tomu, že samotné OpenGL ES je relativně nízkoúrovňová knihovna, je nutné mnoho z těchto operací naprogramovat v shaderech. Dnes si na praktických příkladech ukážeme způsob vykreslení těles složených z trojúhelníků a začneme se zabývat i problematikou texturování, tj. nanesení (typicky) rastrové textury na stěny vykreslovaných těles ve scéně.

Součástí vizualizace 3D scény je i osvětlení.

Obrázek 1: Součástí vizualizace 3D scény je i osvětlení.

Autor: tisnik, podle licence: Rights Managed

Animace rotace krychle v 3D prostoru

Nejprve ovšem změníme naši statickou scénu s 3D krychlí na scénu dynamickou. Krychle bude rotovat okolo dvou os, což znamená, že musíme zajistit periodické překreslení scény.

Oproti příkladu se statickými scénami budeme muset do programu přidat několik proměnných, které reprezentují stav programu (resp. vlastně stav 3D scény). Zejména se jedná o dvě proměnné s uložením úhlů rotace krychle okolo dvou os:

// úhly pro rotaci krychle
let rotX = 0, rotY = 0;

Dále ještě přidáme proměnnou, ve které si zapamatujeme čas vykreslení posledního snímku (překreslování totiž nebude zcela periodické):

// čas vykreslení posledního snímku
let lastTime = 0;

Animace bude spočívat v tom, že pro každý snímek přepočítáme transformační matici, která bude (po konverzi na buffer) předána shaderům v atributu nazvaném umatrix:

// navázání na proměnnou dostupnou ve vertex shaderu
const umatrix = gl.getUniformLocation(program, "umatrix");

Celé vykreslení je realizováno ve funkci draw. Ta je volána automaticky (viz text za zdrojovým kódem) a je jí předán aktuální čas. Vypočteme rozdíl mezi aktuálním časem a časem vykreslení předchozího snímku a na základě rozdílu vypočteme úhly rotace krychle. Dále se smaže barvový buffer, přepočítá se transformační matice a do vykreslovacího řetězce se pošlou informace o vrcholech úseček i o jejich barvách. Následně si zavoláním requestAnimationFrame vyžádáme vykreslení dalšího snímku:

function draw(time) {
    // vykreslení jednoho snímku
 
    // přizpůsobíme se rychlosti vykreslování snímků
    const dt = (time - lastTime) * 0.001;
    lastTime = time;
 
    // výpočet 
    rotX += dt * 0.8;
    rotY += dt * 1.0;
 
    // transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
    gl.viewport(0, 0, canvas.width, canvas.height);
 
    // nastavení barvy pro vymazání barvového bufferu
    gl.clearColor(0, 0, 0, 1);
 
    // vymazání barvového bufferu
    gl.clear(gl.COLOR_BUFFER_BIT);
 
    // 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(rotX);
    const ry = matrix_rotate_around_y(rotY);
    const matrix = matrix_multiply(perspective_fov, matrix_multiply(translate, matrix_multiply(rx, ry)));
 
    // předat matici do vykreslovací pipeline
    gl.uniformMatrix4fv(umatrix, false, new Float32Array(matrix));
 
    // vykreslení dvanácti úseček s předáním souřadnic a barev
    gl.drawArrays(gl.LINES, 0, 24);
 
    // vyžádání vykreslení dalšího snímku
    requestAnimationFrame(draw);

Celá animace se spustí tímto voláním:

// vyžádání vykreslení dalšího snímku
requestAnimationFrame(draw);
Poznámka: v praxi to bude nepatrně komplikovanější. K tomuto tématu se ovšem ještě vrátíme.

Úplný zdrojový kód prvního demonstračního příkladu

Následuje výpis dnešního prvního demonstračního příkladu, který po svém spuštění ve webovém prohlížeči (například z adresy https://tisnik.github.io/tem­p/cube7.html) zobrazí rotující krychli:

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
);
 
 
 
// spuštění programu
gl.useProgram(program);
 
// úhly pro rotaci krychle
let rotX = 0, rotY = 0;
 
// čas vykreslení posledního snímku
let lastTime = 0;
 
// navázání na proměnnou dostupnou ve vertex shaderu
const umatrix = gl.getUniformLocation(program, "umatrix");
 
 
function draw(time) {
    // vykreslení jednoho snímku
 
    // přizpůsobíme se rychlosti vykreslování snímků
    const dt = (time - lastTime) * 0.001;
    lastTime = time;
 
    // výpočet 
    rotX += dt * 0.8;
    rotY += dt * 1.0;
 
    // transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
    gl.viewport(0, 0, canvas.width, canvas.height);
 
    // nastavení barvy pro vymazání barvového bufferu
    gl.clearColor(0, 0, 0, 1);
 
    // vymazání barvového bufferu
    gl.clear(gl.COLOR_BUFFER_BIT);
 
    // 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(rotX);
    const ry = matrix_rotate_around_y(rotY);
    const matrix = matrix_multiply(perspective_fov, matrix_multiply(translate, matrix_multiply(rx, ry)));
 
    // předat matici do vykreslovací pipeline
    gl.uniformMatrix4fv(umatrix, false, new Float32Array(matrix));
 
    // vykreslení dvanácti úseček s předáním souřadnic a barev
    gl.drawArrays(gl.LINES, 0, 24);
 
    // vyžádání vykreslení dalšího snímku
    requestAnimationFrame(draw);
 
// vyžádání vykreslení dalšího snímku
requestAnimationFrame(draw);

Stěny krychle složené z trojúhelníků

Zobrazení drátového modelu (wireframe) sice může být užitečné, například v CADech atd., ovšem už se příliš nehodí jeho použití ve hrách nebo při fotorealistickém zobrazení. Z tohoto důvodu si ukážeme způsob zobrazení krychle, jejíž stěny jsou složeny z trojúhelníků (jinou možnost v OpenGL ES nemáme – nejsou totiž podporovány čtyřúhelníky). Vrcholy krychle a tím pádem i vrcholy trojúhelníků, ze kterých se skládají stěny krychle, budou založeny na stejných souřadnicích, jako tomu bylo u drátového modelu:

// 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

V poli vertexes jsou uloženy souřadnice vrcholů trojúhelníků tvořících čtyři (ano, prozatím jen čtyři) stěny. Celkem se tedy jedná o osm trojúhelníků, tj. 24 vrcholů (72 souřadnic):

// souřadnice trojúhelníků
const vertexes = [
    // přední stěna (2 trojúhelníky)
    min_x, min_y, max_z,
    max_x, min_y, max_z,
    max_x, max_y, max_z,
 
    min_x, min_y, max_z,
    max_x, max_y, max_z,
    min_x, max_y, max_z,
 
    // pravá stěna (2 trojúhelníky)
    max_x, min_y, max_z,
    max_x, min_y, min_z,
    max_x, max_y, min_z,
 
    max_x, min_y, max_z,
    max_x, max_y, min_z,
    max_x, max_y, max_z,
 
    // zadní stěna (2 trojúhelníky)
    max_x, min_y, min_z,
    min_x, min_y, min_z,
    min_x, max_y, min_z,
 
    max_x, min_y, min_z,
    min_x, max_y, min_z,
    max_x, max_y, min_z,
 
    // levá stěna (2 trojúhelníky)
    min_x, min_y, min_z,
    min_x, min_y, max_z,
    min_x, max_y, max_z,
 
    min_x, min_y, min_z,
    min_x, max_y, max_z,
    min_x, max_y, min_z,
];

Nastavení barev trojúhelníků tvořících stěny krychle

Specifikovat musíme i barvy všech trojúhelníků, ze kterých se stěny krychle skládají. Prozatím umíme nastavit (a předat) barvy jednotlivých vrcholů. Pokud budou mít všechny tři vrcholy trojúhelníku totožnou barvu, bude trojúhelník vykreslen (resp. přesněji řečeno vyplněn) konstantní barvou. Proto následující pole obsahuje na každém řádku vždy tři stejné čtveřice hodnot (RGBA). A navíc jsou oba trojúhelníky tvořící stěnu vykresleny stejnou barvou, takže vždy dva po sobě jdoucí řádky jsou totožné:

// barvy vrcholů trojúhelníků
const colors = [
    // přední stěna (2 trojúhelníky)
    0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,
    0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,
 
    // pravá stěna (2 trojúhelníky)
    1, 0, 0, 1,   1, 0, 0, 1,   1, 0, 0, 1,
    1, 0, 0, 1,   1, 0, 0, 1,   1, 0, 0, 1,
 
    // zadní stěna (2 trojúhelníky)
    0, 0, 1, 1,   0, 0, 1, 1,   0, 0, 1, 1,
    0, 0, 1, 1,   0, 0, 1, 1,   0, 0, 1, 1,
 
    // levá stěna (2 trojúhelníky)
    1, 1, 0, 1,   1, 1, 0, 1,   1, 1, 0, 1,
    1, 1, 0, 1,   1, 1, 0, 1,   1, 1, 0, 1,
];

A pochopitelně je nutné vykreslovat trojúhelníky, nikoli úsečky:

// vykreslení trojúhelníků s předáním souřadnic a barev
gl.drawArrays(gl.TRIANGLES, 0, 24);

Úplný zdrojový kód druhého demonstračního příkladu

Dnešní druhý demonstrační příklad si můžete otestovat na adrese https://tisnik.github.io/tem­p/cube8.html. Po otevření této stránky by se měla zobrazit rotující 3D krychle, ovšem zobrazí se jen čtyři její stěny a současně není použita paměť hloubky, takže viditelnost není korektní:

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 trojúhelníků
const vertexes = [
    // přední stěna (2 trojúhelníky)
    min_x, min_y, max_z,
    max_x, min_y, max_z,
    max_x, max_y, max_z,
 
    min_x, min_y, max_z,
    max_x, max_y, max_z,
    min_x, max_y, max_z,
 
    // pravá stěna (2 trojúhelníky)
    max_x, min_y, max_z,
    max_x, min_y, min_z,
    max_x, max_y, min_z,
 
    max_x, min_y, max_z,
    max_x, max_y, min_z,
    max_x, max_y, max_z,
 
    // zadní stěna (2 trojúhelníky)
    max_x, min_y, min_z,
    min_x, min_y, min_z,
    min_x, max_y, min_z,
 
    max_x, min_y, min_z,
    min_x, max_y, min_z,
    max_x, max_y, min_z,
 
    // levá stěna (2 trojúhelníky)
    min_x, min_y, min_z,
    min_x, min_y, max_z,
    min_x, max_y, max_z,
 
    min_x, min_y, min_z,
    min_x, max_y, max_z,
    min_x, max_y, min_z,
];
 
// barvy vrcholů trojúhelníků
const colors = [
    // přední stěna (2 trojúhelníky)
    0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,
    0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,
 
    // pravá stěna (2 trojúhelníky)
    1, 0, 0, 1,   1, 0, 0, 1,   1, 0, 0, 1,
    1, 0, 0, 1,   1, 0, 0, 1,   1, 0, 0, 1,
 
    // zadní stěna (2 trojúhelníky)
    0, 0, 1, 1,   0, 0, 1, 1,   0, 0, 1, 1,
    0, 0, 1, 1,   0, 0, 1, 1,   0, 0, 1, 1,
 
    // levá stěna (2 trojúhelníky)
    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
);
 
 
 
// spuštění programu
gl.useProgram(program);
 
// úhly pro rotaci krychle
let rotX = 0, rotY = 0;
 
// čas vykreslení posledního snímku
let lastTime = 0;
 
// navázání na proměnnou dostupnou ve vertex shaderu
const umatrix = gl.getUniformLocation(program, "umatrix");
 
 
function draw(time) {
    // vykreslení jednoho snímku
 
    // přizpůsobíme se rychlosti vykreslování snímků
    const dt = (time - lastTime) * 0.001;
    lastTime = time;
 
    // výpočet 
    rotX += dt * 0.8;
    rotY += dt * 1.0;
 
    // transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
    gl.viewport(0, 0, canvas.width, canvas.height);
 
    // nastavení barvy pro vymazání barvového bufferu
    gl.clearColor(0, 0, 0, 1);
 
    // vymazání barvového bufferu
    gl.clear(gl.COLOR_BUFFER_BIT);
 
    // 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(rotX);
    const ry = matrix_rotate_around_y(rotY);
    const matrix = matrix_multiply(perspective_fov, matrix_multiply(translate, matrix_multiply(rx, ry)));
 
    // předat matici do vykreslovací pipeline
    gl.uniformMatrix4fv(umatrix, false, new Float32Array(matrix));
 
    // vykreslení trojúhelníků s předáním souřadnic a barev
    gl.drawArrays(gl.TRIANGLES, 0, 24);
 
    // vyžádání vykreslení dalšího snímku
    requestAnimationFrame(draw);
}
 
// vyžádání vykreslení dalšího snímku
requestAnimationFrame(draw);

Vykreslení 3D scény s povolenou pamětí hloubky

Aby se krychle zobrazovala korektně, tj. aby bližší stěny překrývaly stěny vzdálenější (a ne naopak), musíme povolit paměť hloubky (Z-buffer). To již umíme, takže jen v krátkosti. Paměť hloubky si musíme explicitně vyžádat:

// reference na objekt představující kreslicí plátno umístěné na webové stránce
const canvas = document.getElementById("web_gl_canvas");
if (!canvas) {
    throw new Error("Canvas with id 'web_gl_canvas' not found");
}
print("Canvas object retrieved");

// vytvoření a získání kontextu WebGL (3D grafika)
const gl = canvas.getContext("webgl", {
  depth: true
});
if (!gl) {
    throw new Error("WebGL is not supported or context creation failed");
}
print("WebGL context created");

Pokud vše proběhne v pořádku, můžeme paměť hloubky skutečně použít.

Nastavení testu fragmentů oproti paměti hloubky a upravená vykreslovací smyčka

Opět se nejedná o nové téma, protože jsme se funkcí pamětí hloubky již zabývali. Nyní se ovšem poprvé paměť hloubky využije prakticky.

Nejprve paměť hloubky povolíme, nastavíme způsob porovnání hloubky fragmentů a nastavíme implicitní hloubku „pozadí“ (nejvzdálenější zobrazitelné fragmenty):

// povolení Z-bufferu
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LESS);
 
gl.clearDepth(1.0);

Ve funkci draw nesmíme zapomenout na smazání jak barvového bufferu, tak i paměti hloubky (ostatně si sami otestujte, jak budou další snímky scény vypadat, pokud na to zapomenete):

function draw(time) {
    // vykreslení jednoho snímku
 
    // přizpůsobíme se rychlosti vykreslování snímků
    const dt = (time - lastTime) * 0.001;
    lastTime = time;
 
    // výpočet 
    rotX += dt * 0.8;
    rotY += dt * 1.0;
 
    // transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
    gl.viewport(0, 0, canvas.width, canvas.height);
 
    // vymazání barvového bufferu i paměti hloubky
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
 
    // 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(rotX);
    const ry = matrix_rotate_around_y(rotY);
    const matrix = matrix_multiply(perspective_fov, matrix_multiply(translate, matrix_multiply(rx, ry)));
 
    // předat matici do vykreslovací pipeline
    gl.uniformMatrix4fv(umatrix, false, new Float32Array(matrix));
 
    // vykreslení trojúhelníků s předáním souřadnic a barev
    gl.drawArrays(gl.TRIANGLES, 0, 24);
 
    // vyžádání vykreslení dalšího snímku
    requestAnimationFrame(draw);
}

Úplný zdrojový kód třetího demonstračního příkladu

Ve třetím demonstračním příkladu, který lze spustit z adresy https://tisnik.github.io/tem­p/cube9.html, se zobrazí rotující 3D krychle se čtyřmi stěnami. Díky použití paměti hloubky je zobrazení v tomto případě korektní, pochopitelně včetně viditelnosti:

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", {
  depth: true
});
if (!gl) {
    throw new Error("WebGL is not supported or context creation failed");
}
print("WebGL context created");
 
// vytvoření vertex shaderu
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
if (!vertexShader) {
    throw new Error("Failed to create vertex shader.");
}
print("Vertex shader created");
 
// překlad vertex shaderu
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
    const log = gl.getShaderInfoLog(vertexShader);
    gl.deleteShader(vertexShader);
    throw new Error(`Vertex shader compile failed: ${log}`);
}
print("Vertex shader compiled");
 
// vytvoření fragment shaderu
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
if (!fragmentShader) {
    throw new Error("Failed to create fragment shader.");
}
print("Fragment shader created");
 
// překlad fragment shaderu
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
    const log = gl.getShaderInfoLog(fragmentShader);
    gl.deleteShader(fragmentShader);
    throw new Error(`fragment shader compile failed: ${log}`);
}
print("Fragment shader compiled");
 
// registrace obou shaderů
const program = gl.createProgram();
if (!program) {
    throw new Error("Failed to create program.");
}
print("Program created");
 
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
print("Shaders attached to the program");
 
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    const log = gl.getProgramInfoLog(program);
    gl.deleteProgram(program);
    throw new Error(`Program link failed: ${log}`);
}
print("Program linked");
 
// 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 trojúhelníků
const vertexes = [
    // přední stěna (2 trojúhelníky)
    min_x, min_y, max_z,
    max_x, min_y, max_z,
    max_x, max_y, max_z,
 
    min_x, min_y, max_z,
    max_x, max_y, max_z,
    min_x, max_y, max_z,
 
    // pravá stěna (2 trojúhelníky)
    max_x, min_y, max_z,
    max_x, min_y, min_z,
    max_x, max_y, min_z,
 
    max_x, min_y, max_z,
    max_x, max_y, min_z,
    max_x, max_y, max_z,
 
    // zadní stěna (2 trojúhelníky)
    max_x, min_y, min_z,
    min_x, min_y, min_z,
    min_x, max_y, min_z,
 
    max_x, min_y, min_z,
    min_x, max_y, min_z,
    max_x, max_y, min_z,
 
    // levá stěna (2 trojúhelníky)
    min_x, min_y, min_z,
    min_x, min_y, max_z,
    min_x, max_y, max_z,
 
    min_x, min_y, min_z,
    min_x, max_y, max_z,
    min_x, max_y, min_z,
];
 
// barvy vrcholů trojúhelníků
const colors = [
    // přední stěna (2 trojúhelníky)
    0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,
    0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,

    // pravá stěna (2 trojúhelníky)
    1, 0, 0, 1,   1, 0, 0, 1,   1, 0, 0, 1,
    1, 0, 0, 1,   1, 0, 0, 1,   1, 0, 0, 1,

    // zadní stěna (2 trojúhelníky)
    0, 0, 1, 1,   0, 0, 1, 1,   0, 0, 1, 1,
    0, 0, 1, 1,   0, 0, 1, 1,   0, 0, 1, 1,
 
    // levá stěna (2 trojúhelníky)
    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
);
 
 
 
// spuštění programu
gl.useProgram(program);
 
// úhly pro rotaci krychle
let rotX = 0, rotY = 0;
 
// čas vykreslení posledního snímku
let lastTime = 0;
 
// navázání na proměnnou dostupnou ve vertex shaderu
const umatrix = gl.getUniformLocation(program, "umatrix");
 
// povolení Z-bufferu
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LESS);
 
gl.clearDepth(1.0);
 
// nastavení barvy pro vymazání barvového bufferu
gl.clearColor(0, 0, 0, 1);
 
 
 
function draw(time) {
    // vykreslení jednoho snímku
 
    // přizpůsobíme se rychlosti vykreslování snímků
    const dt = (time - lastTime) * 0.001;
    lastTime = time;
 
    // výpočet 
    rotX += dt * 0.8;
    rotY += dt * 1.0;
 
    // transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
    gl.viewport(0, 0, canvas.width, canvas.height);
 
    // vymazání barvového bufferu i paměti hloubky
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
 
    // 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(rotX);
    const ry = matrix_rotate_around_y(rotY);
    const matrix = matrix_multiply(perspective_fov, matrix_multiply(translate, matrix_multiply(rx, ry)));
 
    // předat matici do vykreslovací pipeline
    gl.uniformMatrix4fv(umatrix, false, new Float32Array(matrix));
 
    // vykreslení trojúhelníků s předáním souřadnic a barev
    gl.drawArrays(gl.TRIANGLES, 0, 24);
 
    // vyžádání vykreslení dalšího snímku
    requestAnimationFrame(draw);
}
 
// vyžádání vykreslení dalšího snímku
requestAnimationFrame(draw);

Změna vzdálenosti bližší pohledové roviny

Zapomenout nesmíme ještě na další zajímavou vlastnost OpenGL (ES). Týká se faktu, že ty fragmenty, které jsou umístěny příliš blízko k pozorovateli (nebo naopak příliš daleko) nebudou vykresleny – vykreslovací pipeline bude tyto fragmenty zcela ignorovat. Toho můžeme využít v situaci, kdy je nutné „nahlédnout“ dovnitř zobrazeného tělesa. Aby se tohoto efektu dosáhlo, postačuje posunout bližší podstavu komolého jehlanu, který ohraničuje prostor, který bude vykreslen:

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

Obrázek 2: 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

Ve všech předchozích scénách byla vzdálenost první podstavy nastavena na hodnotu 0,1, což je velmi blízko kamery:

const perspective_fov = matrix_perspective_fov(Math.PI / 3, aspect, 0.1, 100);

Odsunutím této roviny dál od kamery dosáhneme toho, že bližší fragmenty nebudou zobrazeny a část krychle „zmizí“. Bude tedy vidět průhled dovnitř krychle:

const perspective_fov = matrix_perspective_fov(Math.PI / 3, aspect, 1.5, 100);

Úplný zdrojový kód čtvrtého demonstračního příkladu

Dnešní čtvrtý demonstrační příklad si pochopitelně opět můžete spustit přímo ve webovém prohlížeči. Je dostupný na adrese https://tisnik.github.io/tem­p/cubeA.html a po jeho otevření by se měla zobrazit rotující 3D krychle, jejíž část nejbližší kameře bude „uříznuta“:

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", {
  depth: true
});
if (!gl) {
    throw new Error("WebGL is not supported or context creation failed");
}
print("WebGL context created");
 
// vytvoření vertex shaderu
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
if (!vertexShader) {
    throw new Error("Failed to create vertex shader.");
}
print("Vertex shader created");
 
// překlad vertex shaderu
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
    const log = gl.getShaderInfoLog(vertexShader);
    gl.deleteShader(vertexShader);
    throw new Error(`Vertex shader compile failed: ${log}`);
}
print("Vertex shader compiled");
 
// vytvoření fragment shaderu
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
if (!fragmentShader) {
    throw new Error("Failed to create fragment shader.");
}
print("Fragment shader created");
 
// překlad fragment shaderu
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
    const log = gl.getShaderInfoLog(fragmentShader);
    gl.deleteShader(fragmentShader);
    throw new Error(`fragment shader compile failed: ${log}`);
}
print("Fragment shader compiled");
 
// registrace obou shaderů
const program = gl.createProgram();
if (!program) {
    throw new Error("Failed to create program.");
}
print("Program created");
 
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
print("Shaders attached to the program");
 
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    const log = gl.getProgramInfoLog(program);
    gl.deleteProgram(program);
    throw new Error(`Program link failed: ${log}`);
}
print("Program linked");
 
// 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 trojúhelníků
const vertexes = [
    // přední stěna (2 trojúhelníky)
    min_x, min_y, max_z,
    max_x, min_y, max_z,
    max_x, max_y, max_z,
 
    min_x, min_y, max_z,
    max_x, max_y, max_z,
    min_x, max_y, max_z,
 
    // pravá stěna (2 trojúhelníky)
    max_x, min_y, max_z,
    max_x, min_y, min_z,
    max_x, max_y, min_z,
 
    max_x, min_y, max_z,
    max_x, max_y, min_z,
    max_x, max_y, max_z,
 
    // zadní stěna (2 trojúhelníky)
    max_x, min_y, min_z,
    min_x, min_y, min_z,
    min_x, max_y, min_z,
 
    max_x, min_y, min_z,
    min_x, max_y, min_z,
    max_x, max_y, min_z,
 
    // levá stěna (2 trojúhelníky)
    min_x, min_y, min_z,
    min_x, min_y, max_z,
    min_x, max_y, max_z,
 
    min_x, min_y, min_z,
    min_x, max_y, max_z,
    min_x, max_y, min_z,
];
 
// barvy vrcholů trojúhelníků
const colors = [
    // přední stěna (2 trojúhelníky)
    0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,
    0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,

    // pravá stěna (2 trojúhelníky)
    1, 0, 0, 1,   1, 0, 0, 1,   1, 0, 0, 1,
    1, 0, 0, 1,   1, 0, 0, 1,   1, 0, 0, 1,

    // zadní stěna (2 trojúhelníky)
    0, 0, 1, 1,   0, 0, 1, 1,   0, 0, 1, 1,
    0, 0, 1, 1,   0, 0, 1, 1,   0, 0, 1, 1,
 
    // levá stěna (2 trojúhelníky)
    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
);
 
 
 
// spuštění programu
gl.useProgram(program);
 
// úhly pro rotaci krychle
let rotX = 0, rotY = 0;
 
// čas vykreslení posledního snímku
let lastTime = 0;
 
// navázání na proměnnou dostupnou ve vertex shaderu
const umatrix = gl.getUniformLocation(program, "umatrix");
 
// povolení Z-bufferu
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LESS);
 
gl.clearDepth(1.0);
 
// nastavení barvy pro vymazání barvového bufferu
gl.clearColor(0, 0, 0, 1);
 
 
 
function draw(time) {
    // vykreslení jednoho snímku
 
    // přizpůsobíme se rychlosti vykreslování snímků
    const dt = (time - lastTime) * 0.001;
    lastTime = time;
 
    // výpočet 
    rotX += dt * 0.8;
    rotY += dt * 1.0;
 
    // transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
    gl.viewport(0, 0, canvas.width, canvas.height);
 
    // vymazání barvového bufferu i paměti hloubky
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
 
    // transformační matice
    const aspect = canvas.width / canvas.height;
    const perspective_fov = matrix_perspective_fov(Math.PI / 3, aspect, 1.5, 100);
    const translate = matrix_translate(-2.0);
    const rx = matrix_rotate_around_x(rotX);
    const ry = matrix_rotate_around_y(rotY);
    const matrix = matrix_multiply(perspective_fov, matrix_multiply(translate, matrix_multiply(rx, ry)));
 
    // předat matici do vykreslovací pipeline
    gl.uniformMatrix4fv(umatrix, false, new Float32Array(matrix));
 
    // vykreslení trojúhelníků s předáním souřadnic a barev
    gl.drawArrays(gl.TRIANGLES, 0, 24);
 
    // vyžádání vykreslení dalšího snímku
    requestAnimationFrame(draw);
}
 
// vyžádání vykreslení dalšího snímku
requestAnimationFrame(draw);

Doplnění krychle o dvě chybějící stěny

Ve všech předchozích demonstračních příkladech s rotující krychlí složené z trojúhelníků chyběly dvě stěny. Ty je pochopitelně možné velmi snadno doplnit. Každá stěna bude složena ze dvou trojúhelníků a upravit je nutné jak pole vertexes (čtyři nové trojúhelníky), tak i pole colors (barvy vrcholů trojúhelníků).

Do pole vertexes jsou přidány čtyři trojúhelníky a jeho nová podoba vypadá následovně:

// souřadnice trojúhelníků
const vertexes = [
    // přední stěna (2 trojúhelníky)
    min_x, min_y, max_z,
    max_x, min_y, max_z,
    max_x, max_y, max_z,
 
    min_x, min_y, max_z,
    max_x, max_y, max_z,
    min_x, max_y, max_z,
 
    // pravá stěna (2 trojúhelníky)
    max_x, min_y, max_z,
    max_x, min_y, min_z,
    max_x, max_y, min_z,
 
    max_x, min_y, max_z,
    max_x, max_y, min_z,
    max_x, max_y, max_z,
 
    // zadní stěna (2 trojúhelníky)
    max_x, min_y, min_z,
    min_x, min_y, min_z,
    min_x, max_y, min_z,
 
    max_x, min_y, min_z,
    min_x, max_y, min_z,
    max_x, max_y, min_z,
 
    // levá stěna (2 trojúhelníky)
    min_x, min_y, min_z,
    min_x, min_y, max_z,
    min_x, max_y, max_z,
 
    min_x, min_y, min_z,
    min_x, max_y, max_z,
    min_x, max_y, min_z,
 
    // horní stěna (2 trojúhelníky)
    min_x, max_y, max_z,
    max_x, max_y, max_z,
    max_x, max_y, min_z,
 
    min_x, max_y, max_z,
    max_x, max_y, min_z,
    min_x, max_y, min_z,
 
    // spodní stěna (2 trojúhelníky)
    min_x, min_y, min_z,
    max_x, min_y, min_z,
    max_x, min_y, max_z,
 
    min_x, min_y, min_z,
    max_x, min_y, max_z,
    min_x, min_y, max_z,
];

Podobně musíme doplnit i barvy těchto čtyř trojúhelníků:

// barvy vrcholů trojúhelníků
const colors = [
    // přední stěna (2 trojúhelníky)
    0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,
    0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,
 
    // pravá stěna (2 trojúhelníky)
    1, 0, 0, 1,   1, 0, 0, 1,   1, 0, 0, 1,
    1, 0, 0, 1,   1, 0, 0, 1,   1, 0, 0, 1,
 
    // zadní stěna (2 trojúhelníky)
    0, 0, 1, 1,   0, 0, 1, 1,   0, 0, 1, 1,
    0, 0, 1, 1,   0, 0, 1, 1,   0, 0, 1, 1,
 
    // levá stěna (2 trojúhelníky)
    1, 1, 0, 1,   1, 1, 0, 1,   1, 1, 0, 1,
    1, 1, 0, 1,   1, 1, 0, 1,   1, 1, 0, 1,
 
    // horní stěna (2 trojúhelníky)
    0, 1, 1, 1,   0, 1, 1, 1,   0, 1, 1, 1,
    0, 1, 1, 1,   0, 1, 1, 1,   0, 1, 1, 1,
 
    // spodní stěna (2 trojúhelníky)
    1, 0, 1, 1,   1, 0, 1, 1,   1, 0, 1, 1,
    1, 0, 1, 1,   1, 0, 1, 1,   1, 0, 1, 1,
];

Úplný zdrojový kód pátého demonstračního příkladu

Dnešní pátý a současně i poslední demonstrační příklad, který zobrazí rotující 3D krychli se všemi šesti stěnami, je dostupný na adrese https://tisnik.github.io/tem­p/cubeB.html:

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", {
  depth: true
});
if (!gl) {
    throw new Error("WebGL is not supported or context creation failed");
}
print("WebGL context created");
 
// vytvoření vertex shaderu
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
if (!vertexShader) {
    throw new Error("Failed to create vertex shader.");
}
print("Vertex shader created");
 
// překlad vertex shaderu
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
    const log = gl.getShaderInfoLog(vertexShader);
    gl.deleteShader(vertexShader);
    throw new Error(`Vertex shader compile failed: ${log}`);
}
print("Vertex shader compiled");
 
// vytvoření fragment shaderu
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
if (!fragmentShader) {
    throw new Error("Failed to create fragment shader.");
}
print("Fragment shader created");
 
// překlad fragment shaderu
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
    const log = gl.getShaderInfoLog(fragmentShader);
    gl.deleteShader(fragmentShader);
    throw new Error(`fragment shader compile failed: ${log}`);
}
print("Fragment shader compiled");
 
// registrace obou shaderů
const program = gl.createProgram();
if (!program) {
    throw new Error("Failed to create program.");
}
print("Program created");
 
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
print("Shaders attached to the program");
 
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    const log = gl.getProgramInfoLog(program);
    gl.deleteProgram(program);
    throw new Error(`Program link failed: ${log}`);
}
print("Program linked");
 
// 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 trojúhelníků
const vertexes = [
    // přední stěna (2 trojúhelníky)
    min_x, min_y, max_z,
    max_x, min_y, max_z,
    max_x, max_y, max_z,
 
    min_x, min_y, max_z,
    max_x, max_y, max_z,
    min_x, max_y, max_z,
 
    // pravá stěna (2 trojúhelníky)
    max_x, min_y, max_z,
    max_x, min_y, min_z,
    max_x, max_y, min_z,
 
    max_x, min_y, max_z,
    max_x, max_y, min_z,
    max_x, max_y, max_z,
 
    // zadní stěna (2 trojúhelníky)
    max_x, min_y, min_z,
    min_x, min_y, min_z,
    min_x, max_y, min_z,
 
    max_x, min_y, min_z,
    min_x, max_y, min_z,
    max_x, max_y, min_z,
 
    // levá stěna (2 trojúhelníky)
    min_x, min_y, min_z,
    min_x, min_y, max_z,
    min_x, max_y, max_z,
 
    min_x, min_y, min_z,
    min_x, max_y, max_z,
    min_x, max_y, min_z,
 
    // horní stěna (2 trojúhelníky)
    min_x, max_y, max_z,
    max_x, max_y, max_z,
    max_x, max_y, min_z,
 
    min_x, max_y, max_z,
    max_x, max_y, min_z,
    min_x, max_y, min_z,
 
    // spodní stěna (2 trojúhelníky)
    min_x, min_y, min_z,
    max_x, min_y, min_z,
    max_x, min_y, max_z,
 
    min_x, min_y, min_z,
    max_x, min_y, max_z,
    min_x, min_y, max_z,
];
 
// barvy vrcholů trojúhelníků
const colors = [
    // přední stěna (2 trojúhelníky)
    0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,
    0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,
 
    // pravá stěna (2 trojúhelníky)
    1, 0, 0, 1,   1, 0, 0, 1,   1, 0, 0, 1,
    1, 0, 0, 1,   1, 0, 0, 1,   1, 0, 0, 1,
 
    // zadní stěna (2 trojúhelníky)
    0, 0, 1, 1,   0, 0, 1, 1,   0, 0, 1, 1,
    0, 0, 1, 1,   0, 0, 1, 1,   0, 0, 1, 1,
 
    // levá stěna (2 trojúhelníky)
    1, 1, 0, 1,   1, 1, 0, 1,   1, 1, 0, 1,
    1, 1, 0, 1,   1, 1, 0, 1,   1, 1, 0, 1,
 
    // horní stěna (2 trojúhelníky)
    0, 1, 1, 1,   0, 1, 1, 1,   0, 1, 1, 1,
    0, 1, 1, 1,   0, 1, 1, 1,   0, 1, 1, 1,
 
    // spodní stěna (2 trojúhelníky)
    1, 0, 1, 1,   1, 0, 1, 1,   1, 0, 1, 1,
    1, 0, 1, 1,   1, 0, 1, 1,   1, 0, 1, 1,
];
 
// buffer, do kterého se překopírují souřadnice bodu
const vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
    throw new Error("Failed to create buffer.");
}
print("Vertex buffer created");
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
 
const data = new Float32Array(vertexes);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
print("Vertex buffer filled-in by vertexes");
 
// navázání na parametr předávaný do vertex shaderu
const p = gl.getAttribLocation(program, "position");
if (p === -1) {
    throw new Error("Attribute 'position' not found in shader program");
}
print("Attribute 'position' found in shader program")
 
 
// seznam předávaný do vertex shaderu
gl.vertexAttribPointer(p,
                       3,                                // počet prvků v každém atributu
                       gl.FLOAT,                         // typ prvků
                       gl.FALSE,                         // data nejsou normalizována
                       3*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
                       0                                 // offset
);
gl.enableVertexAttribArray(p);
 
// 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
);
 
 
 
// spuštění programu
gl.useProgram(program);
 
// úhly pro rotaci krychle
let rotX = 0, rotY = 0;
 
// čas vykreslení posledního snímku
let lastTime = 0;
 
// navázání na proměnnou dostupnou ve vertex shaderu
const umatrix = gl.getUniformLocation(program, "umatrix");
 
// povolení Z-bufferu
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LESS);
 
gl.clearDepth(1.0);
 
// nastavení barvy pro vymazání barvového bufferu
gl.clearColor(0, 0, 0, 1);
 
 
 
function draw(time) {
    // vykreslení jednoho snímku
 
    // přizpůsobíme se rychlosti vykreslování snímků
    const dt = (time - lastTime) * 0.001;
    lastTime = time;
 
    // výpočet 
    rotX += dt * 0.8;
    rotY += dt * 1.0;
 
    // transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
    gl.viewport(0, 0, canvas.width, canvas.height);
 
    // vymazání barvového bufferu i paměti hloubky
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
 
    // transformační matice
    const aspect = canvas.width / canvas.height;
    const perspective_fov = matrix_perspective_fov(Math.PI / 3, aspect, 0.5, 100);
    const translate = matrix_translate(-2.0);
    const rx = matrix_rotate_around_x(rotX);
    const ry = matrix_rotate_around_y(rotY);
    const matrix = matrix_multiply(perspective_fov, matrix_multiply(translate, matrix_multiply(rx, ry)));
 
    // předat matici do vykreslovací pipeline
    gl.uniformMatrix4fv(umatrix, false, new Float32Array(matrix));
 
    // vykreslení trojúhelníků s předáním souřadnic a barev
    gl.drawArrays(gl.TRIANGLES, 0, 36);
 
    // vyžádání vykreslení dalšího snímku
    requestAnimationFrame(draw);
}
 
// vyžádání vykreslení dalšího snímku
requestAnimationFrame(draw);

Podpora pro zobrazení textur

V závěrečné části dnešního článku o technologii WebGL i o grafické knihovně OpenGL ES se začneme věnovat poměrně rozsáhlé a přitom velmi důležité a zajímavé oblasti polygonální trojrozměrné grafiky. Řeč je o texturování, což je, zjednodušeně řečeno, metoda pro vykreslování trojrozměrných těles (teoreticky ovšem i těles dvourozměrných), které mají na svém povrchu naneseny rastrové obrazce. Stěny těles tedy nejsou vyplněny ani konstantní barvou ani lineárním barvovým přechodem. Při aplikaci texturování je možné vytvářet a zobrazovat vizuálně relativně složité scény bez větších nároků na výpočetní výkon grafického subsystému. Jinými slovy: namísto snahy o modelování každé cihly tvořící stěnu je jednodušší zobrazit jen dva trojúhelníky představující stěnu s vhodnou texturou nanesenou na tyto trojúhelníky. Pochopitelně záleží na konkrétní aplikaci – v některých případech je toto zjednodušení možné, jindy ovšem není žádoucí.

Nanášení textur na povrchy těles

Texturováním (resp. přesněji řečeno nanášením textur) je označen princip obarvení povrchu zobrazovacích těles různými obrazci. Důležité přitom je, že se nijak nemění geometrické vlastnosti těles, pouze se odlišně zobrazuje jejich povrch (to tedy znamená, že i když textura obsahuje „díry“, stěna tělesa žádné tyto díry ve skutečnosti neobsahuje). Obrazce, které se na povrch těles nanášejí, se nazývají textury (textures). Tyto textury jsou většinou představovány plošnými obrázky (dvoudimenzionální textury), některé grafické systémy však podporují i vykreslování jednorozměrných a dokonce trojrozměrných (objemových) textur (k této problematice se ještě vrátíme).

Obrazce pro textury se mohou vytvářet několika způsoby. Buď je možné použít klasické rastrové obrázky vzniklé například namalováním, vyfocením nebo naskenováním, nebo se textura může vytvářet pomocí různých algoritmů založených většinou na fraktálních technikách – tímto způsobem vznikají takzvané procedurální textury (popřípadě se pro tento účel využije generativní umělá inteligence).

Procedurální textury lze použít buď pro výpočet rastrových obrázků před vlastním vykreslováním (po výpočtu obrázku se tato textura chová jako každý jiný rastrový obrázek se všemi výhodami i nevýhodami, které to přináší) nebo se může výpočet textur provádět v reálném čase až při vykreslování, přičemž se parametry výpočtu textury nastaví podle aktuální velikosti a orientace plošky, na kterou se textura nanáší. Tuto druhou možnost však OpenGL ES přímo nepodporuje (i když v případě potřeby lze naprogramovat příslušné shadery), výpočet procedurálních textur je tedy většinou nutné provádět „ručně“ (i k této problematice se ještě vrátíme).

Procedurální textura.

Obrázek 3: Procedurální textura.

Autor: tisnik, podle licence: Rights Managed

Textury reprezentované rastrovými obrázky

V dalším textu se budeme téměř výhradně zabývat texturami reprezentovanými rastrovými obrázky, nezávisle na tom, jakým způsobem původně vznikly. I když řeč bude převážně o dvoudimenzionálních texturách (tedy bitmapách a pixmapách), většina zde popisovaných vlastností se vztahuje i na jednorozměrné a dokonce i na trojrozměrné textury. Případné výjimky a odlišnosti si samozřejmě postupně popíšeme a taktéž ukážeme na demonstračních příkladech. Na obrázku pod tímto odstavcem je ukázána dvoudimenzionální, „ručně“ nakreslená textura. Rastrové textury budeme v demonstračních příkladech vyrábět buď programově (různé šachovnice apod.) nebo je budeme načítat z externích souborů.

Ještě se musíme zmínit o jednom termínu, který se v souvislosti s texturami používá. Podobně jako je bitmapa či pixmapa složená ze základních rastrových elementů (pixelů), tak je textura složena z takzvaných texelů. Pixel a texel mají stejné vlastnosti a podobný či dokonce ekvivalentní způsob uložení v paměti. V dalším textu však budeme oba pojmy navzájem oddělovat, tj. pixel je element vykreslovaný na obrazovce (jeho barva je součástí informace o fragmentu), kdežto texel je rastrový element (většinou) dvourozměrné textury. Texturování při použití této terminologie spočívá v nanášení texelů na vykreslovaný povrch.

Napodobení cihlové zdi s využitím 2D textury.

Obrázek 4: Napodobení cihlové zdi s využitím 2D textury.

Autor: tisnik, podle licence: Rights Managed

Výhody použití rastrových textur

Použití rastrových textur s sebou samozřejmě přináší výhody, ale i některé nevýhody. Jak jsme si již řekli v předchozí kapitole, používá se technika texturování jako určitá náhrada při zobrazování složitých povrchů těles (zeď, omítka, dřevo, kámen), kdy se tento obecně nehomogenní povrch nahradí ploškou s nanesenou texturou. Záleží na vhodné volbě textury, velikosti objektu a nasvícení celé scény, zda tato náhrada bude dostatečná či zda uživatel uvidí vizuální chyby ve scéně (typicky při pohledu z malého úhlu může být patrné, že zdánlivě cihlová stěna je ve skutečnosti zcela plochá).

Velkou výhodou rastrových textur je jejich snadná implementace ve vykreslovacím řetězci. Jednoduché grafické akcelerátory řešily texturování tak, že se do jejich vykreslovacího řetězce přidala vyrovnávací paměť pro textury a několik interpolátorů, pomocí kterých se řešil přístup do texturovací paměti. Moderní grafické akcelerátory jdou mnohem dál: textury je možné komprimovat, jsou podporovány takzvané mipmapy (textury ve více rozlišeních), provádí se antialiasing při vykreslování, jsou podporovány takzvané multitextury apod. Texturovací jednotka však stále patří k těm částem vizuálního systému, která má velmi dobrý poměr složitost/vizuální efekt.

Další výhodou texturování je, že pokud u textur použijeme průhlednost (nazývanou také alfa kanál – opět je zde velká podobnost s běžnými rastrovými obrázky), je možné vizuálně změnit geometrii předmětů, protože se předmět může na některých místech jevit jako děravý. Ukázka této techniky je zobrazena na dalším obrázku. Poznamenejme, že při použití průhlednosti je nutné programově setřídit průhledné nebo poloprůhledné plošky, jelikož by v tomto případě Z-buffer (paměť hloubky) nepracoval korektně – i průhledné texely by se totiž „vykreslily“ a poškodily tak informaci o hloubce fragmentu uloženou v paměti hloubky (Z-bufferu). Vše si opět později ukážeme na demonstračních příkladech.

Chybí v tomto modelu dvě stěny, nebo jsou pokryty průhlednou texturou?

Obrázek 5: Chybí v tomto modelu dvě stěny, nebo jsou pokryty průhlednou texturou?

Autor: tisnik, podle licence: Rights Managed

Nevýhody rastrových textur

První vážnou nevýhodou rastrových textur je jejich předem dané rozlišení, tedy počet texelů, ze kterých se textura skládá. Při volbě rozlišení textury se vždy musí zvolit kompromis mezi dvěma extrémy. Prvním extrémem je volba textury s malým rozlišením, což však při zobrazování vede k viditelným chybám (vzpomeňme například starší hry typu Wolfenstein nebo Doom, které používaly dosti hrubé textury s rozlišením 64×64 texelů; u těchto her se samozřejmě ani OpenGL ani grafická akcelerace nepoužívala). Druhým extrémem je volba textury s neúměrně velkým rozlišením, kdy se naopak zbytečně plýtvá pamětí na grafickém akcelerátoru.

Druhá nevýhoda spočívá ve faktu, že pokud zobrazujeme texturu na obrazovce a použije se zvětšení či zmenšení počtu zobrazovaných texelů (což se stává při texturování téměř vždy, protože se na texturovaný povrch díváme z různých směrů a vzdáleností), dochází k takzvanému aliasu (zjednodušeně řečeno tvorbě moaré), který není možné principiálně odstranit. Lze pouze do určité míry zmírnit jeho vizuální podobu. Z tohoto důvodu se používají různé metody odstranění aliasu (tj. metody antialiasingu), které však komplikují zobrazovací řetězec a obecně vedou ke zpomalení vykreslování.

AI agenti

Třetí nevýhodou je skutečnost, že textury zabírají poměrně velké množství paměti. V případě, že je v paměti grafického akcelerátoru dostatečné místo, je možné textury nahrát do této paměti a vykreslování tak několikanásobně urychlit. Pokud je však v této paměti místa málo (oblast paměti grafického akcelerátoru je mimo jiné obsazena všemi barvovými buffery, pamětí hloubky, stencil bufferem, shadery apod.), musí se textury při vykreslování nahrávat z hlavní paměti počítače, což zatěžuje sběrnici a zpomaluje vykreslování. Tuto nevýhodu částečně řeší různé metody komprimace textur.

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

Všechny demonstrační příklady, které jsme si v předchozí čtveřici článků 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
     
55 cube7.html animace rotace krychle složené z hran (úseček) v 3D prostoru s perspektivní projekcí https://tisnik.github.io/tem­p/cube7.html
56 cube7.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/cube7.js
     
57 cube8.html animace rotace krychle složené z trojúhelníků v 3D prostoru s perspektivní projekcí https://tisnik.github.io/tem­p/cube8.html
58 cube8.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/cube8.js
     
59 cube9.html animace rotace krychle složené z trojúhelníků v 3D prostoru s perspektivní projekcí a povolením paměti hloubky https://tisnik.github.io/tem­p/cube9.html
60 cube9.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/cube9.js
     
60 cubeA.html změna vzdálenosti bližší pohledové roviny https://tisnik.github.io/tem­p/cubeA.html
61 cubeA.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/cubeA.js
     
62 cubeB.html doplnění rotující krychle o obě chybějící stěny https://tisnik.github.io/tem­p/cubeB.html
63 cubeB.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/cubeB.js

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
Neutrální ikona do widgetu na odběr článků ze seriálů

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.


Autor článku

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