Proceduralne podejście do generowania tekstur
Transkrypt
Proceduralne podejście do generowania tekstur
Proceduralne podejście do generowania
tekstur
Paweł Sabat
Praca zamieszczona na http://noni.hswro.org
Wersja bez obrazków na końcu
Praca inżynierska
Promotor: dr Przemysław Kobylański
Wydział Podstawowych Problemów Techniki
Politechnika Wrocławska
Wrocław 2014
1
Wstęp
Celem projektu inżynierskiego było zaimplementowanie przykładowych technik tekstur proceduralnych przy użyciu jednej z najnowszych technologii
internetowych - WebGL 1.0. Obowiązkowym elementem pracy jest implementacja wybranych technik, oraz zaprezentowanie ich. Ważnym elementem
jest też możliwość modyfikowania parametrów i oglądania wyników w czasie
rzeczywistym.
Znaczna część technik i informacji o nich jest zaczerpnięta z książki ”Texturing & Modeling. A procedural approach” [1].
Kod bazowej funkcji - noise - został zaczerpnięty ze strony https://github.com/ashima/webglnoise [NOISE].
Pełny spis pozycji które wykorzystywano przy pisaniu pracy znajduje się
w dziale Literatura.
Praca składa się z następujących rozdziałów
1. Wstęp - opis celu projektu inżynierskiego
2. Wyjaśnienie pojęć - wprowadzanie i wyjaśnienie niezbędnych pojęć
3. Wstęp do tekstur proceduralnych - wyjaśnienie czym są tekstury proceduralne, gdzie się je używa i z jakich powodów
4. Użyte technologie - ogólny opis użytych technologii, oraz podrozdział
przybliżający temat WebGL 1.0
5. Wymagania, uruchamianie programu, różnice między przeglądarkami
6. Szkielet programu - ogólny opis kodu bazowego, współdzielonego przez
wszystkie strony
7. Noise - opis niezbędnej funkcji będącej podstawą wszystkich zaimplementowanych efektów
8. Zaimplementowane techniki - wyjaśnienie i opis
9. Ograniczenia technologii
10. Podsumowanie
Literatura
2
Wyjaśnienie pojęć
• model
Komputerowa reprezentacja obiektu.
1
• tekstura
Obraz nakładany na model w celu nadania mu bardziej realnego wyglądu przy jedoczesnym niskim nakładzie obliczeniowym.
• tekstura proceduralna
Tekstura wygenerowana przez komputer, a nie stworzona przez człowieka. Jako tekstury proceduralne nie są zaliczane też procedury malowane w programach graficznych.
• API
Application Programming Interface - zestaw interfejsów do wykonywania określnego zadania.
• OpenGL
API do renderowania grafiki 3D.
• OpenGl ES
API do renderowania grafiki 3D na urządzeniach mobilnych. Okrojone
w stosunku do OpenGL.
• WebGL
API do renderowania grafiki 3D dostępne z poziomu przeglądarki internetowej za pomocą javascript.
• Shader
Program uruchamiany na karcie graficznej służący do przetwarzania
grafiki 3D i generowania obrazu.
• GLSL
Język programowania w którym pisane są shadery.
• Vertex
Punkt w przestrzeni i powiązane z nim atrybuty np. jego pozycja czy
kolor.
• Vertex shader
Shader, który odpowiada za przetwarzanie vertexów np. zmienianie
ich pozycji przez przesunięcie czy obrót.
• Fragment
Dane, które mogą posłużyć do wygenerowania pixela.
• Fragment shader
Shader, który odpowiada za przetworzenie fragmentów, w szczególności obliczenie koloru możliwego pixela.
2
• Pixel
Najmniejszy wyświetlany fragment obrazu. Zlepek słów od ang. picture element.
• Noise
Funkcja, która w wersji idealnej powinna spełniać następujące kryteria:
– dla tego samego argumentu da to samo wyjście
– jej wartość jest w przedziale -1 do 1
– ma ograniczoną częstotliwość z dołu i z góry, z czego maksymalna
częstotliwość to ok. 1
– nie posiada oczywistych regularności ani krótkiego okresu
– jest stacjonarna, czyli jej statystyczne właściwości nie zmieniają
się po translacji
– jest izotropiczna, czyli jej statystyczne właściwości nie zmieniają
się przy rotacji
3
Wstęp do tekstur proceduralnych
3.1
Czym są?
Tekstury proceduralne to tekstury wygenerowane przez komputer. Za tekstury proceduralne uważa się np. wartości funkcji przekształcone do postaci
możliwej do użycia jako kolor pixela, ale już np. nie będzie teksturą proceduralną obraz malowany w photoshop’ie mimo że całkowicie zostaje stworzony
przez oprogramowanie. Jest kwestią dyskusyjną, czy obraz stworzony przez
np. ręczne nakładanie na siebie tekstur proceduralnych w celu uzyskania
pożądanego efektu jest też teksturą proceduralną.
3.2
Jakie są zalety i wady tekstur proceduralnych?
Tekstury proceduralne mają następujące zalety:
• są bardzo małe w porównaniu z obrazami zapisanymi w plikach
• potrafią generować bardzo skomplikowane wzory
• można łatwo kontrolować ich poziom szczegółów przy powiększaniu/zmniejszaniu
Tekstury proceduralne mają następujące wady:
• ciężko jest stworzyć teksturę z pożądanym efektem
3
• ciężkie lub niemożliwe jest kontrolowanie pojawiających się szczegółów
• wymagają sporej wiedzy nt. programowania, grafiki, obycia i dużej
wyobraźni
3.3
Jak się tworzy tekstury proceduralne
Tekstury proceduralne tworzy się przez napisanie odpowiedniego kodu, który
wygeneruje obraz. Ponieważ wyniki takiego programu często są zaskakujące
i nieprzewidziane, tworzenie tekstur proceduralnych to w dużym stopniu
wprowadzanie możliwych modyfikacji do już istniejącego kodu i sprawdzanie
wyników już w postaci wygenerowanego obrazu. Jest to więc bardziej sztuka
niż nauka ścisła, w której można zawsze z góry przewidzieć jaki obraz się
wygeneruje.
4
Użyte technologie
Praca wykorzystuje technologie:
• HTML5
• JavaScript
• WebGL 1.0, GLSL ES 1
HTML5 oraz JavaScript to trzon wszystkich współczesnych aplikacji webowych. Służą one do prezentowania treści (HTML5) oraz do tworzenia
bardziej interaktywnych i dynamicznych stron (JavaScript). Ponieważ technologie te są powszechnie znane, pomija się ich dokładniejszy opis.
4.1
WebGL 1.0, GLSL ES 1
WebGL 1.0 to opublikowane po raz pierwszy w 2011 roku [WEBGL100] API
do renderowania grafiki 3D zaprojektowane do użycia w sieci. WebGL 1.0
jest oparte na OpenGL ES 2.0, stąd posiada bardzo podobne możliwości,
z tą różnicą że WebGL jest przeznaczone do użycia z elementem HTML5
Canvas [WEBGL10].
Dostęp do API WebGL jest uzyskiwane przez kontekst pobrany z elementu canvas [WEBGL10, HTML5].
1
2
var canvas = document . getElementById ( ’ s o m e c a n v a s i d ’ )
var c o n t e x t = canvas . g e t C o n t e x t ( ’ webgl ’ )
Po pobraniu kontekstu można zacząć programować pod WebGL.
WebGL jest jedynie narzędziem pośredniczącym. Odpowiada ono za ustawienie rzeczy pod renderowanie grafiki 3D (stworzenie buforów, dostarczenie
danych) oraz za wywołanie funkcji, w wyniku której zostanie wyświetlony
4
obraz. Kod wywołujący funkcje WebGL to JavaScript, zatem jest wykonywany po stronie procesora.
Z drugiej strony są shadery. Są to małe programy, dla WebGL 1.0 pisane
w języku OpenGL ES Shading Language 1 (w skrócie GLSL ES 1) i odpalane
na karcie graficznej. To one odpowiadają za przetworzenie dostarczonych
danych i ich renderowanie.
Aby zrozumieć, jak te technologie współdziałają ze sobą, należy zapoznać się z przepływ danych w WebGL. Został on przejrzyście przestawiony
i wyjaśniony w książce [2]. W uproszczeniu wygląda następująco:
bufory danych −→ vertex shader −→ fragment shader −→ ekran
Bufory danych to pamięć na karcie graficznej, do której wgrano informacje potrzebne do wyświetlenia obiektu. Są to np.:
• położenie verteksów w przestrzeni
• kolor każdego verteksa
• normalna do verteksa
• ilość odbijanego światła
Niezbędnym jest jedynie położenie verteksów w przestrzeni, ponieważ
bez tego nie mamy kształtu do wyświetlenia. Wszystkie pozostałe paramtery
są całkowicie opcjonalne. Za tworzenie buforów i wgrywanie do nich danych
odpowiada WebGL.
Vertex shader odpowiada za przekształcenia położenia verteksów w przestrzeni. Pozwala on np. na obrót obiektów na scenie poprzez zaaplikowanie
odpowiedniej transformacji na każdym verteksie tworzącym dany obiekt.
W tej pracy inżynierskiej vertex shader używa się jedynie do transformacji
wyświetlanego obszaru do przedziału [0, 1]2 .
Fragment shader odpowiada za ustalenie jakiego koloru ma być wyświetlany piksel. Jego zadaniem jest nakładanie tekstur na obiekty, aplikowanie
efektów specjalnych czy światła na scenie. Ponieważ fragment shader odpowiada za kolor, kod w nim zawarty odpowiada za wszystkie efekty generowane w ramach tej pracy inżynierskiej i stanowi najważniejszą jej część.
Verteks shader oraz fragment shader razem tworzą tzw. ”program”, który stanowi całość i może zostać użyty przy kolejnym wywołaniu funkcji
rysującej. Program będzie dalej nazywany jako ”webgl program”, żeby nie
powodować dezorientacji.
Całość akcji niezbędnych do wyświetlenia obrazu na ekranie przy użyciu
WebGL 1.0 w uproszczeniu przestawia się następująco:
1. stwórz webgl program
2. stwórz vertex shader
5
3. załaduj źródła do vertex shadera
4. skompiluj vertex shader
5. dodaj vertex shader do webgl programu
6. stwórz fragment shader
7. załaduj źródła do framgent shadera
8. skompiluj fragment shader
9. dodaj fragment shader do webgl programu
10. zlinkuj webgl program
11. ustaw webgl program jako aktualnie używany
12. utwórz bufor na atrybuty verteksów
13. wgraj dane do bufora
14. podepnij bufor pod odpowiedni atrybut fragment shadera
15. wywołaj funkcję rysującą prymitywy
Z powodu tak wielu niezbędnych akcji do narysowania nawet najprostszego kształtu na ekranie, WebGL posiada początkowo bardzo stromą krzywą uczenia się. Nie jest możliwe rozbicie tych etapów na mniejsze, łatwiej
przyswajalne i niezależne części, ponieważ wszystkie one są niezbędne.
Osoba chcąca zrozumieć działanie całego procesu powinna zajrzeć do
pozycji [2], oraz uzupełniająco do [WEBGL10] oraz [GLES20].
5
Wymagania, uruchamianie programu, różnice między przeglądarkami
Całość pracy została napisana w postaci stron internetowych. Nie jest więc
wymagana instalacja. Wystarczy uruchomić odpowiednią stronę.
Wymagane za to są:
• przeglądarka internetowa obsługująca HTML5, javascript, WebGL 1.0
• włączona obsługa WebGL w przeglądarce
• dostępna akceleracja 3D w systemie
6
Program był uruchamiany na Firefox 27, Chrome 33, Internet Explorer
11 pod Windows 7. Na wszystkich przeglądarkach działa, jednak z powodu
różnic między przeglądarkami strony wykazują trochę odmienne zachowanie.
Firefox oraz IE nie obsługują pola ”color” w formularzach HTML5, stąd
pole to jest w nich wyświetlane jedynie jako tekst z wartością koloru w
postaci #RRGGBB, tak jak w CSS. Wartości te można ręcznie modyfikować.
W Chrome pole to jest wyświetlane w postaci przycisku wyświetlającego
aktualnie wybrany kolor, który po kliknięciu pozwala wybrać inny kolor z
palety kolorów.
Firefox nie przesyła na bieżąco powiadomień do strony nt. modyfikacji
pola typu ”range” w trakcie przesuwania. Stąd obraz jest odświeżany dopiero
po puszczeniu suwaka. W Chrome oraz IE powiadomienia o zmianie wartości
są przesyłane na bieżąco, dzięki czemu można w trakcie obserwować zmiany
jakie to powoduje w generowanym obrazie.
IE pyta za każdym razem czy pozwalać stronie na włączenie zablokowanych skryptów. Żeby strona działała, niezbędna jest zgoda użytkownika.
Program nie był testowany w przeglądarce Opera. WebGL jest tam domyślnie wyłączony i trzeba go najpierw aktywować.
6
Opis szkieletu programu
Wszystkie strony składają się z wcześniej utworzonego kodu szkieletowego,
który następnie był powielany i dostosowywany na potrzeby danego efektu.
Różnice między poszczególnymi efektami to przede wszystkim kod fragment
shadera oraz kontrolki pozwalające na wygodną zmianę przekazywanych parametrów. Poniżej przedstawiam główne elementy szkieletu. Dokładne opisy
użytych tutaj funkcji znajdują się w specyfikacjach [HTML5, WEBGL10,
GLES20, GLES20GLSL]. Ogromnie przydatną pozycją, bez której ciężko
byłoby napisać cokolwiek jest książka [2]. Implementacja szkieletu wykorzystuje bibliotekę ze strony [MATRIX] do operacji na macierzach. Opis
teoretyczny przekształceń przestrzeni na macierzach, ich znaczenie i wyprowadzenie znajduje się w znakomitej pozycji [3].
Zakładam, że w zmiennej gl znajduje się kontekst elementu canvas.
1
2
var canvas = document . getElementById ( ’ s o m e c a n v a s i d ’ )
var g l = canvas . g e t C o n t e x t ( ’ webgl ’ )
6.1
Tworzenie shadera
1 f u n c t i o n g e t S h a d e r ( g l , t agId , shaderType )
2 {
3
var shaderNode = document . getElementById ( t a g I d ) ;
4
var s h a d e r S r c = shaderNode . innerHTML ;
7
5
6
7
8
9
10
var s h a d e r = g l . c r e a t e S h a d e r ( shaderType ) ;
g l . s h a d e r S o u r c e ( shader , s h a d e r S r c ) ;
g l . compileShader ( shader ) ;
11
12
13
14
15
16
17 }
i f ( g l . ge tS had er Par am et er ( shader , g l .
COMPILE STATUS ) != t r u e )
{
a l e r t ( t a g I d + ’ s h a d e r c o m p i l a t i o n f a i l e d \n\n ’
+ g l . getShaderInfoLog ( shader ) ) ;
return null ;
}
return shader ;
Powyższa funkcja odpowiada za:
• utworzenie shadera
• wyciągnięcia źródeł z elementu strony o przekazanym id
• wstawienie źródeł do shadera
• skompilowanie shadera
• ewentualną obsługę błędów podczas kompilacji
Pierwsze dwie linijki w ciele funkcji odpowiadają za uzyskanie kodu źródłowego dla shadera. Następnie tworzony jest shader, do którego wstawiane
są źródła shadera, po czym następuje kompilacja. Jeśli wystąpiły błędy są
one wyświetlane za pomocą powiadomienia alert i zwracany jest null. Jeśli
wszystko przebiegło pomyślnie, funkcja zwraca skompilowany shader.
Aby przekazać źródła shadera do funkcji gl.ShaderSource, trzeba je mieć
w zmiennej typu string. Niektórzy autorzy stron używających WebGL piszą
shader bezpośrednio jako string, jednak jest to wysoce niewygodne.
Innym rozwiązaniem, używanym np. na stronie http://learningwebgl.com/
jest wstawienie kodu shadera do tagu
1 < s c r i p t type = ’ ’ xxx ’ ’ i d = ’ ’ s c r i p t i d ’ ’ > . . . < / s c r i p t >
gdzie xxx to typ skryptu, który przeglądarka nie rozpoznaje. Domyślnym zachowaniem przeglądarki jest zignorowanie takiego elementu, a dzięki
przypisanemu id jest możliwe odwołanie się do tego elementu w dalszych
skryptach.
1
var s c r i p t E l e m e n t = document . getElementById ( ‘ s c r i p t i d
’) ;
8
Aby wyciągnąć tekst shadera z elementu, który już został znaleziony
można posłużyć się jedną z dwóch metod:
• użyć API HTML DOM (Document Object Model)
• użyć atrybutu innerHTML
Użycie HTML DOM polega na pobraniu wszystkich węzłów zawartych
w uzyskanym elemencie i sklejeniu zawartości węzłów tekstowych. Jest to
jednak niepotrzebne, ponieważ istnieje drugi sposób.
Atrybut innerHTML zawiera tekstową reprezentację elementów zawartych w ciele elementu, na którym ten atrybut jest sprawdzany. Stąd można
łatwo i wygodnie uzyskać treść shadera ze skryptu.
1
var s o u r c e = s c r i p t E l e m e n t . innerHTML ;
W pracy tej użyto sposobu drugiego.
6.2
Tworzenie i używanie webgl programu
1 // c r e a t e program
2
var program = g l . c reate Prog ram ( ) ;
3 // add s h a d e r s t o program
4
g l . a t t a c h S h a d e r ( program , v e r t e x S h a d e r ) ;
5
g l . a t t a c h S h a d e r ( program , fragmentShader ) ;
6 // l i n k program
7
g l . linkProgram ( program ) ;
8
i f ( g l . getProgramParameter ( program , g l .
LINK STATUS ) != t r u e )
9
{
10
a l e r t ( ” Program l i n k i n g f a i l e d ” + g l .
getProgramInfoLog ( program ) ) ;
11
return ;
12
}
13 // s e t t o u s e program
14
g l . useProgram ( program ) ;
W pierwszej linijce tworzony jest webgl program. Następnie dodawane
są do niego wcześniej utworzone shadery za pomocą funkcji getShader(), po
czym następuje linkowanie.
Ponieważ vertex shader i fragment shader ściśle ze sobą współpracują,
muszą one być ze sobą zgodne:
• zmienne przez które parametry są przekazywane z jednego do drugiego
(zmienne typu varying) muszą mieć takie same nazwy i typy w obu
9
• zmienne uniform przez które przekazywane są parametry globalne dla
shaderów muszą mieć zgodne nazwy jeśli są użyte w obu
Dodatkowo na shadery są narzucone pewne ograniczenia, takie jak maksymalna ilość zmiennych typu uniform. Wszystkie te rzeczy są sprawdzane
podczas linkowania. Jeśli w trakcie linkowania nastąpiły błędy, są one wyświetlane. W przeciwnym razie webgl program jest ustawiany jako aktualnie
używany. Od tej pory wszystkie wyświetlane kształty będą przez niego przetwarzane.
6.3
Przekazywanie danych do webgl programu
Dane do webgl programu są przekazywane przez dwa typy zmiennych:
• uniform
• attrib
6.3.1
uniform
Zmienne typu uniform można traktować jak zmienne globalne w ramach
programu. Wartość tych zmiennych jest przypisana przed uruchomieniem
funkcji wyświetlającej, zatem podczas pracy shadera, wartość zmiennych
uniform jest stała. W zmiennych tych zapisuje się np. macierz transformacji
dla verteksów. Wszystkie parametry dostępne do modyfikacji na stronach
implementujących techniki proceduralne są przekazywane przez zmienne typu uniform, np. kolory.
1
2
uniform vec3 c o l o r a t 0 ;
uniform vec3 c o l o r a t 1 ;
Ustawianie wartości typu uniform odbywa się przez wywołanie funkcji
WebGL. Zmienna typu uniform przyjmuje dokładnie jedną wartość, która
w trakcie działania shadera się nie zmienia. Bufor na dane jest więc niepotrzebny.
1
2
p r o g r a m v a r s . s c a l e = g l . g e t U n i f o r m L o c a t i o n ( program , ’
color at 0 ’)
g l . uniform3f ( program vars . c o l o r a t 0 , 1 . 0 , 1 . 0 , 0 . 0 )
W pierwszej linijce pobierana jest lokalizacja zmiennej uniform o nazwie
color at 0. Lokalizacja ta służy jako id dla zmiennej w programie. Następnie
do zmiennej tej (pod jej lokalizację) jest wstawiana wartość (1.0, 1.0, 0.0),
co odpowiada kolorowi żółtemu.
Pozostałe parametry dostępne do modyfikacji na stronie (scale, threshold, . . . ) też są zmiennymi typu uniform, tak samo jak color at 0, są więc
przekazywane do shadera w analogiczny sposób.
10
6.3.2
attrib
Zmienne typu attrib są parametrami wejściowymi dla shadera, które mogą być różne dla każdego verteksa. Wartość zmiennych attrib jest ustalana
na stałe lub pobierana z ustalonego wcześniej bufora. W zmiennych tych
przechowuje się wszelkie atrybuty unikalne dla verteksów np. ich pozycja
czy kolor. Zmienne typu attrib mogą być przekazywane tylko do verteks
shadera. Stąd, żeby fragment shader dostał parametr unikalny dla każdego
verteksa, verteks shader musi go przekazać fragment shaderowi.
Ustawianie wartości typu attrib zostanie omówione na przykładzie poniższego kodu.
1 // v a l u e s f o r aPos w i l l be taken from a r r a y
2
var vsPosIndex = g l . g e t A t t r i b L o c a t i o n ( program , ’
aPos ’ ) ;
3
i f ( vsPosIndex == −1 )
4
{
5
a l e r t ( ” I n v a l i d a t t r i b u t e name i n
getAttribLocation ( ) ”) ;
6
return ;
7
}
8
g l . e n a b l e V e r t e x A t t r i b A r r a y ( vsPosIndex ) ;
9
10 // s e t some p o i n t s
11
var v e r t i c e s = [
12
0.0 , 1.0 , 0.5 ,
13
0.0 , 0.0 , 0.5 ,
14
1.0 , 1.0 , 0.5 ,
15
1.0 , 0.0 , 0.5
16
];
17
18 // c r e a t e b u f f e r f o r v e r t i c e s
19
vertBuffer = gl . createBuffer () ;
20
g l . b i n d B u f f e r ( g l .ARRAY BUFFER, v e r t B u f f e r ) ;
21 // put v e r t i c e s i n t o b u f f e r
22
g l . b u f f e r D a t a ( g l .ARRAY BUFFER, new F l o a t 3 2 A r r a y (
v e r t i c e s ) , g l .STATIC DRAW ) ;
23
24 // say t h a t v a l u e s f o r v e r t e x S h a d e r . pos s h o u l d be
taken from v e r t B u f f e r
25
g l . v e r t e x A t t r i b P o i n t e r ( vsPosIndex , 3 , g l .FLOAT,
false , 0 , 0 ) ;
Najpierw trzeba pobrać lokalizację atrybutu w programie. Lokalizacja ta
służy jako id dla zmiennej w programie. W tym celu przekazuje się program
11
oraz nazwę zmiennej do funkcji getAttribLocation(), która zwraca żądaną
lokalizację.
Następnie ustawiane jest, że zmienna aPos (jej lokalizacja jest w vsPosIndex) ma mieć wartości pobierane z bufora, a nie ustalone na jedną wartość.
Dalej tworzona jest tablica pozycji kolejnych verteksów oraz bufor na nie,
po czym bufor ten zostaje ustawiony jako aktualny ARRAY BUFFER. W
tym momencie wszystko co zostaje wykonane na ARRAY BUFFER będzie
dotyczyło vertBuffer. Należy zaznaczyć, że bufor ten znajduje się w pamięci
karty graficznej, a nie w pamięci RAM.
Ponieważ funkcja bufferData() przyjmuje jedynie tzw. typed arrays, trzeba skonwertować tablicę vertices na tablicę typed array odpowiedniego typu.
Zajmuje się tym funkcja Float32Array(). Szczegóły dotyczące typed arrays
są w odpowiedniej specyfikacji [TYPEDARRAY].
Po konwersji na tablicę typed array dane z vertices są wstawiane do bufora aktualnie przypisanego do ARRAY BUFFER, czyli do wcześniej utworzonego vertBuffer.
Na koniec bufor ten zostaje powiązany z atrybutem aPos, którego kolejne
wartości będą brane z właśnie przypisanego bufora.
6.4
Rysowanie wprowadzonych danych
Gdy wszystko zostanie już ustalone należy wywołać jedną z dwóch funkcji
rysujących: drawArrays() bądź drawElements().
Obie z tych funkcji rysują prymitywy takie jak:
• punkty
• linie
• trójkąty
drawArrays() bierze jednak kolejne verteksy z bufora, natomiast drawElements() bierze verteksy z bufora wedle indeksów przechowywanych w
innym buforze. Pozwala to zaoszczędzić pamięć, jeśli duża liczba verteksów
w wyświetlanym kształcie się powtarza. Ponieważ jednak w tej pracy wykorzystuje się jedynie dwa trójkąty do wyświetlania obrazu, używana jest
funkcja drawArrays().
1 // f i n a l l y show what we ’ ve done
2
g l . drawArrays ( g l . TRIANGLE STRIP , 0 , v e r t i c e s .
length /3) ;
Powyższa funkcja powoduje wyświetlenie trójkątów zbudowanych z 4
verteksów (w vertices jest 12 punktów), bez żadnego offsetu. Trójkąty są
bufowane za pomocą trybu TRIANGLE STRIP, który bierze dwa pierwsze verteksy, po czym dla każdego kolejnego tworzy trójkąt zbudowany z
aktualnego verteksa i dwóch poprzednich.
12
6.5
Interfejs użytkownika
Interfejs użytkownika został stworzony na bazie HTML5. Na każdej ze stron
zawiera on canvas na którym wyświetlany jest generowany obraz, oraz odpowiednie kontrolki podpięte pod zmienne uniform shaderów. Obraz jest
odświeżany przy każdej zmianie dowolnego parametru, dzięki czemu można
od razu oglądać wyniki dokonanych zmian.
Zakresy kontrolek zostały dobrane eksperymentalnie, tak żeby najlepiej
było widać tworzące się ciekawe zachowania generowanego obrazu. Stąd w
pewnych teksturach ten sam parametr może mieć większy zakres, bądź inną
granulację.
W zależności od tekstury dodano też do stron dodatkowe paramtery kontrolujące nowe funkcje, tak żeby udostępnić użytkownikowi nie tylko podstawowy zakres modyfikacji parametrów, ale też dać mu nowe możliwości
kontroli.
7
Noise
Bazą prawie wszystkich stworzonych efektów jest funkcja noise. Bazuje ona
na rozmieszczeniu watości losowych na n-wymiarowej kracie, których suma
przemnożona przez współczynniki jest zwracana jako wartość. Współczynniki te zależą od punktu w którym chcemy obliczyć wartość funkcji i zawsze
są równe zero oprócz komórek kratki w bezpośrednim otoczeniu punktu.
Szczegółowe omówienie implementacji znajduje się w książce [1].
Implementacja funkcji noise wykorzystana w tej pracy została pobrana
ze strony [NOISE]. Implementacja ta jest powszechnie używana nie tylko
w swojej oryginalnej postaci, ale też przepisywana do innych języków. Z
powodu ograniczeń technologii implementacja ta jest wklejona bezpośrednio
w kod źródłowy każdej strony, a nie zaimportowana osobno z pliku.
8
Zaimplementowane techniki
Techniki tekstur proceduralnych zostały zaimplementowane i osadzone w
stronach internetowych, we wcześniej opisany sposób. Nazwy poniższych
podrozdziałów odpowiadają nazwom poszczególnych stron zawierających
implementację odpowiedniej techniki.
Opisy kolejnych stron nie zawierają rzeczy opisane na stronach wcześniejszych. Stąd jeśli czytelnik ma wątpliwości, należy przeczytać opis wcześniejszych technik.
Obrazy prezentujące poszczególne techniki dla różnych parametrów zostały zamieszczone na końcu pracy inżynierskiej.
13
8.1
01 noise
Strona przedstawia jak wygląda noise bez żadnych dodatkowych przekształceń. Ponieważ antydziedzina funkcji noise jest wartością z przedziału [−1.0, 1.0],
musi ona zostać przekształcona na przedział [0.0, 1.0] żeby mogła być wyświetlona. Przekształceniem zastosowanym jest tutaj bezpośrednie odwzorowanie odcinka [−1.0, 1.0] na odcinek [0.0, 1.0].
1
f l o a t n o i s e = ( c n o i s e ( vPos . xy ) + 1 . 0 ) / 2 . 0 ;
Dodatkowo użyty jest parametr scale pozwalający na przeskalowanie
wektora wejściowego dla noise, dzięki czemu można kontrolować stopień powiększenia funkcji w generowanym obrazie.
1
f l o a t n o i s e = ( c n o i s e ( vPos . xy ∗ s c a l e ) + 1 . 0 ) / 2 . 0 ;
Wartość wynikowa funkcji noise jest poddana przekształceniu przez funkcję wykładniczą. W ramach ekperymentowania uznano, że funkcja wykładnicza najlepiej nadaje się do przekształcania wartości wyjściowej. Ma ona te
zalety, że jest ciągła, monotonicznie rosnąca w przedziale [0.0, 1.0], a wartości z odcinka [0.0, 1.0] poddane przekształceniu nigdy poza niego nie wychodzą. Dodatkowo efekty jakie powstają przez takie przekształcenie są w
wielu przypadkach interesujące. Na stronie wartość pow factor jest nazywana threshold z powodu efektu jaki zdaje się ona mieć na generowany obraz.
1
f l o a t f a c t o r = pow ( n o i s e , p o w f a c t o r ) ;
Ostatnim elementem jest możliwość wybrania koloru dla wartości 0.0
oraz 1.0. Kolor, który jest wyświetlany jest interpolowany między wybranymi kolorami zgodnie z wartością powyższej zmiennej factor.
1
g l F r a g C o l o r = mix ( c o l o r 0 , c o l o r 1 , f a c t o r ) ;
8.2
01a noise abs
Strona ta jest identyczna z 01 noise poza jednym przekształceniem. W 01 noise
wartość funkcji noise jest przekształcona przez przesunięcie i przeskalowanie, tak żeby wartości były w przedziale [0.0, 1.0]. Natomiast w 01a noise abs
użyto wartości bezwzględnej do przekształcenia wartości funkcji noise.
1
f l o a t n o i s e = abs ( c n o i s e ( vPos . xy ∗ s c a l e ) ) ;
Ciekawe jest, że tak drobna zmiana ma znaczący wpływ na generowany
obraz. Zamiast rozmytych, nieokreślonych kształtów wyraźnie widać linie
oddzielające fragmenty.
14
8.3
02 fractalsum
Kod fractalsum został oparty na kodzie (książka [1], s.85):
1 float
2 f r a c t a l s u m ( p o i n t Q)
3 {
4
f l o a t value = 0;
5
f o r ( f = MINFREQ; f < MAXFREQ; f ∗= 2 )
6
v a l u e += s n o i s e (Q ∗ f ) / f ;
7
return value ;
8 }
który przedstawia ideę nakładania kolejnych wartości funkcji na siebie,
jednak za każdym razem w innej skali i z innego punktu. Jest to ciekawe
narzędzie, które można wykorzystywać w wielu różnych miejscach.
Kod 02 fractalsum został oparty bezpośrednio na tym kodzie, a interesujący fragment wygląda następująco:
1 f l o a t f r a c t a l s u m ( vec2 p o i n t )
2 {
3
f l o a t value = 0 . 0 ;
4
float freq = 1.0;
5
6
v a l u e += c n o i s e ( p o i n t ) ; // c n o i s e ( p o i n t ∗ 1 . 0 ) /
1.0;
7
v a l u e += c n o i s e ( p o i n t ∗ 2 . 0 ) / 2 . 0 ;
8
v a l u e += c n o i s e ( p o i n t ∗ 4 . 0 ) / 4 . 0 ;
9
v a l u e += c n o i s e ( p o i n t ∗ 8 . 0 ) / 8 . 0 ;
10
11
r e t u r n clamp ( value , −1.0 , 1 . 0 ) ;
12 }
13
14 v o i d main ( v o i d )
15 {
16
vec4 c o l o r 0 = vec4 ( c o l o r a t 0 , 1 . 0 ) ;
17
vec4 c o l o r 1 = vec4 ( c o l o r a t 1 , 1 . 0 ) ;
18
19
f l o a t v a l u e = ( f r a c t a l s u m ( vPos . xy ∗ s c a l e ) + 1 . 0 )
/ 2.0;
20
f l o a t f a c t o r = pow ( va lue , p o w f a c t o r ) ;
21
g l F r a g C o l o r = mix ( c o l o r 0 , c o l o r 1 , f a c t o r ) ;
22 }
Pętla w funkcji fractalsum została ręcznie rozwinięta, z powodu ograniczeń narzuconych na pętle w GLSL ES 1.0. Szczegóły ograniczeń są w
15
[GLES20GLSL] Appendix A, sekcja 4 Control Flow. Dodatkowo użyto funkcji clamp na obliczonej wartości, żeby funkcja fractalsum zwracała wartość
z przedziału [−1.0, 1.0], w celu wygodniejszego jej użycia.
Funkcja main pobiera wartość fractalsum dla aktualnego punktu i przeskalowuje ją do przedziału [0.0, 1.0]. Na podstawie tej wartości obliczany
kolor, którego wartość jest między wybranymi przez użytkownika kolorami.
8.4
02a factalsum levels
Pomysł ten został oparty na książce [1, s.88], która w podobny sposób uzyskuje efekt zwany marble (marmur). Pomysł polega na podzieleniu odcinka [0.0, 1.0] na przedziały. Każda granica między przedziałami ma nadany
kolor. Wartość wynikowego koloru jest mieszaniną dwóch sąsiadujących ze
sobą kolorów z wybranego punktu. Pozycję punktu na rzeczonym odcinku
[0.0, 1.0] uzyskuje się używając wartości zwróconej przez fractalsum.
Funkcja marble color zwraca kolor w zależności od parametru, który
przyjmuje się że jest w znormalizowanym zakresie [0.0, 1.0]. Dzieli ona ten
zakres na cztery części, których granice są w punktach:
• 0.0
• 0.4
• 0.8
• 1.0
Kolory dla tych punktów to odpowiednio:
• Color 0
• Color 1
• Color 2
• Color 3
Implementacja wygląda następująco:
1
2
3
4
5
6
7
8
vec3 m a r b l e c o l o r ( f l o a t f a c t o r )
{
i f ( factor < 0.4)
{
r e t u r n mix ( c o l o r 0 , c o l o r 1 , f a c t o r / 0 . 4 )
;
}
else i f ( factor < 0.8)
{
16
r e t u r n mix ( c o l o r 1 , c o l o r 2 , ( f a c t o r −
0.4) / 0.4) ;
9
10
11
12
13
}
else
{
14
15
16
17
18
19
20
21
}
r e t u r n mix ( c o l o r 2 , c o l o r 3 , ( f a c t o r −
0.8) / 0.2) ;
}
v o i d main ( v o i d )
{
f l o a t v a l u e = clamp (
f r a c t a l s u m ( vec2 ( vPos . x ∗ s c a l e x , vPos . y ∗
s c a l e y ) . xy ∗ s c a l e ) ,
−1.0 , 1 . 0 ) ;
value = ( value + 1.0) / 2 . 0 ;
f l o a t f a c t o r = pow ( va lue , p o w f a c t o r ) ;
g l F r a g C o l o r = vec4 ( m a r b l e c o l o r ( f a c t o r ) , 1 . 0 )
;
}
22
23
24
25
26
8.5
02b fractalsum levels jump
Efekt ten to modyfikacja 02a fractalsum levels polegająca na ostrej zmianie
jednego koloru w drugi, bez żadnej interpolacji między nimi. Powoduje to
powstanie ciekawych granic między obszarami.
Przy odpowiednim doborze kolorów i przeskalowań można uzyskać efekt
przypominający korę drzewa. Z drugiej strony uzyskuje się bardzo ciekawy
efekt przejścia przypominający wypalanie się papieru, jeśli dwa środkowe
kolory zostaną ustawione na czerwony i żółty, natomiast dwa skrajne na
zupełnie inne kolory. Przy płynnej zmianie parametru threshold wyraźnie
widać efekt przejścia między skrajnymi kolorami.
1
2
3
4
5
6
7
8
vec3 m a r b l e c o l o r ( f l o a t f a c t o r )
{
i f ( factor < 0.5)
{
r e t u r n mix ( c o l o r 0 , c o l o r 1 , f a c t o r ∗ 2 . 0 )
;
}
else
{
17
r e t u r n mix ( c o l o r 2 , c o l o r 3 , ( f a c t o r −
0.5) ∗ 2.0) ;
9
}
10
11
12
13
14
15
16
17
}
v o i d main ( v o i d )
{
f l o a t v a l u e = clamp (
f r a c t a l s u m ( vec2 ( vPos . x ∗ s c a l e x , vPos . y ∗
s c a l e y ) . xy ∗ s c a l e ) ,
−1.0 , 1 . 0 ) ;
value = ( value + 1.0) / 2 . 0 ;
f l o a t f a c t o r = pow ( va lue , p o w f a c t o r ) ;
g l F r a g C o l o r = vec4 ( m a r b l e c o l o r ( f a c t o r ) , 1 . 0 )
;
}
18
19
20
21
22
8.6
02c fractalsum 9 levels 2 colors
Kod tej strony rozwija pomysł poprzedniej, żeby tworzyć ostre przejście między kolorami. Tym razem jednak ilość kolorów została zredukowana tylko
do dwóch, jednak ilość poziomów na jakie jest dzielony odcinek [0.0, 1.0] został zwiększony do 9. W ramach każdego pododcinka następuje interpolacja
między kolorami, jednak początek następnego pododcinka zaczyna się od
razu od pierwszego koloru bez łagodnego przejścia. Powoduje to powstanie
poziomów, niczym na mapie terenu, bądź na korze niektórych drzew.
1 vec3 m a r b l e c o l o r ( f l o a t f a c t o r )
2 {
3
i f ( factor < 0.11)
4
{
5
r e t u r n mix ( c o l o r 0 , c o l o r 1 , f a c t o r / 0 . 1 1 ) ;
6
}
7
else i f ( factor < 0.22)
8
{
9
r e t u r n mix ( c o l o r 0 , c o l o r 1 , ( f a c t o r − 0 . 1 1 ) /
0.11 ) ;
10
}
11
else i f ( factor < 0.33)
12
{
13
r e t u r n mix ( c o l o r 0 , c o l o r 1 , ( f a c t o r − 0 . 2 2 ) /
0.11 ) ;
14
}
18
else i f ( factor < 0.44)
{
r e t u r n mix ( c o l o r 0
0.11 ) ;
}
else i f ( factor < 0.55)
{
r e t u r n mix ( c o l o r 0
0.11 ) ;
}
else i f ( factor < 0.66)
{
r e t u r n mix ( c o l o r 0
0.11 ) ;
}
else i f ( factor < 0.77)
{
r e t u r n mix ( c o l o r 0
0.11 ) ;
}
else i f ( factor < 0.88)
{
r e t u r n mix ( c o l o r 0
0.11 ) ;
}
else
{
r e t u r n mix ( c o l o r 0
0.12 ) ;
}
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 }
, color 1 , ( factor − 0.33) /
, color 1 , ( factor − 0.44) /
, color 1 , ( factor − 0.55) /
, color 1 , ( factor − 0.66) /
, color 1 , ( factor − 0.77) /
, color 1 , ( factor − 0.88) /
Funkcja main jest identyczna jak w przypadku 02a fractalsum levels.
8.7
03 turbulence
Kod turbulence został oparty na kodzie (książka [1], s.86):
1 float
2 t u r b u l e n c e ( p o i n t Q)
3 {
4
f l o a t value = 0;
5
f o r ( f = MINFREQ; f < MAXFREQ; f ∗= 2 )
6
v a l u e += abs ( s n o i s e (Q ∗ f ) ) / f ;
19
7
8 }
return value ;
który do złudzenia przypomina kod fractalsum. Jedyną różnicą jest użycie funkcji abs() na wartości funkcji snoise(). Przez ten zabieg wartości są
znacznie przesunięte w stronę zera, co też wyraźnie widać jeśli porówna się
obrazy generowane przez turbulence i fractalsum. Turbulence ma znacznie
mniejszą różnorodność w odcieniach barw jakie widać.
Kod zaimplementowanej funkcji wygląda następująco:
1 f l o a t t u r b u l e n c e ( vec2 p o i n t )
2 {
3
f l o a t value = 0 . 0 ;
4
float freq = 1.0;
5
6
v a l u e += abs ( c n o i s e ( p o i n t ) ) ; // c n o i s e ( p o i n t ∗
1.0) / 1.0;
7
v a l u e += abs ( c n o i s e ( p o i n t ∗ 2 . 0 ) ) / 2 . 0 ;
8
v a l u e += abs ( c n o i s e ( p o i n t ∗ 4 . 0 ) ) / 4 . 0 ;
9
v a l u e += abs ( c n o i s e ( p o i n t ∗ 8 . 0 ) ) / 8 . 0 ;
10
11
r e t u r n clamp ( value , −1.0 , 1 . 0 ) ;
12 }
13
14 v o i d main ( v o i d )
15 {
16
vec4 c o l o r 0 = vec4 ( c o l o r a t 0 , 1 . 0 ) ;
17
vec4 c o l o r 1 = vec4 ( c o l o r a t 1 , 1 . 0 ) ;
18
19
f l o a t v a l u e = ( t u r b u l e n c e ( vPos . xy ∗ s c a l e ) + 1 . 0 )
/ 2.0;
20
f l o a t f a c t o r = pow ( va lue , p o w f a c t o r ) ;
21
g l F r a g C o l o r = mix ( c o l o r 0 , c o l o r 1 , f a c t o r ) ;
22 }
Zgodnie z oczekiwaniami kod 02 fractalsum i 03 turbulence różnią się
jedynie użyciem funkcji abs() na wartościach zwracanych przez cnoise.
8.8
04 lattice
Lattice (ang. krata) to efekt sprawdzenia, co wyjdzie jeśli użyje się funkcji
okresowej np. sinus na współrzędnych obrazu. Okazuje się, że jeśli do sinusa
podana zostanie wartość x każdego fragmentu, to otrzyma się pasy jako obraz. Dodano więc sin(y) dla każdego fragmentu, a wartość w danym punkcie
jest determinowana przez mniejszą wartość z tych dwóch funkcji.
20
1 v o i d main ( v o i d )
2 {
3
vec4 c o l o r 0 = vec4 ( c o l o r a t 0 , 1 . 0 ) ;
4
vec4 c o l o r 1 = vec4 ( c o l o r a t 1 , 1 . 0 ) ;
5
6
f l o a t v a l u e x = ( s i n ( vPos . x∗ s c a l e ) + 1 . 0 ) / 2 . 0 ;
7
f l o a t v a l u e y = ( s i n ( vPos . y∗ s c a l e ) + 1 . 0 ) / 2 . 0 ;
8
f l o a t v a l u e = min ( v a l u e x , v a l u e y ) ;
9
g l F r a g C o l o r = mix ( c o l o r 0 , c o l o r 1 , pow ( va lue ,
pow factor ) ) ;
10 }
8.9
04a lattice and noise
Program sprawdza jaki wyjdzie obraz, jeśli połączy się lattice oraz noise.
Dla pozycji x jest liczony sinus do którego jest dodawana wartość noise, a
wynik jest przycinany do przedziału [−1.0, 1.0] i przeskalowany do [0.0, 1.0].
Tak samo dla pozycji y. Wartością wynikową jest mniejsza z tych dwóch
wartości. Najciekawszy efekt uzyskuje się przy dużych wartościach lattice
scale. Obraz przypomina wtedy, jakby był drukowany.
1 v o i d main ( v o i d )
2 {
3
vec4 c o l o r 0 = vec4 ( c o l o r a t 0 , 1 . 0 ) ;
4
vec4 c o l o r 1 = vec4 ( c o l o r a t 1 , 1 . 0 ) ;
5
6
f l o a t value x =
7
(
8
clamp ( −1.0 , 1 . 0 , s i n ( vPos . x ∗ 1 0 . 0 ∗
l a t t i c e s c a l e ) + n o i s e 2 d ( vPos . xy∗ s c a l e )
∗3.0)
9
+ 1.0
10
)
11
/ 2.0;
12
f l o a t value y =
13
(
14
clamp ( −1.0 , 1 . 0 , s i n ( vPos . y ∗ 1 0 . 0 ∗
l a t t i c e s c a l e ) + n o i s e 2 d ( vPos . xy∗ s c a l e )
∗3.0)
15
+ 1.0
16
)
17
/ 2.0;
18
21
19
20
f l o a t v a l u e = min ( v a l u e x , v a l u e y ) ;
g l F r a g C o l o r = mix ( c o l o r 0 , c o l o r 1 , pow ( va lue ,
pow factor ) ) ;
21 }
8.10
04b sin and noise
To ciekawe połączenie używa wartości funkcji noise jako parametru dla funkcji sinus, co w efekcie daje mocno zniekształcone okręgi. Przeskalowanie
parametru dla sinusa powoduje zagęszczenie pojawiających się okręgów, natomiast threshold kontroluje ich grubość.
1 v o i d main ( v o i d )
2 {
3
vec4 c o l o r 0 = vec4 ( c o l o r a t 0 , 1 . 0 ) ;
4
vec4 c o l o r 1 = vec4 ( c o l o r a t 1 , 1 . 0 ) ;
5
6
f l o a t v a l u e = ( s i n ( n o i s e 2 d ( vPos . xy∗ s c a l e ) ∗
sin scale ) + 1.0) / 2.0;
7
f l o a t f a c t o r = pow ( va lue , p o w f a c t o r ) ;
8
g l F r a g C o l o r = mix ( c o l o r 0 , c o l o r 1 , f a c t o r ) ;
9 }
9
Ograniczenia technologii
Wersja GLSL użyta w WebGL 1.0 nie pozwala na tworzenie pętli, której
nie da się rozwinąć do postaci nieiteracyjnej w trakcie kompilacji shader’a.
Narzuca to ograniczenia w możliwych modyfikacjach i może stanowić przeszkodę do osiągania ciekawych efektów inaczej niedostępnych.
W pracy zrezygnowano z tego powodu z implementacji komórek voronoi
jako elementu bazowego do tworzenia kolejnych technik proceduralnych, ponieważ implementacja funkcji wyznaczającej odległość do drugiego najbliższego punktu przy dynamicznym generowaniu punktów jest mocno utrudniona. Klasyczny diagram voronoi jest możliwy do zaimplementowania w
WebGL dzięki sztuczce z buforem głębokości [VORONOI].
WebGL 1.0 nie udostępnia rozszerzenia Transform Feedback, które pozwala na przechowywanie danych już przetworzonych przez shader. Pozwalało by to nie tylko na przeprowadzanie wcześniejszych obliczeń w celu optymalizacji, ale też używanie karty graficznej do obliczeń na bieżąco, co w przypadku bardziej skomplikowanych technik proceduralnych może mieć kluczowe znaczenie.
22
10
Podsumowanie
Techniki proceduralne są bardzo ciekawym narzędziem w rękach wprawionego artysty. Przy stosunkowo małym nakładzie pracy możliwe jest tworzenie
ciekawych efektów, których aplikacje ogranicza jedynie wyobraźnia osoby
tworzącej.
Techniki proceduralne pozwalają też w zupełnie innym świetle spojrzeć
na powszechnie znane funkcje. Zamiast myśleć o sinusie jako o funkcji mającej coś wspólnego z kątami, możemy jej używać jako narzędzia do generowania pasów czy krat w teksturach proceduralnych. Funkcja wykładnicza
natomiast okazuje się być bardzo dobrym przekształceniem tworzącym złudzenie parametru określającego wartość progową.
Literatura
[1] David S. Ebert, F. Kenton Musgrave, Darwyn Peachey and Ken Perlin,
Texturing & modeling. A procedural approach. Third edition. 2003.
[2] Aaftab Munshi, Dan Ginsburg, Dave Shreiner, OpenglES 2.0 Programming Guide. 2008.
[3] Peter Shirley, Michael Ashikhmin and Steve Marschner, Fundamentals
of Computer Graphics. 2009.
[HTML5] HTML5 A vocabulary and associated APIs for HTML and
XHTML. W3C Candidate Recommendation 04 February 2014
http://www.w3.org/TR/html5/
[WEBGL10] WebGL 1.0 Specification Version 1.0.2. 01 March 2013
https://www.khronos.org/registry/webgl/specs/1.0/
[WEBGL100] WebGL 1.0 Specification Version 1.0. 10 February 2011
https://www.khronos.org/registry/webgl/specs/1.0.0/
R
[GLES20] OpenGL
ES
Common
Profile
Specification
Version
2.0.25,
A.
Munshi,
J.
Leech,
November
2010.
http://www.khronos.org/registry/gles/specs/2.0/es full spec 2.0.25.pdf
R
[GLES20GLSL] The
OpenGL
ES
Shading
Language
Version
1.00,
R.
Simpson,
May
2009.
http://www.khronos.org/registry/gles/specs/2.0/GLSL ES Specification 1.0.17.pdf
[TYPEDARRAY] Typed Array Specification Version 1.0, 08 February 2011
https://www.khronos.org/registry/typedarray/specs/1.0/
[NOISE] https://github.com/ashima/webgl-noise
23
[VORONOI] http://blog.alexbeutel.com/332/interactive-voronoidiagrams-with-webgl/
[MATRIX] http://glmatrix.net/
24