WebGL

Kurz počítačové grafiky

Autor: Jiří Hnídek / jiri.hnidek@tul.cz

Historie: Web & 3D

Snaha dostat 3D do webového prohlížeče není nová.

  • VRML: značkovací jazyk založený na XML
    • Nutné instalovat do prohlížeče dodatečný plugin
    • Většinou pouze procházení statické scény
  • Blender Player: interaktivní aplikace (hry)
    • Nutné instalovat do prohlížeče dodatečný plugin
    • Plně interaktivní aplikace skriptovatelné v Pythonu
    • Obsah nutné vytvářet v jedné aplikaci
  • Flash: snad ani není nutné komentovat

Důvody neúspěchu?

  • Nutné použít plugin
  • Paměťové náročné
  • Špatný návrh

HTML5 & WebGL

WebGL je standard pro 3D grafiku odvozený z OpenGL ES 2.0 (OpenGL for Embeded Systems). O standardizaci se stará Khronos Group.

Podpora ve webových prohlížečích

V současnosti (2016) podporují WebGL 1.0.4 všechny hlavní prohlížeče (jejich poslední verze). Statisticky 92% uživatelů má zapnutou podporu WebGL. Aktuální statistika je dostupná na webglstats.com (průkaznost statistických dat?).

Podpora pro WebGL 2.0 (založeno na OpenGL ES 3.0) je experimentálně dostupná pouze v některých prohlížečích a musí být explicitně povolená.

Důvod nefunkčnosti WebGL

  • Stará verze prohlížeče.
  • Neaktualizovaný a nebezpečný ovladač grafické karty (blacklisting).
  • Vypnutá podpora WebGL v nastavení prohlížeče.

Test funkčnosti WebGL

Khronos Conformance testy

Khronos Group vytvořil sadu testů podobných ACID testům od W3C. Svůj prohlížeč si můžete otestovat na adrese:

https://goo.gl/LMJfC6

Canvas & Javascript & WebGL

  • Nutná je nejprve podpora por HTML5 elelement canvas
  • Následně je nezbytné, aby Javascript API mělo přístup k 3D kontextu canvasu ("webgl"/"experimental-webgl")

Minimální WebGL aplikace

<!DOCTYPE html>
<html><body>
<canvas id="c01"></canvas>
<script type="text/javascript">
	var canvas = document.getElementById("c01");
	var gl = canvas.getContext("webgl");
	gl.clearColor(0.3, 0.8, 0.3, 1.0);
	gl.clear(gl.COLOR_BUFFER_BIT);
</script>
</body></html>

Zobrazovací řetězec WebGL

  • WebGL nepoužívá pevně danou zobrazovací řetězec jako má OpenGL (viz. předchozí přednáška)
  • WebGL vyžaduje použití vertex a fragment shaderů

Vertex & Fragment Shader

  • Speciální programovací jazyk podobný jazyku C
  • Kompilovaný na grafické kartě
  • Vykonávaný na grafické kartě
  • Grafická karta (2016): masivně paralelní stroj (stovky až tisíce výpočetních jader) - velmi rychlý běh

Vertex Shader

  • Každý vrchol vstupující do vertex shaderu je tímto shaderem transformován. Zde mohou být prováděny například tyto operace:
    • Transformace (rotace, zvětšní, posun, atd.)
    • Transformace do souř. systému kamery
    • Promítání (perspektivní/roviné)
    • Mnohé další
  • Výstupem je čtyřprvkový vektor gl_Position, velikost bodu gl_PointSize a případně nastavení hodnot pro proměnné typu varying.

Minimální Vertex Shader

<!DOCTYPE html>
<html><body>
<canvas id="c02"></canvas>
<script type="text/javascript">
	var canvas = document.getElementById("c02");
	var gl = canvas.getContext("webgl");
	var vertexShader = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vertexShader,
		'void main(void) { gl_Position = vec4(0, 0, 0, 1);}');
	gl.compileShader(vertexShader);
	var program = gl.createProgram();
	gl.attachShader(program, vertexShader);
	gl.linkProgram(program); gl.useProgram(program);
	gl.clearColor(0.3, 0.3, 0.8, 1.0);
	gl.clear(gl.COLOR_BUFFER_BIT);
</script>
</body></html>

Příklad minimálního Vertex Shaderu

Demonstrační program má drobnou nevýhodu: vertex shader nic nedělá, protože nemá žádná vstupní data a jeho výstup není vstupem do fragment shaderu.

Tvorba primitiv a rasterizace

  • Po transformaci vertexů dojde k vytvoření jednotlivých primitiv (základních elementů):
    • Body
    • Hrany
    • Trojúhelníky
  • Následně dojde k rasterizaci těchto základních elementů do rastru fragmentů (pixelů).

Fragment Shader

  • Fragment shader je aplikovaný na každý fragment (pixel) rasterizovaných elementů.
  • Výstup fragment shaderu se uloží do framebufferu a zobrazí

Miniální Fragment Shader

<!DOCTYPE html>
<html><body>
<canvas id="c03"></canvas>
<script type="text/javascript">
	var canvas = document.getElementById("c03");
	var gl = canvas.getContext("webgl");
	var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fragmentShader,
		'void main(void) {gl_FragColor = vec4(1, 1, 1, 1);');
	gl.compileShader(fragmentShader);
	var program = gl.createProgram();
	gl.attachShader(program, fragmentShader);
	gl.linkProgram(program); gl.useProgram(program);
	gl.clearColor(0.8, 0.3, 0.3, 1.0);
	gl.clear(gl.COLOR_BUFFER_BIT);
</script>
</body></html>

Příklad minimálního Fragment Shaderu

Demonstrační program má opět drobnou nevýhodu: fragment shader nic nedělá, protože nemá žádné vstupní fragmenty.

Příklad minimálního a funkčního vertex & fragment shaderu


https://goo.gl/p8UZor

Shadery a datové typy

  • Jednoduché datové typy: void, bool, int, float
  • Vektory floatů: vec2, vec3, vec4
  • Vektory booleanů: bvec2, bvec3, bvec4
  • Vektory integerů: ivec2, ivec3, ivec4
  • Čtvercové matice floatů: mat2, mat3, mat4
  • Textury: sampler2D, samplerCube
  • Jednorozměrné pole a struktury

Konstrukce pro řízení shaderů

  • Cykly:
  • for(;;) {}
    while() {}
    do {} while()
  • Podmínky:
  • if() {}
    if() {} else {}
    
  • Skoky:
  • break, continue, return, discard
  • V shader si můžete definovat vlastní funkce:
  • float add(float a, float) {return a + b};

Vestavěné funkce shaderů

Je jich cca 60. Příklady nejpoužívanějších: sin(), asin(), cos(), acos(), tan(), atan(), pow(), exp(), log(), exp2(), log2(), sqrt(), abs(), floor(), ceil(), mod(), min(), max(), atd.

Kompletní seznam je například v Quick Reference Card

Double-buffering

“WebGL: double-buffering batteries included!”
  • Framebuffer není ve skutečnosti jeden, ale jsou dva: přední a zadní.
  • Do zadního se postupně vykresluje
  • Přední je zobrazován uživateli
  • Při dokončení vykreslování se buffery prohodí
  • Zamezuje problikávání a vzniku rušivých artefaktů

Z-Buffer alias paměť hloubky

  • Efektivně řeší problém viditelnosti objektů
  • Každý fragment si v sobě uchovává svoji vzdálenost od kamery v tzv. z-bufferu.
  • Vzdálený objekt nemůže překreslit objekty blíže ke kameře.
  • gl.enable(gl.DEPTH_TEST);

Vstupní data: vertexy & indexy

  • Nejprve je nutné říci, že vertex shader bude používat nějaká vstupní data a jak je bude používat:
  • 
    attribute vec3 pos;
    void main(void) { gl_Position = vec4(pos, 1); }
    
  • Vstupní data je nutné předat do vertex shaderu pomocí ArrayBufferu

ArrayBuffer & typové pole

  • Data jsou mezi aplikací psanou v Javascriptu a shadery předávána v tzv. ArrayBufferech
  • ArrayBuffer se vytváří pomocí tzv. typového pole (novinka v HTML5).
  • Typové pole může obsahovat data pouze jednoho typu.
    • Uint8Array, Uint16Array, Uint32Array
    • Int8Array, Int16Array, Int32Array
    • Float32Array, Float64Array

Předáváme data pomocí ArrayBuffer

  • Nejprve řekneme, do jakého atributu budeme data předávat
  • var posLoc = gl.getAttribLocation(program, "pos");
    gl.enableVertexAttribArray(posLoc);
  • Následně vytvoříme ArrayBuffer, naplníme ho obsahem z typového pole a propojíme ho s atributem pos
  • var posBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer); // aktivní buffer
    var vertices = [0.0, 0.0, 0.0];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices),
        gl.STATIC_DRAW);
    gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0)
    

Předávaná data

  • Data předávaná do vertex shaderu nemusí a nejsou být pouze souřadnice vertexů.
  • Může jít o barvu vertexů, UV souřadnice, matice, normálové vektory, atd.

Vykreslení elementů

  • Elementy (v tomto případě body) následně vykreslíme pomocí (vykreslujeme data z aktivního bufferu):
  • gl.drawArrays(gl.POINTS, 0, vertices.length/3);
  • Podporované typy elementů: POINTS, LINE_STRIP, LINE_LOOP, LINES, TRIANGLE_STRIP, TRIANGLE_FAN, TRIANGLES

Elementy a ArrayBuffer

  • Složitější útvary než vertexy (hrany a trojúhelníky) je vhodné vykreslovat pomocí indexů do bufferu vertexů.
  • K tomu slouží ElementArrayBuffer:
  • 
    var indexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    var indices = [0, 1, 2];
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,
        new Uint8Array(indices), gl.STATIC_DRAW);
    

Vykreslení elementů

  • Elementy pomocí indexů vykreslujeme následovně (opět se data berou z aktuálního bufferu)
  • gl.drawElements(gl.LINES, 6, gl.UNSIGNED_BYTE, 0);
  • Podporované typy elementů jsou stejné: POINTS, LINE_STRIP, LINE_LOOP, LINES, TRIANGLE_STRIP, TRIANGLE_FAN, TRIANGLES

Souřadné systémy

  • Je dobré si povšimnout, že hodnoty, které vystupují z vertex shaderu jsou v homogenních souřadnicích a následně jsou převedeny ho kartézské soustavy souřadné.
  • Pohledové souřadnice mají počátek soustavy souřadné v prostředku a v pravém horním rohu je bod [1, 1, 0].
  • V neposlední řadě je třeba zmínit soustavu souřadnou canvasu. Počátek soustavné je v levém horním rohu. Bod [width, height] je v pravém dolním rohu.

Varying proměnné

  • Pokud je nějaká promenná označena ve vertex shaderu klíčovým slovem varying, tak během rasterizaci elementů dochází k její lineární interpolaci. Pro každý fragment elementu (úsečka, trojúhelník) je spočítána hodnota.
  • varying vec3 color;

Uniform proměnné

  • Proměnné označené klíovým slovem unform jsou neměné pro celý vertex nebo fragment shader.
  • Nejčastěji se používají pro matice

Quick Reference Card

WebGL Reference Card

Děkuji za pozornost.