Elm a WebGL

V předchozích povídáních jsem ukazoval, jak Elm aplikaci přimět spolupracovat s JavaScriptem. Nyní si naopak ukážeme, jak to vypadá, když má Elm nějakou pěknou funkcionalitu v sobě zabudovanou. Konkrétně se zaměříme na práci s WebGL.

Jak funguje WebGL

Jak funguje WebGL vysvětlil před lety Ondřej Žára v seriálu Začínáme s WebGL. Hned v úvodu prvního dílu se dočtete, že:

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…

Používat přímo javascriptové rozhraní WebGL je zřejmě určitý druh masochismu. Zmiňovaná knihovna three.js bude asi dost dobrá. Všiml jsem si, že ji používá mimo jiné Sketchup a Freecad pro export svých 3D modelů do WebGL. Mohli bychom ji použít i v Elm aplikaci. Ano, museli bychom psát kód v JavaScriptu a s Elm aplikací bychom komunikovali přes porty, podobně jako jsme to dělali s mapovým API. Tak takhle to dělat nebudeme, protože to není pěkné a hlavně to dělat vůbec nemusíme.

elm-webgl

Implementace WebGL v Elmu je překvapivě velice stručná. Balíček elm-webgl je sestaven z jediného modulu a jediného nativního modulu. Stojí za to krátce se na ten zdrojový kód podívat. Modul WebGL.elm příliš zajimavý není. Je to vlastně jen rozhraní k nativnímu modulu. Všimněte si, jak je zdrojový kód v Elmu čitelný, jak sám sebe přehledně dokumentuje. To je příjemná vlastnost funkcionálních jazyků.

Nahlédněme do nativního modulu Native/WebGL.js. Právě zde je veškerá rutina, kterou vysvětluje Ondřej Žára v prvním díle svého seriálu o WebGL (Odkazovaný článek je důležitý pro můj další výklad, bez jeho přečtení vám nemusí být úplně zřejmé, o čem je v dalším textu řeč!) Pokud bychom chtěli přepsat příklad z toho článku do Elmu, tak můžeme vynechat celou část, která vytváří ze shaderů program:

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);

a stejně tak můžeme vynechat veškerou práci s buffery.

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);

Jasně, tohle je většinou to první, co každá knihovna pro WebGL nějak obalí a zpřístupní lepším způsobem. Elm, respektive balíček elm-webgl to dělá podobně.

Tak, a co zbylo? Přesně to, co Ondřej Žára píše ve zmiňovaném článku:

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

Výborně. Nejprve si tedy nainstalujeme potřebné balíčky. Kromě balíčku elm-webgl si přidáme ještě balíček elm-linear-algebra pro práci s vektory.

$ elm package install elm-community/elm-webgl
$ elm package install elm-community/elm-linear-algebra

A nyní splníme ty tři věci, které WebGl potřebuje pro vykreselní.

module Main exposing (main)

import Math.Vector3 as Vec3 exposing (Vec3)
import Html as H exposing (Html)
import Html.Attributes as HA
import WebGL


{-
   Ve funkci main rovnou vytvoříme html element canvas a v něm pomocí WebGL vykreslíme náš bod

   Funkce WebGL.toHtml http://package.elm-lang.org/packages/elm-community/elm-webgl/3.0.3/WebGL#toHtml vytvoří scénu,
   předáme jí dva parametry
   1) pole html atributů, zde nastavíme parametry pro element canvas, takže po kompilaci budeme mít <canvas width="400" height="300">
   2) pole renderovatelných komponent, zde máme jednu komponentu,
      vytvoříme ji tak, že funkci WebGL.render http://package.elm-lang.org/packages/elm-community/elm-webgl/3.0.3/WebGL#render
      předáme čtyři  parametry: vertex shader a fragment shader a data k renderování.
      Čtvrtý parametr zatím budeme ignorovat a předáme prázdný záznam.
-}


main : Html msg
main =
    WebGL.toHtml
        [ HA.width 400
        , HA.height 300
        ]
        [ WebGL.render vertexShader fragmentShader myPoint {} ]



{-
   Když se podíváte na Ondrův první příklad, tak zjistíte, že bod k vykreslení
   zadává přímo jako pole
   new Float32Array([0.5, 0.5])
   a na jiném místě deklaruje, že bod (vrchol) bude v shaderu přiřazen do parametru pos
   gl.getAttribLocation(program, "pos")
   (je kolem toho spousta další práce, kterou elm-webgl udělá za nás)

   Elm je staticky typovaný, a tak si předem nadefinujeme typ vrcholu.
   Vrchol bude záznam s jedním atributem pos.
   V tomto atributu budou souřadnice vrcholu, namísto javascriptového typu *Float32Array*
   použijeme vektor z knihovny Math.Vector3.

   **Použil jsem zrovna trojrozměrný vektor, tedy s třetí souřadnicí pro osu z.
   Ondra používá dvojrozměrné pole
   a třetí souřadnici, kterou zatím nepotřebuje, nastavuje až když je to potřeba ve vertex shaderu jako hodnotu 0.**
-}


type alias Vertex =
    { pos : Vec3 }



{-
   Tak a tady máme bod, který budeme chtít vykreslit
-}


myPoint : WebGL.Drawable Vertex
myPoint =
    WebGL.Points
        [ { pos = Vec3.vec3 0.5 0.5 0 }
        ]



{-
   Co je vertex shader je v článku Ondřeje Žáry https://www.zdrojak.cz/clanky/vytvarime-hello-world-pro-webgl

   glsl kód nepíšeme jako řetězec, ale jako shader blok,
   tedy glsl kód zapíšeme sem [glsl| /*tady bude glsl kód*/ |]
   je to přehlednější než předávání pomocí řetězce v javascriptu
   je to asi stejně přehledné jako psaní glsl kódu do html elementu <script id="vs" type="x-shader/x-vertex">...</script>,
   ale odpadá jeho načítání pomocí document.querySelector("#vs").textContent


-}


vertexShader : WebGL.Shader Vertex {} {}
vertexShader =
    [glsl|
  precision mediump float;
  attribute vec3 pos;
  void main() {
    gl_Position = vec4(pos, 1);
    gl_PointSize = 5.0;
  }
|]



{-
   Co je fragment shader, je vysvětleno opět v článku Ondřeje Žáry https://www.zdrojak.cz/clanky/vytvarime-hello-world-pro-webgl

   Zápis je podobný jako u vertex shaderu.
-}


fragmentShader : WebGL.Shader {} {} {}
fragmentShader =
    [glsl|
  precision mediump float;
  void main() {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
  }
|]

Téměř identický příklad najdete na githubu elm-webgl v examples firstrender_bug.elm, bylo v podstatě jen třeba vyměnit trojúhelník za bod, což obnáší změnit asi dva řádky v kódu.

Oproti knihovně three.js je balíček elm-webgl nízkorúrovňová knihovna, pracujeme přímo se shadery a musíme také psát kód v jazyce glsl. V podstatě to je pouze implementace javascriptového WebGL API do Elmu. Nad elm-webgl si můžete napsat funkce pro kreselní různých těles, tak jak to umí třeba knihovna three.js a další podobné projekty.

Elm mne překvapil ještě jednou věcí. Kompilátor jazyka Elm umí odhalit chyby i v jazyce glsl. Vrátím se k tomu v příštím díle.

Jsem programátor na volné noze. Věnuji se především zpracování a prezentaci dat. Zajímám se o programovací jazyky. Začínal jsem s jazykem C/C++ a PHP pro web. Pak jsem se nadchnul pro Python. Když přišel Node.js, začal jsem věnovat více své pozornosti JavaScriptu. Nyní mne zajímají především jazyky Rust a Elm. Krom toho opravuji s přáteli zchátralý dětský tábor v Karpatech (nejen) z hlíny a slámy. A dělám spoustu dalších věcí, kvůli kterým mi na programování nezbývá čas.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 2

Přehled komentářů

uetoyo Dík za článek
Petr Bolf Re: Dík za článek
Zdroj: https://www.zdrojak.cz/?p=19005