Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

WebGL: tvorba 3D grafiky s využitím programovatelné vykreslovací pipeline

Дата публикации: 21-09-2026 22:00:05

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 OpenGL

V 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:

OpenGL pipeline

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:

  1. Geometrická a rastrová data jsou zpracovávána odděleně odlišnými jednotkami, které tak mohou pracovat paralelně. Při zpracování rastrových dat jsou výpočetní jednotky celkem jednoduché a proto jich může být implementováno více.
  2. Moduly zobrazené na předchozím obrázku mohou pracovat nezávisle na sobě a jsou řízeny pouze daty. Proto lze vytvořit zřetězené zpracování dat (pipeline) s velkým počtem řezů, kde jednotlivé moduly představují řezy pipeline.
  3. Uvnitř modulů se provádí operace, které lze rozložit na velké množství jednodušších operací, a ty lze opět vykonávat zřetězeně. Například násobení matice vektorem lze rozložit na jednotlivá násobení a sčítání prvků matice a vektoru, které se provádí za sebou. V jednom modulu může tato pipeline obsahovat i stovky řezů.
  4. Tok většiny dat (zejména geometrických) je jednosměrný, takže se používá technika streamingu a zmenší se tok dat přenesených mezi grafickým čipem a pamětí na grafickém akcelerátoru.

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:

  1. Vykreslování bitmap (jednobarevných rastrových obrázků). Bitmapy se používají zejména pro výpis řetězců resp. jednotlivých znaků. Vzhledem k tomu, že u bitmap je pro každý pixel rezervovaný pouze jeden bit, je nutné před vlastním vykreslováním příkazem glColor*() zadat barvu bitmapy.
  2. Vykreslování rastrových obrázků s vyšší barevnou hloubkou. V terminologii OpenGL se tyto obrázky nazývají pixmapy. Pixmapy mohou být zadány v několika barevných modelech a jsou podporovány různé barevné hloubky, aby bylo možné použít různé typy rastrových obrázků bez nutnosti programové konverze.
  3. Pokrytí povrchu těles texturou. Textury se obecně po jejich zadání nikam přímo nevykreslují, ale zůstávají v texturovací paměti. Odtud je lze použít při nanášení na povrch těles. V ideálním případě je texturovací paměť umístěna přímo na grafickém akcelerátoru, ale je možné, že při větším množství velkých textur se tyto umístí do hlavní paměti počítače.
Per-vertex operation and primitive assembly

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 operations

Operace 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.

Rasterization

Grafická 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 assembly

V 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 operations

Pokud 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.

Framebuffer

Vý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 ES

WebGL 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čích

Samotná 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á část

Vš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/tem­p/step1.html) by se měl otevřít dialog s informací o velikosti kreslicího plátna:

Rozměry kreslicího plátna získané po otevření webové stránky.

 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 WebGL

Canvas 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 kontextem

V 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:

Využití canvasu s kontextem "2D" pro kreslení 2D grafiky.

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 WebGL

Objekt 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ě:

Vymazání barvového bufferu zvolenou barvou.

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 bufferu
Transformace 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:

  1. Horizontální souřadnice levého horního rohu
  2. Vertikální souřadnice levého horního rohu
  3. Šířka
  4. Výška

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:

Vymazání barvového bufferu zvolenou barvou + nastavení transformace.

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 WebGL

V 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.):

Vykreslení bodu do počátku souřadného systému.

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říkladu

Ná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 entit

První 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:

  1. Vložení canvasu do plochy webové stránky
  2. Získání objektu představujícího canvas
  3. Získání kontextu pro WebGL
  4. Překlad a registrace vertex shaderu
  5. Překlad a registrace fragment shaderu
  6. Vytvoření a spuštění naprogramované pipeline
  7. Konstrukce bufferu s vložením souřadnic bodu (bodů) do tohoto bufferu
  8. Poslání obsahu bufferu do pipeline
  9. Vymazání barvového bufferu
  10. Vykreslení bodu (bodů) příkazem gl.drawArrays(gl.POINTS, …, …)

Ú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:

Vykreslení bodu na zvolených souřadnicích.

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ény

Nepatrnou ú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ě:

Školení Zabbix

Vykreslení pětice bodů na zvolených souřadnicích.

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říklady

Vš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/tem­p/step1.html
2 step1.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step1.js
     
3 step2.html využití kreslicího plátna s 2D kontextem https://tisnik.github.io/tem­p/step2.html
4 step2.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step2.js
     
5 step3.html vymazání barvového bufferu pro kontext WebGL https://tisnik.github.io/tem­p/step3.html
6 step3.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step3.js
     
7 step4.html transformace souřadnic: z normalizovaných souřadnic na souřadnice na plátnu https://tisnik.github.io/tem­p/step4.html
8 step4.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step4.js
     
9 step5.html vykreslení jediného bodu na plátno v kontextu WebGL https://tisnik.github.io/tem­p/step5.html
10 step5.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step5.js
     
11 step6.html vykreslení bodu s určením jeho souřadnic ve scéně https://tisnik.github.io/tem­p/step6.html
12 step6.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step6.js
     
13 step7.html vykreslení většího množství bodů do scény https://tisnik.github.io/tem­p/step7.html
14 step7.js skript s implementací tohoto příkladu https://tisnik.github.io/tem­p/step7.js
Seriály a články o OpenGL i přidružených technologiích
  1. Seriál Grafická knihovna OpenGL
    https://www.root.cz/serialy/graficka-knihovna-opengl/
  2. Seriál OpenGL a nadstavbová knihovna GLU
    https://www.root.cz/serialy/opengl-a-nadstavbova-knihovna-glu/
  3. Seriál OpenGL evaluátory
    https://www.root.cz/serialy/opengl-evaluatory/
  4. Seriál OpenGL Imaging Subset
    https://www.root.cz/serialy/opengl-imaging-subset/
  5. Seriál Tvorba přenositelných grafických aplikací využívajících knihovnu GLUT
    https://www.root.cz/serialy/tvorba-prenositelnych-grafickych-aplikaci-vyuzivajicich-knihovnu-glut/
  6. Stručný přehled grafických knihoven pro Raspberry Pi
    https://www.root.cz/clanky/strucny-prehled-grafickych-knihoven-pro-raspberry-pi/
  7. Práce s rastrovými obrázky v knihovně OpenVG
    https://www.root.cz/clanky/prace-s-rastrovymi-obrazky-v-knihovne-openvg/
  8. Práce s rastrovými obrázky v knihovně OpenVG (pokračování)
    https://www.root.cz/clanky/prace-s-nbsp-rastrovymi-obrazky-v-nbsp-knihovne-openvg-pokracovani/
  9. Práce s rastrovými obrázky v knihovně OpenVG (okomentované demonstrační příklady)
    https://www.root.cz/clanky/prace-s-rastrovymi-obrazky-v-knihovne-openvg-okomentovane-demonstracni-priklady/
  10. Použití knihoven OpenVG a EGL (nejenom) na Raspberry Pi
    https://www.root.cz/clanky/pouziti-knihoven-openvg-a-egl-nejenom-na-raspberry-pi/
Odkazy na Internetu
  1. WebGL: 3D Graphics for the Web
    https://www.khronos.org/webgl/
  2. OpenGL Shading Language
    https://en.wikipedia.org/wi­ki/OpenGL_Shading_Language
  3. WebGL (Wikipedia)
    https://en.wikipedia.org/wiki/WebGL
  4. WebGL
    https://wikis.khronos.org/web­gl/Main_Page
  5. OpenGL ES (Wikipedia)
    https://en.wikipedia.org/wi­ki/OpenGL_ES
  6. Float32Array
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Re­ference/Global_Objects/Flo­at32Array
  7. ECMAScript compatibility table
    https://compat-table.github.io/compat-table/es6/
  8. Learn-OpenGLES-Tutorials
    https://github.com/learnopengles/Learn-OpenGLES-Tutorials
  9. Implement OpenGL ES and EGL
    https://source.android.com/doc­s/core/graphics/implement-opengl-es
  10. Khronos Group
    https://www.khronos.org/
  11. Khronos Group (Wikipedia)
    https://en.wikipedia.org/wi­ki/Khronos_Group
  12. Raspberry Pi VideoCore APIs
    http://elinux.org/Raspberry_Pi_Vi­deoCore_APIs
  13. EGL quick reference card
    https://www.khronos.org/files/egl-1–4-quick-reference-card.pdf
  14. EGL Reference Pages Index
    https://www.khronos.org/re­gistry/egl/sdk/docs/man/html/in­dexflat.php
  15. GLSL
    https://glsl.app/
  16. Shader Tutorials by Ronja
    https://www.ronja-tutorials.com/
  17. CanvasRenderingContext2D
    https://developer.mozilla.org/en-US/docs/Web/API/CanvasRen­deringContext2D
  18. WebGL2RenderingContext
    https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext
  19. WebGLRenderingContext
    https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
13d в MonoGame, python в c# или всё таки KNI в браузере? трёхслойная постирония о том, как я игру делал010.4209-03-2026
2Hravá Květná zahrada – program pro rodiny s dětmi06.1523-06-2026
3Brněnská nezávislá divadla – scéna plná odvahy, experimentu a překvapení6701-07-2026
4Углеродное волокно открывает новые возможности в создании лёгких и прочных ...05.9306-08-2026
5Yandex B2B Tech запустила Vibecraft – сервис для создания сайтов и веб-приложений по текстовому описанию0525-06-2026
6🚀 3D-МОДЕЛИРОВАНИЕ И АДДИТИВНЫЕ ТЕХНОЛОГИИ (1–4 КЛАСС) 🖨️ Друзья, представляем ...09.301-09-2026
7Ученые РТУ МИРЭА нашли способ печатать керамические детали для СВЧ-техники на 3D-принтере012.3128-08-2026
8Как самые технологичные компании используют 3D-печать? SpaceX применяет аддитивные технологии ...5721-07-2026
9I Created a Game Engine for Django?011.124-01-2026

Классификация: Пресс-релизы. Схожих патентов: 0. Схожих новостей: 9. Тональность: 0. Информативность: 6.48. Источник: www.root.cz.