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
- Animace rotace krychle v 3D prostoru
- Úplný zdrojový kód prvního demonstračního příkladu
- Stěny krychle složené z trojúhelníků
- Nastavení barev trojúhelníků tvořících stěny krychle
- Úplný zdrojový kód druhého demonstračního příkladu
- Vykreslení 3D scény s povolenou pamětí hloubky
- Nastavení testu fragmentů oproti paměti hloubky a upravená vykreslovací smyčka
- Úplný zdrojový kód třetího demonstračního příkladu
- Změna vzdálenosti bližší pohledové roviny
- Úplný zdrojový kód čtvrtého demonstračního příkladu
- Doplnění krychle o dvě chybějící stěny
- Úplný zdrojový kód pátého demonstračního příkladu
- Podpora pro zobrazení textur
- Nanášení textur na povrchy těles
- Textury reprezentované rastrovými obrázky
- Výhody použití rastrových textur
- Nevýhody rastrových textur
- Repositář s demonstračními příklady
- 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ě.
Obrázek 1: Součástí vizualizace 3D scény je i osvětlení.
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);
Ú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/temp/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/temp/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/temp/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:
Obrázek 2: Komolý jehlan obsahující tu část prostoru, která se promítne do výsledné scény.
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/temp/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/temp/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).
Obrázek 3: Procedurální textura.
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.
Obrázek 4: Napodobení cihlové zdi s využitím 2D textury.
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.
Obrázek 5: Chybí v tomto modelu dvě stěny, nebo jsou pokryty průhlednou texturou?
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í.
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/temp/step1.html |
| 2 | step1.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step1.js |
| 3 | step2.html | využití kreslicího plátna s 2D kontextem | https://tisnik.github.io/temp/step2.html |
| 4 | step2.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step2.js |
| 5 | step3.html | vymazání barvového bufferu pro kontext WebGL | https://tisnik.github.io/temp/step3.html |
| 6 | step3.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step3.js |
| 7 | step4.html | transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu | https://tisnik.github.io/temp/step4.html |
| 8 | step4.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step4.js |
| 9 | step5.html | vykreslení jediného bodu na plátno v kontextu WebGL | https://tisnik.github.io/temp/step5.html |
| 10 | step5.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step5.js |
| 11 | step6.html | vykreslení bodu s určením jeho souřadnic ve scéně | https://tisnik.github.io/temp/step6.html |
| 12 | step6.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step6.js |
| 13 | step7.html | vykreslení většího množství bodů do scény | https://tisnik.github.io/temp/step7.html |
| 14 | step7.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step7.js |
| 15 | step8.html | kontrola provedení jednotlivých operací | https://tisnik.github.io/temp/step8.html |
| 16 | step8.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step8.js |
| 17 | step9.html | vykreslení trojice úseček na plochu kreslicího plátna | https://tisnik.github.io/temp/step9.html |
| 18 | step9.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/step9.js |
| 19 | stepA.html | nastavení konstantní barvy úseček | https://tisnik.github.io/temp/stepA.html |
| 20 | stepA.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/stepA.js |
| 21 | stepB.html | vykreslení na sebe navazujících úseček s předáním souřadnic | https://tisnik.github.io/temp/stepB.html |
| 22 | stepB.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/stepB.js |
| 23 | stepC.html | vykreslení uzavřené sekvence úseček s předáním souřadnic | https://tisnik.github.io/temp/stepC.html |
| 24 | stepC.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/stepC.js |
| 25 | stepD.html | vykreslení bodů, každý bod může mít libovolnou barvu | https://tisnik.github.io/temp/stepD.html |
| 26 | stepD.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/stepD.js |
| 27 | stepE.html | vykreslení úseček, barva je vypočtena interpolací barev | https://tisnik.github.io/temp/stepE.html |
| 28 | stepE.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/stepE.js |
| 29 | triangle1.html | vykreslení trojúhelníku konstantní barvou | https://tisnik.github.io/temp/triangle1.html |
| 30 | triangle1.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle1.js |
| 31 | triangle2.html | interpolace barev při vyplňování trojúhelníku | https://tisnik.github.io/temp/triangle2.html |
| 32 | triangle2.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle2.js |
| 33 | triangle3.html | vykreslení pásu trojúhelníků | https://tisnik.github.io/temp/triangle3.html |
| 34 | triangle3.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle3.js |
| 35 | triangle4.html | vykreslení trsu trojúhelníků – neúplná varianta | https://tisnik.github.io/temp/triangle4.html |
| 36 | triangle4.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle4.js |
| 37 | triangle5.html | vykreslení trsu trojúhelníků – úplná varianta | https://tisnik.github.io/temp/triangle5.html |
| 38 | triangle5.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle5.js |
| 39 | triangle6.html | překrývající se trojúhelníky (Z-buffer není použit) | https://tisnik.github.io/temp/triangle6.html |
| 40 | triangle6.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle6.js |
| 41 | triangle7.html | překrývající se trojúhelníky (Z-buffer je použit) | https://tisnik.github.io/temp/triangle7.html |
| 42 | triangle7.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/triangle7.js |
| 43 | cube1.html | 3D krychle vykreslená do 2D plochy bez aplikace transformací | https://tisnik.github.io/temp/cube1.html |
| 44 | cube1.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube1.js |
| 45 | cube2.html | transformace reprezentovaná jednotkovou maticí | https://tisnik.github.io/temp/cube2.html |
| 46 | cube2.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube2.js |
| 47 | cube3.html | zobrazení 3D kostky z různých úhlů pohledu | https://tisnik.github.io/temp/cube3.html |
| 48 | cube3.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube3.js |
| 49 | cube4.html | zobrazení 3D kostky s využitím základní perspektivní projekce | https://tisnik.github.io/temp/cube4.html |
| 50 | cube4.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube4.js |
| 51 | cube5.html | krychle v 3D prostoru | https://tisnik.github.io/temp/cube5.html |
| 52 | cube5.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube5.js |
| 53 | cube6.html | rotace krychle v 3D prostoru s perspektivní projekcí | https://tisnik.github.io/temp/cube6.html |
| 54 | cube6.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube6.js |
| 55 | cube7.html | animace rotace krychle složené z hran (úseček) v 3D prostoru s perspektivní projekcí | https://tisnik.github.io/temp/cube7.html |
| 56 | cube7.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube7.js |
| 57 | cube8.html | animace rotace krychle složené z trojúhelníků v 3D prostoru s perspektivní projekcí | https://tisnik.github.io/temp/cube8.html |
| 58 | cube8.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/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/temp/cube9.html |
| 60 | cube9.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cube9.js |
| 60 | cubeA.html | změna vzdálenosti bližší pohledové roviny | https://tisnik.github.io/temp/cubeA.html |
| 61 | cubeA.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cubeA.js |
| 62 | cubeB.html | doplnění rotující krychle o obě chybějící stěny | https://tisnik.github.io/temp/cubeB.html |
| 63 | cubeB.js | skript s implementací tohoto příkladu | https://tisnik.github.io/temp/cubeB.js |
Odkazy na Internetu
- WebGL: 3D Graphics for the Web
https://www.khronos.org/webgl/ - OpenGL Shading Language
https://en.wikipedia.org/wiki/OpenGL_Shading_Language - WebGL (Wikipedia)
https://en.wikipedia.org/wiki/WebGL - WebGL
https://wikis.khronos.org/webgl/Main_Page - OpenGL ES (Wikipedia)
https://en.wikipedia.org/wiki/OpenGL_ES - Float32Array
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array - ECMAScript compatibility table
https://compat-table.github.io/compat-table/es6/ - Learn-OpenGLES-Tutorials
https://github.com/learnopengles/Learn-OpenGLES-Tutorials - Implement OpenGL ES and EGL
https://source.android.com/docs/core/graphics/implement-opengl-es - Khronos Group
https://www.khronos.org/ - Khronos Group (Wikipedia)
https://en.wikipedia.org/wiki/Khronos_Group - Raspberry Pi VideoCore APIs
http://elinux.org/Raspberry_Pi_VideoCore_APIs - EGL quick reference card
https://www.khronos.org/files/egl-1–4-quick-reference-card.pdf - EGL Reference Pages Index
https://www.khronos.org/registry/egl/sdk/docs/man/html/indexflat.php - GLSL
https://glsl.app/ - Shader Tutorials by Ronja
https://www.ronja-tutorials.com/ - CanvasRenderingContext2D
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D - WebGL2RenderingContext
https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext - WebGLRenderingContext
https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext - Matrix Transformations
https://vitaminac.github.io/Matrices-in-Computer-Graphics/ - Homogeneous coordinates
https://en.wikipedia.org/wiki/Homogeneous_coordinates - Transformation matrix
https://en.wikipedia.org/wiki/Transformation_matrix - Affine transformation
https://en.wikipedia.org/wiki/Affine_transformation - OpenGL Projection Matrix
https://songho.ca/opengl/gl_projectionmatrix.html
