WebGL – teoria shaderów
Tutorial oparty jest na WebGL 1.0. Wprowadzenie do niego znajdziesz w tym poście.
Potok renderingu
Tak jak wspomniałam we wcześniejszym wpisie, program oparty na WebGL-u będzie się składać z dwóch części: wysokopoziomowej (w JS-ie) oraz tej dotyczącej shaderów, napisanej w GLSL. Dziś zajmę się właśnie specyfiką tego drugiego.
Czym w ogóle jest GLSL?
OpenGL Shading Language (GLSL, glslang) – język programowania potoku graficznego składniowo zbliżony do języka C, wykorzystywany przez bibliotekę OpenGL. Program napisany w GLSL wykonywany jest bezpośrednio na GPU.
Język ten został stworzony, aby dać programistom lepszą kontrolę nad potokiem renderingu bez potrzeby uciekania się do bardzo niskopoziomowych i hardware’owych metod.
Aby zrozumieć jak będziemy wykorzystywać shadery w pisaniu kodu w WebGL-u, warto zatrzymać się na chwilę nad samym potokiem renderingu (graphics pipeline).
Powyższa ilustracja przedstawia najważniejsze etapy potoku renderingu. Rozpoczynamy od przygotowania danych dotyczących wierzchołków trójkątów naszego modelu. W WebGL-u będziemy umieszczać je w odpowiednim buforze. Następnie do gry wkracza shader wierzchołków. Jest to program wywoływany raz dla każdego wierzchołka, pozwalający przekształcić jego współrzędne z oryginalnego systemu koordynat do koordynat związanych z tzw. clip space (na każdej osi rozpiętych pomiędzy -1.0 a 1.0).
Teselacja, która w przypadku nieskomplikowanych modeli WebGL-owych nie będzie nad dotyczyła, odpowiada za zwiększenie ilości prymitywów geometrycznych w danym kształcie, by mocniej wygładzać siatkę opisywanego modelu.
Shader geometrii to również etap nieobowiązkowy, pozwalający na dodatkowe przetworzenie geometrii tuż przed procesem rasteryzacji.
Podczas rasteryzacji prymitywy przekształcane są na piksele. Na tym etapie generowane są fragmenty. czyli informacja o pozycji danego piksela oraz interpolowany kolor wierzchołka i koordynaty tekstury. Shader fragmentów wykonuje się raz dla każdego fragmentu z procesu rasteryzacji. Z jego pomocą możemy m.in. zdefiniować finalny kolor lub teksturę punktów
Wykorzystanie WebGL-a opiera się właśnie na tych dwóch wyróżnionych shaderach: shaderze wierzchołków oraz shaderze fragmentów.
Vertex shader
Za każdym razem, gdy próbujemy wyświetlić na ekranie obraz, shader wierzchołków jest uruchamiany dla każdego wierzchołka indywidualnie. Jego głównym zadaniem, o czym już wspominałam, jest przepisanie koordynat punktu na clip space. Odpowiada również za inne transformacje vertexów, generowanie współrzędnych tekstur, aplikowanie kolorów oraz świateł. Po wszystkich operacjach shader musi zwrócić zmodyfikowany wierzchołek w formie specjalnej zmiennej gl_Position.
Fragment shader
Dane wynikowe z shadera wierzchołków są interpolowane na wnętrza konkretnych prymitywów (zazwyczaj trójkątów), z których składa się model obiektu. Piksele te są nazywane fragmentami i to na nich operuje fragment shader. Działania, które może wykonać to nakładanie tekstur i świateł, a także wyliczanie kolorów na podstawie tych ustawionych w wierzchołkach. Zwraca obowiązkową zmienną gl_FragColor, czyli finalną barwę fragmentu.
Działanie shaderów dobrze pokazuje poniższa ilustracja (źródło):
Rodzaje zmiennych w WebGL
Podczas tworzenia kodu shaderów będziemy korzystać z trzech rodzajów zmiennych:
- attributes
- występują wyłącznie w shaderze wierzchołków
- przechowują wartości wejściowe do programu, wskazując na kolejne obiekty z bufora wierzchołków
- dotyczą każdego wierzchołka z osobna
- uniforms
- zawierają dane możliwe do wykorzystania zarówno w shaderze wierzchołków, jak i fragmentów
- znajdziemy w nich informacje na temat świateł, tekstur i kolorów
- są wspólne i stałe dla wszystkich wierzchołków
- varyings
- są interfejsem pomiędzy shaderami
- stanowią obiekty wyjściowe z shadera wierzchołków i wejściowe do shadera fragmentów
Oprócz wymienionych wyżej rodzajów zmiennych, będziemy korzystać również z typów, takich jak np. float, vec2, vec3, vec4, mat2, mat3 lub mat4.
Przykładowa definicja zmiennych będzie wyglądać następująco:
1 2 3 4 |
uniform float scale; attribute vec2 position; attribute vec4 color; varying vec4 v_color; |
Zmienne wbudowane, o których już wspominałam, czyli gl_Position oraz gl_FragColor mają typ vec4 – pierwsza z nich przechowuje pozycję wierzchołka, a druga kolor fragmentu.
Program w WebGL-u krok po kroku
Zanim zaczniemy tworzyć kod shaderów, warto zastanowić się, w którym miejscu naszego programu powinniśmy go umieścić. Cała otoczka i zarządzanie będzie napisana w JavaScripcie i to w nim napiszemy kolejno:
- inicjalizację kontekstu WebGL – co umawialiśmy w poprzednim wpisie
- umieszczenie danych geometrycznych w odpowiednich tablicach
- stworzenie buforów na podstawie danych z tablic
- stworzenie i kompilację shaderów
- utworzenie programu, do którego podpięte zostaną shadery
- powiązania attributes i uniforms z odpowiednimi zmiennymi w shaderach
Niezależnie od tego, czy nasz program ma wyświetlić p0jedynczy trójkąt czy skomplikowany model 3D, struktura kodu i kolejność poszczególnych operacji zawsze będzie bardzo podobna do tej powyżej.
Wiedząc już co nas czeka, możemy zabrać się za właściwy kod – już niedługo pojawi się na ten temat nowy wpis :)