Witaj świecie!
Zazwyczaj przykład "Hello world!" stanowi pierwszy krok przy nauce nowego języka. Jest to prosty jednolinijkowy programy, który zwraca pełną entuzjazmu wiadomość powitalną i tym samym zapowiada nadchodzące przygody.
W świecie GPU renderowanie tekstu jest jednak zbyt skomplikowanym zadaniem dla żółtodzioba. Zamiast tego wybierzemy jasny, serdeczny kolor by wykrzyczeć naszą ekscytację!
Jeżeli czytasz tę książkę w przeglądarce: powyższy blok kodu jest interaktywny. Oznacza to, że możesz edytować dowolną linijkę kodu w celach eksploracyjnych. Shader kompiluje się na bieżąco, więc zmiany widoczne będą natychmiast. Spróbuj pozmieniać wartości w linijce 8.
Choć kod jest prosty, to możemy wyciągnąć z niego ważne wnioski:
-
Podobnie jak w C, GLSL ma jedną funkcje
main
. Pod koniec zwraca ona kolor. -
Finalny kolor piksela przypisywany jest do zarezerowanej zmiennej globalnej
gl_FragColor
. -
Ten C-podobny język ma wbudowane zmienne (jak
gl_FragColor
), funkcje i typy. W aktualnym przykładzie występuje jedynie typvec4
, oznaczający czterowymiarowy wektor zmiennoprzecinkowy (ang. "float vector"). Później zobaczymy również takie typy jakvec3
,vec2
oraz znajomefloat
,int
ibool
. -
Patrząc na typ
vec4
, możemy wywnioskować, że jego cztery argumenty odnoszą się do kanałów CZERWONEGO, ZIELONEGO, NIEBIESKIEGO i ALPHA. Widać też, że jego wartości są znormalizowane, więc znajdują się w zakresie od0.0
do1.0
. Później zobaczymy, jak normalizowanie wartości pomaga w mapowaniu wartości między zakresami. - Kolejną ważną C-podobną własnością w tym przykładzie jest obecność makr preprocessora. Dzięki nim można definiować zmienne globalne za pomocą
#define
oraz operacje warunkowe za pomocą#ifdef
("if defined"),#ifndef
("if not defined") i#endif
. Wszystkie makra zaczynają się od płotka#
i ewaluowane są podczas procesu prekompilacji poprzedzającego kompilację. W naszym powyższym przykładzie linijka 2 kompilowana jest tylko wtedy, gdy zmiennaGL_ES
jest zdefiniowana (co występuje na urządzeniach mobilnych i w przeglądarkach).
-
Typy zmiennoprzecinkowe są kluczowe w shaderach, więc ich poziom precyzji (ang. precision) jest kluczowy. Niższa precyzja oznacza szybsze renderowanie, ale kosztem jakości. Możesz być wybredny i określać precyzję każdej zmiennej zmiennoprzecinkowej z osobna. W linijce 2 (
precision mediump float;
) ustawiamy średnią precyzję zmiennych zmiennoprzecinkowych ("mediump", bo "medium precision"). Możemy też ustawić ją jako niską (precision lowp float;
) lub wysoką (precision highp float;
). - Ostatni i chyba najważniejszy szczegół specyfikacji GLSL: nie ma gwaracji, że zmienne będą automatycznie castowane (np. z
int
dofloat
przy dzieleniu liczby 5 przez 2). Producenci GPU mogą stosować przeróżne optymalizacje w kartach graficzncyh, ale muszą przy tym przestrzegać pewnych wytycznych. Automatyczne castowanie nie jest jednym z nich. W naszym przykładzievec4
ma precyzję zmiennoprzecinkową i dlatego jego argumenty wymagająfloat
ów. Przezwyczaj się do stawiania kropek (.
) wefloat
ach (1.
lub1.0
, a nie1
), jeżeli nie chcesz spędzić godzin przy debugowaniu. Poniższy kod nie zawsze będzie, zatem, działał:
void main() {
gl_FragColor = vec4(1,0,0,1); // ERROR
}
Czas na ćwiczenia! Pamiętaj, że w wypadku błędu kompilacji pokaże się informacje o błędzie i linijce w której wystąpił, a kanwa zmieni kolor na biały.
-
Spróbuj zamienić
float
y naint
y. Jeśli kod się nie kompiluje, to widocznie twoja karta graficzna tego nie toleruje -
Zakomentuj linię 8
- Stwórz osobną funckję, która zwraca dowolny kolor i użyj jej w
main()
. Wskazówka: poniższy kod zwraca kolor czerwony:
vec4 red(){
return vec4(1.0,0.0,0.0,1.0);
}
- Jest wiele sposobów tworzenia typu
vec4
- spróbuj je znaleźć. Jeden z nich wygląda tak:
vec4 color = vec4(vec3(1.0,0.0,1.0),1.0);
Choć przykład ten nie jest zbyt ekscytujący, ale stanowi ważną podstawę. W następnych rozdziałach zobaczymy, jak zmienić kolor piksela z pomocą inputu przestrzennego (położenie piksela na ekranie) i temporalnego (okres czasu od momentu załadowania się strony).