The Book of Shaders by Patricio Gonzalez Vivo & Jen Lowe

Bahasa Indonesia - Tiếng Việt - 日本語 - 中文版 - 한국어 - Español - Portugues - Français - Italiano - Deutsch - Русский - Polski - English


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:

  1. Podobnie jak w C, GLSL ma jedną funkcje main. Pod koniec zwraca ona kolor.

  2. Finalny kolor piksela przypisywany jest do zarezerowanej zmiennej globalnej gl_FragColor.

  3. Ten C-podobny język ma wbudowane zmienne (jak gl_FragColor), funkcje i typy. W aktualnym przykładzie występuje jedynie typ vec4, oznaczający czterowymiarowy wektor zmiennoprzecinkowy (ang. "float vector"). Później zobaczymy również takie typy jak vec3, vec2 oraz znajome float, int i bool.

  4. 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 od 0.0 do 1.0. Później zobaczymy, jak normalizowanie wartości pomaga w mapowaniu wartości między zakresami.

  5. 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 zmienna GL_ES jest zdefiniowana (co występuje na urządzeniach mobilnych i w przeglądarkach).
  1. 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;).

  2. Ostatni i chyba najważniejszy szczegół specyfikacji GLSL: nie ma gwaracji, że zmienne będą automatycznie castowane (np. z int do float 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ładzie vec4 ma precyzję zmiennoprzecinkową i dlatego jego argumenty wymagają floatów. Przezwyczaj się do stawiania kropek (.) we floatach (1. lub 1.0, a nie 1), 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.

vec4 red(){
    return vec4(1.0,0.0,0.0,1.0);
}
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).