Programy, ve kterých je využita 3D grafika, nejsou omezeny na hry ani na desktopové aplikace. Podpora 3D grafiky je integrována i do webových prohlížečů díky technologii WebGL nabízející mj. i programovatelnou pipeline.
Využívá se přitom OpenGL ES, přičemž zkratka ES znamená „embedded systems“. Striktně řečeno webové prohlížeče do této kategorie nespadají, ovšem díky tomu, že je OpenGL ES podporováno jak v prohlížečích, tak i například ve smartphonech, se jedná o zajímavou a užitečnou technologii, kterou je vhodné prozkoumat a porovnat ji s původní myšlenkou OpenGL (která byla společně s vývojem programovatelných GPU rozšířena).
V praktické části článku si ukážeme základy OpenGL ES. Využijeme přitom pouze webový prohlížeč (a programátorský textový editor). Žádné další nástroje (překladače, NPM atd.) nejsou zapotřebí.
Původní neměnitelný vykreslovací řetězec (pipeline) v OpenGLV této kapitole si ve stručnosti a s malými nepřesnostmi popíšeme původní pevný (neprogramovatelný) vykreslovací řetězec OpenGL, který se používal na pracovních stanicích i v PC (typicky před příchodem programovatelných GPU). Při vykreslování 2D obrazců či celých 3D scén se do vykreslovacího řetězce (rendering pipeline) OpenGL nahrávají dva odlišné typy dat: údaje o vrcholech (vertex) a rastrová data. Tato data jsou průběžně vykreslovacím řetězcem zpracovávána, přičemž se na výstupu (ve framebufferu) vytváří rastrová podoba zadané scény, která je následně zobrazena na monitoru. Jednotlivé moduly vykreslovacího řetězce jsou zobrazeny na následujícím obrázku:
Obrázek 1: Pevná vykreslovací pipeline OpenGL.
Autor: tisnik, podle licence: Rights Managed
V dalších odstavcích budou tyto moduly podrobněji popsány. Všimněme si však nejprve celkové koncepce systému. Vykreslovaná data jsou logicky rozdělena na geometrickou část (vrcholy neboli vertexy grafických primitiv) a rastrovou část (bitmapy, pixmapy, textury). Tato data jsou ze začátku zpracovávána odděleně a teprve při rasterizaci v modulu označeném Rasterization dojde k jejich vzájemnému spojení. Po vykreslení dat do framebufferu je možné některé údaje z framebufferu přečíst zpátky do hlavní paměti procesoru nebo již vykreslený snímek zkombinovat se snímkem, který právě vzniká. Tato technika se používá například při blendingu nebo vykreslení scén se zrcadlícími povrchy.
Důležité je, že jednotlivé moduly pracují relativně nezávisle na ostatních modulech a mezi moduly se nepředávají žádné řídicí informace. Vše je řízeno pouze daty (jedná se tedy o architekturu data-flow), které do vykreslovacího řetězce postupně vstupují, což je významný rozdíl například vůči klasickému procesoru, který je řízen programem. Z tohoto faktu také vyplývá velká rychlost vykreslování pomocí grafických akcelerátorů, která je umožněna čtyřmi faktory:
Jedním ze dvou typů dat, které vstupují do vykreslovacího řetězce OpenGL, jsou údaje o vrcholech grafických primitiv (vertexech). Pro každý vertex musíme v klasickém OpenGL (ovšem ne v ES!) zadat minimálně jeho souřadnice příkazem glVertex*()(souřadnice jsou zadány ve dvojici [x, y], trojici [x, y, z] nebo čtveřici [x, y, z, w]). Dále je možné u každého vertexu specifikovat další parametry: barvu, normálový vektor, souřadnice v prostoru textury, parametry materiálu či vlastnosti hrany (u úseček, trojúhelníků atd.).
Druhým typem dat, které vstupují do vykreslovacího řetězce, jsou rastrová data. Ta se používají ve třech případech:
Na každý vertex (specifikovaný buď příkazem glVertex*(), uložený v display listu nebo vyhodnocený pomocí evaluátorů) jsou aplikovány per-vertex operace. Jedná se zejména o transformaci souřadnic vertexu s použitím matice ModelView. Pokud jsou specifikovány i texturovací souřadnice, jsou transformovány s použitím Texture matice. Dále může být na základě údajů o materiálu vertexu a jeho normály vypočteno osvětlení.
Jednotlivé vertexy mohou být dále ořezány některou předem zadanou ořezávací rovinou. Pokud je vertex umístěn za ořezávací rovinou, je odstraněn z dalšího zpracování. V případě úseček nebo polygonů mohou být po ořezání další vertexy přidány, aby se vykreslil zbytek viditelné části primitiva.
Po ořezání jsou souřadnice vertexů transformovány pomocí transformační matice Projection, kde je u 3D scén většinou provedena perspektivní projekce. Po této transformaci je provedeno další ořezání, teď už však pro šest základních ořezávacích rovin (jsou paralelní s osami souřadného systému).
Pixel operationsOperace nad pixely spočívají v dekódování barev jednotlivých pixelů z formátu zadaného aplikací do interního formátu, který je na grafické kartě používán. Po tomto dekódování, které spočívá v rozložení (unpack) tří nebo čtyř barvových složek, jejich případném prohození (swap), posunutí (bias), změně kontrastu (scale) a přemapování (map) se pixely buď uloží do texturovací paměti jako rastr textury, nebo jsou přímo poslány dále do rasterizační jednotky.
RasterizationGrafická primitiva specifikovaná svými vrcholy jsou vykreslena tak, že se vygenerují ty fragmenty, které dané primitivum pokrývá. Toto vygenerování se nazývá rasterizace. Při rasterizaci je zapotřebí brát v úvahu jak transformované souřadnice jednotlivých vertexů, tak i případná rastrová data (textury nebo pixely bitmap a pixmap).
Texture assemblyV tomto bloku se provádí různé algoritmy nad daty uloženými v texturovací paměti a rastrovými daty, které posílá aplikace. Jedná se většinou o jednoduché (a tím i rychlé) algoritmy pro kombinování více textur, mipmaping apod. Textury jsou v ideálním případě uloženy přímo v paměti grafického akcelerátoru. V horším případě jsou uloženy v hlavní paměti počítače a v době, kdy jsou zapotřebí pro vykreslování jsou postupně nahrávány do grafického akcelerátoru, což může znamenat značné zatížení sběrnice procesoru.
Per-fragment operationsPokud je povoleno texturování, je pro každý fragment vygenerován jeden pixel z texturovací paměti nazývaný texel. Tento texel je potom na fragment aplikován. V nejjednodušším případě je barva fragmentu změněna přímo na barvu texelu, ve složitějších případech se mohou provést další operace nad daty fragmentu a texelu, například smíchání barev (blending).
Dále je pro každý fragment proveden výpočet mlhy (fog) a antialias. Poté se provedou operace s jednotlivými buffery (pokud jsou povoleny), tedy test na hodnotu alfa složky fragmentu (alpha test), test s hodnotou uloženou ve stencil bufferu (stencil test) a test s hloubkou uloženou v Z-bufferu (depth test). Následně může být proveden dithering a v true-color režimu se může provést blending (smíchání barev více fragmentů, které obsazují stejné místo na obrazovce).
Všechny tyto operace se samozřejmě mohou buď povolit nebo zakázat. Pokud všechny operace nad fragmenty zakážeme, vykreslí se grafická primitiva postupně tak, jak jsou posílána do vykreslovacího řetězce (mohou se tedy postupně překreslovat) a není nijak změněna jejich barva. Zapnutím texturování, mlhy, antialiasingu, ditheringu nebo operací s buffery lze zásadně ovlivnit výsledný vzhled jednotlivých fragmentů a tím i vzhled celé scény.
FramebufferVýstup je uložen do takzvaného framebufferu, který se skládá z několika samostatných bufferů. Mezi nejdůležitější buffery patří barvový buffer (color buffer), paměť hloubky (Z-buffer), paměť šablony (stencil buffer) a akumulační buffer (accumulation buffer). Podobně jako jsou v bitmapách uloženy jednotlivé pixely, jsou ve framebufferu uloženy takzvané fragmenty, které představují průřez všemi buffery. Pro uživatele je jistě nejdůležitější color buffer, neboť v něm jsou uloženy barvy všech fragmentů. Tento buffer je většinou přímo zobrazen na obrazovce, i když jsou možné i další (netypické) režimy zobrazení.
Od klasického OpenGL k OpenGL ESWebGL je technologie založená nikoli na klasické knihovně OpenGL s pevnou vykreslovací pipeline, ale na OpenGL ES, která se též někdy označuje zkratkou GLES. Jedná se o variantu známé knihovny OpenGL, ovšem upravené takovým způsobem, aby se tato knihovna dala efektivně použít na smartphonech a tabletech vybavených programovatelným GPU (právě z tohoto důvodu je v názvu použito „ES“, což v tomto kontextu znamená „for Embedded Systems“). V současnosti existuje již několik specifikací OpenGL ES. Nás bude v dnešním článku nejvíce zajímat OpenGL ES 2.0, protože právě tato verze je podporována webovými prohlížeči. A nejenom tam: OpenGL ES 2.0 je totiž dostupná například i pro Raspberry Pi (několik vývojářů se dokonce vyjádřilo v tom smyslu, že na RPi je specifikace dodržována velmi dobře a proto je RPi ideální pro ladění aplikací, které OpenGL ES 2.0 používají).
Již na začátku je nutné zdůraznit, že OpenGL ES 2.0 je v mnoha ohledech odlišná od „klasické desktopové“ knihovny OpenGL (ta vznikla z dnešního pohledu v počítačovém „středověku“ pro grafické pracovní stanice společnosti SGI, ostatně některé funkce OpenGL nalezneme i ve starší knihovně IRIS GL). To znamená, že není možné například převzít zdrojové kódy aplikace postavené na klasickém OpenGL a GLUTu a přeložit je beze změny pro Raspberry Pi nebo převést do WebGL. Provedené úpravy v API GLESu však mají svůj smysl, protože konstrukce GPU se velmi změnila – namísto pevně nastavené vykreslovací pipeline dnes máme k dispozici programovatelná GPU, takže například všechny funkce, které se v původní OpenGL zabývaly výpočtem osvětlení, již není nutné mít zafixovány, protože je možné implementovat si vlastní shadery. Navíc je možné vhodným naprogramováním GPU použít tento čip i pro jiné výpočty (FFT, kodeky, dnes pochopitelně i pro AI a LLM apod.)
Poznámka: někdy se můžeme setkat s názorem, že OpenGL ES je jednodušší než původní OpenGL. Na jednu stranu je v OpenGL ES definováno méně funkcí, ovšem na stranu druhou je nutné implementovat vlastní shadery, překládat je atd. a navíc se do pipeline OpenGL ES data předávají poněkud složitějším způsobem, než v klasické OpenGL (jednotlivé vykreslování primitiv popř. display listy).
Shadery a OpenGL Shading Language (GLSL)V OpenGL ES je celá vykreslovací pipeline programovatelná, což vývojářům přináší větší svobodu, protože je možné realizovat velké množství různých efektů, které by při použití pevné pipeline buď vůbec nebyly možné, nebo by vyžadovaly mnohem více výpočetního výkonu popř. přesuny dat mezi buffery a operační pamětí. Ovšem na druhou stranu je v OpenGL ES nutné nahradit původní „pevné“ výpočty s vertexy a fragmenty vlastními algoritmy. Implementace těchto algoritmů se nazývá shader a i v těch nejjednodušších aplikacích postavených nad OpenGL ES se setkáme se dvěma shadery: vertex shaderem a fragment shaderem.
Shadery budeme muset definovat i dnes, ovšem prozatím si vystačíme s jejich značně zjednodušenou (až primitivní) formou. Nejjednodušší bude fragment shader, protože ten bude nastavovat neměnnou barvu vykreslovaných fragmentů. Vertex shader může být stejně jednoduchý, ovšem v praxi je nutné, aby alespoň pracoval se souřadnicemi vykreslovaných entit.
WebGL: podpora OpenGL ES ve webových prohlížečíchSamotná knihovna OpenGL ES slouží ke konfiguraci vykreslovací pipeline i k následnému vykreslení 3D scény do framebufferu. Ovšem zde její role končí, takže je nutné nějakým jiným způsobem zajistit zobrazení obsahu framebufferu na monitoru (a to buď na celé obrazovce nebo v okně). K tomuto účelu lze využít několik knihoven, například EGL apod. Ovšem existuje i (z pohledu vývojáře) jednodušší způsob – vykreslení 3D scény přímo na ploše webové stránky. A právě k tomuto účelu slouží WebGL.
Jedná se o aplikační programové rozhraní, které umožňuje volání funkcí OpenGL ES (včetně překladu shaderů) z JavaScriptu. To znamená, že si můžeme vyzkoušet zobrazení 3D scén s využitím OpenGL ES bez nutnosti instalace a „rozcházení“ různých vývojářských knihoven, nemusíme ani instalovat překladač shaderů atd. Využijeme pouze dva nástroje, které má (prakticky) každý vývojář k dispozici: programátorský textový editor a jakýkoli moderní webový prohlížeč.
Praktická částVšechny dále popsané demonstrační příklady mají shodnou strukturu. Každý příklad je tvořen dvojicí souborů: HTML stránkou a JavaScriptovým kódem. V HTML stránce je deklarováno kreslicí plátno (canvas), což je čtvercová či obdélníková plocha, do které je možné vykreslit 2D či 3D scénu. HTML stránka navíc obsahuje i značku pro načtení kódu v JavaScriptu, který nějakým způsobem provede vykreslení scény na plátno. HTML stránky všech příkladů jsou prakticky shodné a liší se pouze titulkem a taktéž jménem souboru, který obsahuje JavaScriptový kód (ten je schválně načten až poté, co je do dokumentu umístěno plátno):
<!doctype html>
<html>
<head>
<title>WebGL: step1</title>
</head>
<body>
<!-- kreslicí plátno -->
<canvas id="web_gl_canvas" width="256" height="256"></canvas>
<!-- skript, který kreslicí plátno využije -->
<script type="application/javascript" src="step1.js"></script>
</body>
</html>Všechny příklady by měly být spustitelné pouze otevřením příslušné HTML stránky v prohlížeči – nemělo by tedy být nutné spouštět lokální webový server.
Poznámka: není tajemstvím, že autor článku v žádném případě není fanoušek jazyka JavaScript. My z tohoto jazyka ovšem využijeme pouze samotné základy, takže nebude nutné bojovat s jeho prapodivným typovým systémem atd. V některém z navazujících článků si navíc ukážeme i zápis programů v Pythonu s (trans)překladem tohoto programu do JavaScriptu s využitím Transcryptu.
První krok: webová stránka s kreslicím plátnem (canvasem)Dnešní první demonstrační příklad bude velmi jednoduchý. Pouze v něm zjistíme, jakým způsobem se v JavaScriptu získá reference na objekt představující kreslicí plátno a jak se následně čtou atributy tohoto objektu. Na HTML stránce (viz předchozí kapitolu) má značka canvas přidělen identifikátor web_gl_canvas, takže získání reference na objekt je snadná. A přístup k atributům objektu (jejich čtení), konkrétně k jeho šířce a výšce, zajistí standardní operátor tečky:
// reference na objekt představující kreslicí plátno umístěné na webové stránce
const canvas = document.getElementById("web_gl_canvas");
// zobrazení základních informací o získaném objektu
window.alert("Canvas: " + canvas.width + "x" + canvas.height);Po otevření HTML stránky s příkladem (https://tisnik.github.io/temp/step1.html) by se měl otevřít dialog s informací o velikosti kreslicího plátna:
Obrázek 2: Rozměry kreslicího plátna vypsané dnešním prvním demonstračním příkladem.
Autor: tisnik, podle licence: Rights Managed
Kontext pro vykreslování: 2D a WebGLCanvas ve skutečnosti představuje „pouhou“ oblast umístěnou na plochu webové stránky. Samotné vykreslování je na tuto plochu prováděno přes takzvaný kontext. V současnosti lze využít dva kontexty: první z nich se používá pro vykreslování 2D grafiky (interaktivní mapy, grafy apod.), druhý pro 3D grafiku. Příslušný kontext je nejdříve nutné získat. Z pohledu JavaScriptu se jedná o další objekt, jehož reference se přečte metodou canvas.getContext, které se předá textový identifikátor kontextu. V případě, že vyžadovaný kontext ještě neexistuje, je vytvořen.
Pro tvorbu 2D grafiky se používá kontext získaný takto:
// reference na objekt představující kreslicí plátno umístěné na webové stránce
const canvas = document.getElementById("web_gl_canvas");
// vytvoření a získání kontextu 2D (2D grafika)
const ctx = canvas.getContext("2d");Naopak pro vizualizaci 3D scény použijeme kontext získaný následujícím způsobem:
// reference na objekt představující kreslicí plátno umístěné na webové stránce
const canvas = document.getElementById("web_gl_canvas");
// vytvoření a získání kontextu WebGL (3D grafika)
const gl = canvas.getContext("webgl");Druhý demonstrační příklad: využití kreslicího plátna s 2D kontextemV případě, že získáme objekt představující 2D kontext, je možné voláním metod tohoto objektu provádět vykreslování v rovině. V následujícím demonstračním příkladu je ukázán způsob vykreslení kružnice, která je definována formou cesty (path) tvořené jediným obloukem. Povšimněte si, jakým způsobem se nastavuje styl vykreslování přímou modifikací atributů objektu představujícího kontext (alternativně lze kružnici vykreslit jako elipsu s totožnou šířkou a výškou):
// reference na objekt představující kreslicí plátno umístěné na webové stránce
const canvas = document.getElementById("web_gl_canvas");
// vytvoření a získání kontextu 2D (2D grafika)
const ctx = canvas.getContext("2d");
// styl vykreslování
ctx.strokeStyle = "red"
ctx.lineWidth = 5
// cesta tvořená kružnicovým obloukem
ctx.beginPath()
ctx.arc(canvas.width/2, canvas.height/2, canvas.width/3, 0, 2 * Math.PI);
ctx.stroke();HTML stránka dnešního druhého demonstračního příkladu vypadá takto (u dalších příkladů ji již nebudeme uvádět):
<!doctype html>
<html>
<head>
<title>WebGL: step2</title>
</head>
<body>
<!-- kreslicí plátno -->
<canvas id="web_gl_canvas" width="256" height="256"></canvas>
<!-- skript, který kreslicí plátno využije -->
<script type="application/javascript" src="step2.js"></script>
</body>
</html>Po otevření webové stránky s příkladem by se do canvasu měla vykreslit červená kružnice:
Obrázek 3: Využití canvasu s kontextem "2D" pro kreslení 2D grafiky.
Autor: tisnik, podle licence: Rights Managed
Třetí demonstrační příklad: vymazání barvového bufferu pro kontext WebGLObjekt představující 3D kontext (WebGL) nabízí naprosto odlišné aplikační programové rozhraní než objekt s 2D kontextem, protože se zde namísto přímého umisťování 2D tvarů do plochy musí pracovat s programovatelnou vykreslovací pipeline. Ostatně si to můžeme ukázat na jednoduchém příkladu, ve kterém nejdříve získáme 3D kontext (WebGL) a následně vyplníme celý barvový buffer (color buffer – viz druhou kapitolu) nastavenou barvou:
// reference na objekt představující kreslicí plátno umístěné na webové stránce
const canvas = document.getElementById("web_gl_canvas");
// vytvoření a získání kontextu WebGL (3D grafika)
const gl = canvas.getContext("webgl");
// nastavení barvy pro vymazání barvového bufferu
gl.clearColor(0.8, 1.0, 0.8, 1);
// vymazání barvového bufferu
gl.clear(gl.COLOR_BUFFER_BIT);V případě, že webový prohlížeč podporuje WebGL, měla by plocha webové stránky vypadat následovně:
Obrázek 4: Vymazání barvového bufferu zvolenou barvou.
Autor: tisnik, podle licence: Rights Managed
Mimochodem – to, že referenci na kontext umisťuji do proměnné nazvané gl, není náhoda. Výsledný kód totiž do značné míry „kopíruje“ původní řešení v tradičním OpenGL (a v jazyce C), které by vypadalo zhruba takto:
glClearColor(0.8, 1.0, 0.8, 0.0); // nastaveni mazaci barvy na cernou glClear(GL_COLOR_BUFFER_BIT); // vymazani bitovych rovin barvoveho bufferuTransformace souřadnic: z normalizovaných souřadnic zařízení na souřadnice na kreslicím plátnu
Jednou z transformací, která se při vykreslování 3D scény provádí, je transformace z takzvaných normalizovaných souřadnic zařízení na souřadnice odpovídající pozicím pixelů na kreslicím plátnu (canvasu). Parametry této transformace jsou interně vypočteny automaticky na základě čtyř předaných hodnot popisujících takzvaný viewport:
Tyto souřadnice a rozměry se předávají metodě viewport. V našem konkrétním případě nastavíme šířku a výšku získanou z informací o rozměrech canvasu, tedy konkrétně atributy canvas.width a canvas.height:
// reference na objekt představující kreslicí plátno umístěné na webové stránce
const canvas = document.getElementById("web_gl_canvas");
// vytvoření a získání kontextu WebGL (3D grafika)
const gl = canvas.getContext("webgl");
// 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.8, 1.0, 0.8, 1);
// vymazání barvového bufferu
gl.clear(gl.COLOR_BUFFER_BIT);Vykreslená scéna by měla být naprosto shodná s předchozím příkladem, což ovšem není překvapivé, protože prozatím pouze mažeme obsah barvového bufferu:
Obrázek 5: Vymazání barvového bufferu zvolenou barvou + nastavení transformace.
Autor: tisnik, podle licence: Rights Managed
Registrace a překlad shaderůProzatím se mohlo zdát, že je vykreslování 2D i 3D scény prováděno prakticky totožným způsobem. Je tomu tak z toho důvodu, že jsme se nepokusili o vykreslení skutečných 3D primitiv. Abychom toho dosáhli, je nutné zaregistrovat a přeložit vertex i fragment shader. Pokusím se o co nejjednodušší variantu shaderů a i z tohoto důvodu bude vykreslovaná scéna značně primitivní – bude totiž obsahovat pouze jediný (červený) bod umístěný do počátku souřadné soustavy.
Vertex shader bude v takovém případě značně zjednodušen. Pouze v něm nastavíme souřadnice (bodu) tak, aby byl bod umístěn do počátku souřadné soustavy. Čtvrtá souřadnice 4D vektoru bude jedničková, protože souřadnice zadáváme v homogenní souřadné soustavě (ve stručnosti – hodnoty x, y a z jsou děleny poslední hodnotou w). A nastavíme i velikost bodu. Povšimněte si, že zápis shaderu v jazyce GLSL se do značné míry podobá céčku:
void main() {
gl_Position = vec4(0.0, 0.0, 0.0, 1);
gl_PointSize = 20.0;
}Poznámka: zejména v počítačové grafice, ale například i v geometrii, je zapotřebí nějakým způsobem rozlišit body a vektory. Řešení spočívá v použití výše zmíněných homogenních souřadnic, kdy místo třech souřadnic (x, y, z) používáme souřadnice čtyři (x, y, z, w). Čtvrtá souřadnice je nazývána váha – odtud plyne její označení symbolem w (weight). Pokud je váha nastavena na jedničku, tj. (x, y, z, 1), jedná se o zadání bodu, pokud je váha nastavena na nulu, tj. (x, y, z, 0) jedná se o vektor (představte si spíše paprsek). Jiné (nenulové) hodnoty váhy w se eliminují tak, že se provede transformace z (x, y, z, w) na (x/w, y/w, z/w, 1).
Fragment shader musí pouze nastavit barvu vykreslování (bodu). Ovšem typicky se ještě setkáme s tím, že je shader doplněn o řádek se specifikací použitého formátu hodnot. V našem konkrétním případě odpovídá mediump float formátu single resp. float32:
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1);
}Poznámka: gl_Position, gl_PointSize a gl_FragColor jsou atributy, které jsou součástí interního stavu vykreslovací pipeline.
Oba shadery je nutné zaregistrovat a přeložit. Samotné zdrojové kódy shaderů pro jednoduchost uložíme do řetězců:
const vertexShaderSource = `
void main() {
gl_Position = vec4(0.0, 0.0, 0, 1);
gl_PointSize = 20.0;
}
`
const fragmentShaderSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1);
}
`Registrace a překlad obou shaderů se provede takto:
// vytvoření a získání kontextu WebGL (3D grafika)
const gl = canvas.getContext("webgl");
// překlad vertex shaderu
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
// překlad fragment shaderu
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);Poznámka: prozatím nekontrolujeme výsledek prováděných operací, takže se v žádném případě nejedná o produkční kód! (o to čitelnější však celé řešení je) Podrobnosti o kontrole stavu celého WebGL si vysvětlíme příště.
Vykreslení jediného bodu na plátno v kontextu WebGLV dalším kroku celou vykreslovací pipeline zkonstruujeme, připojíme do ní oba shadery a spustíme ji (opět: v klasickém OpenGL žádná obdoba tohoto postupu není vyžadována):
// registrace obou shaderů const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); // spuštění programu gl.useProgram(program);
Konečně následuje poslední řádek. Ten do vykreslovací pipeline pošle příkaz pro vykreslení jediného bodu:
// vykreslení jednoho bodu (nepředáváme souřadnice) gl.drawArrays(gl.POINTS, 0, 1);
Vykreslovací pipeline zavolá oba shadery, které (zjednodušeně řečeno) provedou tyto tři operace:
gl_Position = vec4(0.0, 0.0, 0, 1); gl_PointSize = 20.0; gl_FragColor = vec4(1, 0, 0, 1);
Díky tomu, že je známa poslední transformace definovaná přes gl.viewport, bude bod vykreslen přesně doprostřed canvasu. Bod bude mít červenou barvu a rozměry přibližně 20×20 pixelů (ovšem různé GPU mohou provádět antialiasing atd.):
Obrázek 6: Vykreslení bodu do počátku souřadného systému.
Autor: tisnik, podle licence: Rights Managed
Úplný zdrojový kód pátého demonstračního příkladuNásleduje výpis úplného zdrojového kódu dnešního pátého demonstračního příkladu, který by po svém spuštění měl vykreslit jediný bod doprostřed plochy canvasu:
const vertexShaderSource = `
void main() {
gl_Position = vec4(0.0, 0.0, 0, 1);
gl_PointSize = 20.0;
}
`
const fragmentShaderSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1);
}
`
// reference na objekt představující kreslicí plátno umístěné na webové stránce
const canvas = document.getElementById("web_gl_canvas");
// vytvoření a získání kontextu WebGL (3D grafika)
const gl = canvas.getContext("webgl");
// překlad vertex shaderu
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
// překlad fragment shaderu
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
// registrace obou shaderů
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
// transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
gl.viewport(0, 0, canvas.width, canvas.height);
// nastavení barvy pro vymazání barvového bufferu
gl.clearColor(0, 0, 0, 1);
// vymazání barvového bufferu
gl.clear(gl.COLOR_BUFFER_BIT);
// spuštění programu
gl.useProgram(program);
// vykreslení jednoho bodu (nepředáváme souřadnice)
gl.drawArrays(gl.POINTS, 0, 1);Vertex shader, kterému se předávají 2D souřadnice grafických entitPrvní verze vertex shaderu, kterou jsme si ukázali v předchozím textu, pouze nastavovala souřadnice vykreslovaného bodu na hodnotu [0, 0, 0, 1]. V praxi však pochopitelně budeme vyžadovat vykreslení bodů na jakémkoli místě v rovině či prostoru. Omezme se prozatím na rovinu, tj. na situaci, kdy budeme potřebovat nastavit interní atribut vykreslovací pipeline gl_Position nikoli na hodnotu [0, 0, 0, 1] ale na [x, y, 0, 1], kde x a y jsou hodnoty nějakým způsobem předané do vykreslovací pipeline. To lze pochopitelně zařídit, a to následovně:
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0, 1);
gl_PointSize = 20.0;
}Poznámka: povšimněte si, že jsme vlastně nespecifikovali parametry funkce main, ale atribut nazvaný position, který je typu dvourozměrný vektor vec2. Obě souřadnice uložené v tomto vektoru se předají do konstruktoru vec4, který k nim přidá další dvě souřadnice z a w nastavené na hodnotu 0,0 resp. 1,0.
Vykreslení bodu s určením jeho souřadnic ve scéněNyní následuje poslední neznámý krok, který musíme vyřešit – jak se vlastně předávají souřadnice (v našem případě bodů) do vykreslovací pipeline? Začneme definicí souřadnice bodu, který se má vykreslit. Souřadnice by měly ležet v rozsahu –1,0 až 1,0 (viz nastavení viewportu):
// souřadnice bodu
vertexes = [
// x y
0.9, 0.9
];Zatím je to snadné, ovšem souřadnice i další data se předávají přes takzvané buffery, které je nutné zkonstruovat a zapsat do nich data ve specifikovaném formátu:
// buffer, do kterého se překopírují souřadnice bodu const buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexes), gl.STATIC_DRAW);
Poznámka: souřadnice jsou konstruktorem Float32Array převedeny z obecného seznamu na pole obsahující hodnoty typu float32.
To ovšem nestačí, protože musíme říci, kterému atributu vertex či fragment shaderu vlastně předávaná data odpovídají. V našem vertex shaderu jsme specifikovali atribut nazvaný position:
// navázání na parametr předávaný do vertex shaderu const p = gl.getAttribLocation(program, "position");
Poslání obsah bufferu do vykreslovací pipeline se provede takto (význam předávaných parametrů je popsán v komentářích):
// seznam předávaný do vertex shaderu
gl.vertexAttribPointer(p,
2, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
2*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
0 // offset
);
gl.enableVertexAttribArray(p);Poznámka: v klasickém OpenGL bychom si namísto toho vystačili s jediným řádkem glVertex2f(x, y).
Zopakujme si tedy celý postup:
Úplný zdrojový kód příkladu, který vykreslení bodu provede, vypadá následovně:
const vertexShaderSource = `
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0, 1);
gl_PointSize = 20.0;
}
`
const fragmentShaderSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1);
}
`
// reference na objekt představující kreslicí plátno umístěné na webové stránce
const canvas = document.getElementById("web_gl_canvas");
// vytvoření a získání kontextu WebGL (3D grafika)
const gl = canvas.getContext("webgl");
// překlad vertex shaderu
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
// překlad fragment shaderu
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
// registrace obou shaderů
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
// souřadnice bodu
vertexes = [
// x y
0.9, 0.9
];
// buffer, do kterého se překopírují souřadnice bodu
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexes), gl.STATIC_DRAW);
// navázání na parametr předávaný do vertex shaderu
const p = gl.getAttribLocation(program, "position");
// seznam předávaný do vertex shaderu
gl.vertexAttribPointer(p,
2, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
2*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
0 // offset
);
gl.enableVertexAttribArray(p);
// transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
gl.viewport(0, 0, canvas.width, canvas.height);
// nastavení barvy pro vymazání barvového bufferu
gl.clearColor(0, 0, 0, 1);
// vymazání barvového bufferu
gl.clear(gl.COLOR_BUFFER_BIT);
// spuštění programu
gl.useProgram(program);
// vykreslení jednoho bodu s předáním souřadnic
gl.drawArrays(gl.POINTS, 0, 1);Výsledkem by měla být tato scéna vykreslená do plochy webové stránky:
Obrázek 7: Vykreslení bodu na zvolených souřadnicích.
Autor: tisnik, podle licence: Rights Managed
Vykreslení většího množství bodů do scényNepatrnou úpravou zdrojového kódu lze docílit vykreslení většího množství bodů. Nejdříve je pochopitelně nutné definovat souřadnice těchto bodů:
// souřadnice bodů
vertexes = [
// x y
0.0, 0.0,
0.9, 0.9,
0.9, -0.9,
-0.9, 0.9,
-0.9, -0.9,
];Další změna spočívá v úpravě druhého parametru předávaného do metody gl.drawArrays. Musíme určit, že se bude předávat deset hodnot (pět dvourozměrných souřadnic):
// vykreslení pěti bodů s předáním souřadnic gl.drawArrays(gl.POINTS, 0, 5);
To je vše – výsledek by měl vypadat následovně:
Obrázek 8: Vykreslení pětice bodů na zvolených souřadnicích.
Autor: tisnik, podle licence: Rights Managed
Úplný zdrojový kód sedmého a současně i posledního demonstračního příkladu, který jsme si dnes popsali, vypadá následovně:
const vertexShaderSource = `
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0, 1);
gl_PointSize = 20.0;
}
`
const fragmentShaderSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1);
}
`
// reference na objekt představující kreslicí plátno umístěné na webové stránce
const canvas = document.getElementById("web_gl_canvas");
// vytvoření a získání kontextu WebGL (3D grafika)
const gl = canvas.getContext("webgl");
// překlad vertex shaderu
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
// překlad fragment shaderu
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
// registrace obou shaderů
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
// souřadnice bodů
vertexes = [
// x y
0.0, 0.0,
0.9, 0.9,
0.9, -0.9,
-0.9, 0.9,
-0.9, -0.9,
];
// buffer, do kterého se překopírují souřadnice bodu
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexes), gl.STATIC_DRAW);
// navázání na parametr předávaný do vertex shaderu
const p = gl.getAttribLocation(program, "position");
// seznam předávaný do vertex shaderu
gl.vertexAttribPointer(p,
2, // počet prvků v každém atributu
gl.FLOAT, // typ prvků
gl.FALSE, // data nejsou normalizována
2*Float32Array.BYTES_PER_ELEMENT, // velikost vertexu v bajtech
0 // offset
);
gl.enableVertexAttribArray(p);
// transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu
gl.viewport(0, 0, canvas.width, canvas.height);
// nastavení barvy pro vymazání barvového bufferu
gl.clearColor(0, 0, 0, 1);
// vymazání barvového bufferu
gl.clear(gl.COLOR_BUFFER_BIT);
// spuštění programu
gl.useProgram(program);
// vykreslení pěti bodů s předáním souřadnic
gl.drawArrays(gl.POINTS, 0, 5);Repositář s demonstračními příkladyVšechny demonstrační příklady, které jsme si v dnešním článku ukázali, je možné spustit přímo z odkazů uvedených v tabulce pod tímto odstavcem. Doplněny jsou i odkazy na zdrojové kódy psané v JavaScriptu:
| # | 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 |
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | 3d в MonoGame, python в c# или всё таки KNI в браузере? трёхслойная постирония о том, как я игру делал | 0 | 10.42 | 09-03-2026 |
| 2 | Hravá Květná zahrada – program pro rodiny s dětmi | 0 | 6.15 | 23-06-2026 |
| 3 | Brněnská nezávislá divadla – scéna plná odvahy, experimentu a překvapení | 6 | 7 | 01-07-2026 |
| 4 | Углеродное волокно открывает новые возможности в создании лёгких и прочных ... | 0 | 5.93 | 06-08-2026 |
| 5 | Yandex B2B Tech запустила Vibecraft – сервис для создания сайтов и веб-приложений по текстовому описанию | 0 | 5 | 25-06-2026 |
| 6 | 🚀 3D-МОДЕЛИРОВАНИЕ И АДДИТИВНЫЕ ТЕХНОЛОГИИ (1–4 КЛАСС) 🖨️ Друзья, представляем ... | 0 | 9.3 | 01-09-2026 |
| 7 | Ученые РТУ МИРЭА нашли способ печатать керамические детали для СВЧ-техники на 3D-принтере | 0 | 12.31 | 28-08-2026 |
| 8 | Как самые технологичные компании используют 3D-печать? SpaceX применяет аддитивные технологии ... | 5 | 7 | 21-07-2026 |
| 9 | I Created a Game Engine for Django? | 0 | 11.1 | 24-01-2026 |