Přejít k navigační liště

Zdroják » JavaScript » Vytváříme Hello World pro WebGL

Vytváříme Hello World pro WebGL

Články JavaScript

Když jsem se před časem poprvé ponořil do světa WebGL, začínal jsem na zelené louce. Kdybych chtěl mít rychle nějaký výstup, jistě bych sáhnul po hotovém řešení, poskytujícím přímo graf scény (například vynikající three.js). Já chtěl ale vědět, jak a proč ty věci fungují; každou funkci si vyzkoušet a pochopit její účel. Své poznatky budu sepisovat, kdyby se náhodou někomu hodily…

Nálepky:

WebGL asi není třeba dlouze představovat – jde o JavaScriptové API pro akcelerované vykreslování grafiky do HTML canvasu; rozhraní je navrženo tak, aby bylo architektonicky identické s OpenGL ES 2.0. Pokud tedy máte s OpenGL nějakou zkušenost (já neměl žádnou), bude pro vás tento článek možná zbytečný a nezáživný.

Co vývojáře po prvních pár experimentech s WebGL nejvíce zaujme?

  • Podpora prohlížečů je dobrá, ale může se lišit v závislosti na konkrétním hardwaru či operačním systému.
  • WebGL je kromobyčejně ukecané, v porovnání s typickým JS API. To je daň za ohromnou flexibilitu a výkon.
  • WebGL se mizerně ladí; zpravidla je lepší postupovat po malých krůčcích a dokázat případné problémy redukovat a izolovat.
  • Na WebGL i OpenGL není skoro nic trojrozměrné; většinu 3D věcí si človek musí zařídit sám.

Během tohoto úvodního článku zkusíme vytvořit nejmenší funkční ukázku WebGL: nakreslíme jeden barevný bod. Něco takového je v tradičním 2D canvasu otázkou tří řádků kódu (viz článek Canvas – říkejme tomu plocha na kreslení); řešení ve WebGL je o poznání složitější a okouknout ho můžeme na této adrese: http://jsfiddle.net/ondras/qt7sk/.

Pojďme se nejprve podívat, co všechno WebGL potřebuje, aby mohlo něco vykreslit:

  1. Souřadnice prvků, které chceme vykreslovat. Můžou být jedno- až čtyřrozměrné; v našem případě to bude jeden dvourozměrný bod.
  2. Vertex shader; program, jehož úkolem je zejména přepočítat naše vlastní souřadice do tzv. clipspace, souřadného systému pro vykreslování v canvasu.
  3. Fragment shader (někdy též méně korektně nazývaný pixel shader); program, jehož úkolem je spočítat barvu jednotlivých bodů všech vykreslovaných objektů.

Abychom mohli s WebGL cokoliv dělat, musíme nejprve z HTML canvasu získat kontext:

var gl = document.querySelector("canvas").getContext("experimental-webgl");

Všechna následná volání budou metody objektu gl. Název „experimental-webgl“ je dočasný (i když jej používají všechny současné prohlížeče) a bude v budoucnu nahrazen plnohodnotným „webgl“.

Shadery

Vertex i fragment shader jsou programy v jazyce, který byl velmi originálně nazván GLSL (GL Shading Language). Tyto programy jsou vykonávány přímo na GPU (přesněji: ovladač grafické karty je kompiluje do kódu, který je vykonáván na GPU), a proto jsou extra rychlé. Pro nás to bohužel znamená nutnost naučit se krom WebGL API ještě další jazyk, ale naštěstí to není nic složitého. Jeho syntaxe je podobná C a pro náš testovací program budou oba shadery triviální. Zdrojový kód shaderů se ve WebGL předává jako řetězec; můžeme ho proto uložit do JS stringu, do externího souboru (XHR) nebo do uzlu <script> ve stránce. Pro naše účely bude bohatě stačit předání v JS řetězci. Ve WebGL musíme shader nejprve vytvořit, pak mu předat zdrojový kód a pak ho zkompilovat. Jak bude vypadat ten náš?

attribute vec2 pos;

void main(void) {
    gl_Position = vec4(pos, 0.0, 1.0);
    gl_PointSize = 5.0;
}

Jeho vstupem je proměnná (dvourozměrné pole) pos; hlavním úkolem vertex shaderu je naplnit pro každý vstupní bod vestavěnou proměnnou gl_Position, která odpovídá výslednému umístění bodu. Zajímavé je, že gl_Position je čtyřrozměrné pole; proto k našemu dvourozměrnému doplníme nulu (na ose Z; tato hodnota nás vůbec nezajímá) a jedničku. Jednička na konci je důležitá: gl_Position je tzv. homogenní souřadnice, která u bodů v prostoru potřebuje poslední hodnotu právě jedna. (Proč? Více o tomto v některé z dalších kapitol, kde si povíme o tom, jak přesně probíhá výpočet pozice v canvasu.) Jako bonus ještě nastavíme vestavěnou hodnotu gl_PointSize, aby náš bod vypadal větší než jeden pixel.

A můžeme jít na další shader:

void main(void) {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

Fragment shader je ještě jednodušší: do vestavěné proměnné gl_FragColor akorát přiřadí červenou. Barvy se zapisují též jako čtyřrozměrné vektory; čtvrtá složka je průhlednost (1 = žádná průhlednost). Všechny složky jsou hodnoty mezi nulou a jedničkou.

Následně z obou shaderů vytvoříme tzv. OpenGL program a nastavíme jej jako aktivní. To proto, že v praxi budeme chtít mít k dispozici programů/shaderů celou řadu:

var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);

Dále si „sáhneme“ na vstupní hodnotu vertex shaderu (pos) a řekneme, že její hodnotu budeme definovat pomocí JS pole:

var posLoc = gl.getAttribLocation(program, "pos");
gl.enableVertexAttribArray(posLoc);

Většina příprav je za námi, jde se renderovat! Vlastně ne, ještě jsme nikam nezadali naši geometrii, respektive souřadnice našeho jediného bodu. Pro tento účel se musíme seznámit s buffery.

Buffery

Veškerá data, předávaná do GPU, jsou uložena v tzv. bufferech – kusech paměti, rychle přístupných z grafické karty. Práce s buffery může vypadat podivně, protože připomíná stavový automat: nejprve nastavíme buffer jako aktivní (bindBuffer) a tím říkáme, že všechny následné operace budou prováděny právě nad ním. Žádné předávání bufferů jako parametrů.

var posBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0.5, 0.5]), gl.STATIC_DRAW);

Konstantou gl.ARRAY_BUFFER specifikujeme, že chceme pracovat s obecným polem. Ještě je tu varianta gl.ELEMENT_ARRAY_BUFFER, o které si povíme někdy příště. Voláním bufferData nahrajeme zadané JS pole (musí to být typované pole, v našem případě Float32Array) do paměti grafické karty. Třetí parametr (gl.STATIC_DRAW) říká, jak máme v plánu tato data využívat a měnit: v našem případě říkáme, že je nechceme měnit vůbec.

gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);

Tímto voláním provážeme právě aktivní buffer (ten, kde jsou souřadnice našeho bodu) s vstupem vertex shaderu. Dalšími parametry říkáme, že se z bufferu bude číst po dvou hodnotách, že to jsou destinná čísla, že je nechceme normalizovat, že nechceme žádné hodnoty přeskakovat a že budeme číst od začátku bufferu. Uff!

gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 1);

Konečně jsme něco namalovali! Voláním clearColor nastavujeme barvu, kterou se má canvas vymazat (černou). Voláním clear plátno vymažeme a vposled dáme pokyn k vykreslení. Naši geometrii chceme vykreslovat jako prosté body (gl.POINTS), začneme na prvním a vykreslíme jeden. Vidíme červený čtvereček, hurá!

Celý zdrojový kód z tohoto článku

Ve spustitelné podobě ho najdete na http://jsfiddle.net/ondras/qt7sk/, kde s ním můžete dále experimentovat (třeba při řešení domácího úkolu).

var gl = document.querySelector("canvas").getContext("experimental-webgl");

var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, 'attribute vec2 pos; void main(void) { gl_Position = vec4(pos, 0.0, 1.0); gl_PointSize = 5.0;}');
gl.compileShader(vertexShader);

var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, 'void main(void) {gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);}');
gl.compileShader(fragmentShader);

var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);

var posLoc = gl.getAttribLocation(program, "pos");
gl.enableVertexAttribArray(posLoc);

var posBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0.5, 0.5]), gl.STATIC_DRAW);
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);

gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 1);

Domácí úkol

Na konec této kapitoly můžeme zvážit nějaká drobná vylepšení za domácí úkol:

  • Změnit barvu bodu? Jasně, stačí upravit fragment shader.
  • Změnit pozici bodu? Jasně, stačí upravit pole souřadic, předávané v metodě bufferData.
  • Vykreslit více bodů? Jasně, přidat další souřadnice do pole bodů a zvýšit jejich počet v metodě drawArrays.
  • Vykreslit body jako kolečka? Pokud se čtverečky nelíbí, bude to obtížnější. Takovou úpravu bychom provedli ve fragment shaderu (který je vykonáván pro každý vykreslovaný pixel). Přidali bychom test hodnoty gl_PointCoord, která nabývá hodnot mezi nulou a jedničkou a určuje vzdálenost od středu vykreslovaného vrcholu. Pokud by vzdálenost od středu byla vyšší než zadaný poloměr, bod bychom vykreslili jinou barvou (např. průhlednou).

Komentáře

Odebírat
Upozornit na
guest
3 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
Zobrazit všechny komentáře
Jan Prachař

Napínavý až do konce. :+1:

Ten „dvourozměrný bod“ by měl spíš být „bod ve dvourozměrném prostoru“.

nn
var FSHADER_SOURCE = 'void main(void) {\n' +
    '       if ( distance(gl_PointCoord - vec2(0.5, 0.5), vec2(0.0, 0.0)) <= 0.5) {\n' + 
    '              gl_FragColor = vec4(1.0, 0.4, 0.0, 0.60);\n' + // Set the color - farba bodu
    '       }\n' +
    '       else\n' +
    '               discard;\n' +
    '}\n';
Ondra B

Dobrý den.

Vyvíjím obsáhlejší aplikaci pomocí webgl. Doteď šlo vše dobře ale najednou mi všechny prohlížeče začali vypisovat chybu.
Např chrome: „Aj chyba! Při zobrazování této webové stránky došlo k chybě. Může pomoci když zpřístupníte více paměti tím, že zavřete aplikace a karty které nepotřebujete.“

Tuším že je chyba v velkém množství dat. Kvůli několika OBJ objektům s texturou aplikace pracuje s cca 50MB dat.
Já vím že je to mnoho. Pokud je problém zde, jak by šla aplikace upravit? Zaslechl jsem, že lze spouštět javascript i na serveru. Byla by to cesta?

Přístupnost není jen o splnění norem: nový pohled na inkluzivní design

Přístupnost a inkluze možná nepatří mezi nejžhavější témata digitálního světa – dokud o nich nezačne mluvit Vitaly Friedman. Na WebExpo 2024 předvedl, že inkluzivní design není jen o splněných checkboxech, ale hlavně o lidech. S energií sobě vlastní obrátil zažité přístupy naruby a ukázal, že skutečně přístupný web je nejen možný, ale i nezbytný.

Efektivnější vývoj UI nebo API: Co si odnést z WebExpo 2025?

Různé
Komentáře: 0
Jak snadno implementovat moderní uživatelské rozhraní? Které funkce brzdí rychlost vašeho webu? A kdy raději sami přibrzdit, abychom využitím AI nepřekročili etické principy? Debatu aktuálních dev témat rozdmýchá sedmnáctý ročník technologické konference WebExpo, která proběhne v Praze od 28. do 30. května. Který talk či workshop si rozhodně nenechat ujít? Toto je náš redakční výběr z vývojářských hroznů.

Zapřáhněte AI jako nikdy předtím. Květnová konference WebExpo přivítá hvězdy technologického světa

Od 28. do 30. května 2025 promění pražský Palác Lucerna na tři dny technologická konference WebExpo. Na programu je více než 80 přednášek a workshopů od expertů z celého světa. WebExpo tradičně propojuje vývojáře, designéry, marketéry i byznysové lídry a nabízí praktické dovednosti, strategické myšlení a přináší nejnovější trendy nejen v oblasti AI.