Uniformy
Do tej pory widzieliśmy jak GPU zarządza wieloma równoległymi wątkami, z których każdy odpowiada za kolor części renderowanego obrazu. Choć wątki nie komunikują się między sobą, to jednak muszą jakoś otrzymywać input z CPU. Ze względu na architekturę karty graficznej taki input musi być jednakowy (ang. "uniform") dla wszystkich wątków i, z konieczności, tylko do odczytu (ang. "read-only"). Innymi słowy, każdy wątek otrzymuje takie same dane, które może odczytać, ale nie nadpisać, zmienić.
Inputy te nazywamy uniformami i mogę być większości wspieranych typów: float, vec2, vec3, vec4, mat2, mat3, mat4, sampler2D i samplerCube. Uniformy definiowane są zwykle na górze shaderu zaraz po przypisaniu domyślnej precyzji float'ów.
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution; // wielkość/rozdzielczość kanwy (szerokość, wysokość)
uniform vec2 u_mouse; // pozycja myszy na kanwie (wyrażona w pikselach)
uniform float u_time; // czas w sekundach od załadowania shadera
Wyobraź sobie te uniformy jak małe mosty między CPU i GPU. Ich nazwy bywają różne, ale w tej książce używam: u_time, u_resolution i u_mouse (przeczytaj komentarze w kodzie, aby wiedzieć, co robią). Podążam za konwencją dodawnaia u_ przed nazwą uniformów, aby było wiadomo, że nie są to zwykłe zmienne, ale ostatecznie jest to kwestia gustu. Przykładowo, ShaderToy.com używa takich samych uniformów, ale z następującym nazewnictwem:
uniform vec3 iResolution;
uniform vec4 iMouse;
uniform float iTime;
(zwróć uwagę, że iResolution jest typu vec3, a iMouse typu vec4; uniformy te zawierają po prostu dodatkowe informacje, np.: stosunek szerokości do wysokości pikseli na ekranie, czy któryś z przycisków myszy został kliknięty albo czy jest przytrzymywany)
Koniec gadania, czas zobaczyć uniformy w akcji. W pożniszym kodzie używamy u_time (liczby sekund od uruchomienia shadera) razem z funkcją sinus, aby stworzyć animację przejścia od koloru czerwonego do czarnego.
Jak widać GLSL skrywa wiele niespodzianek, na przykład w postaci specjalnych, zaimplementowanych w hardware'rze, funkcji trygonometryczne czy wykładniczych. Tutaj podaję część z nich: sin(), cos(), tan(), asin(), acos(), atan(), pow(), exp(), log(), sqrt(), abs(), sign(), floor(), ceil(), fract(), mod(), min(), max() i clamp().
Pobawmy się powyższym kodem:
-
Zmniejsz częstotliwość tak bardzo, aby zmiany koloru stały się nie zauważalne.
-
Zwiększ częstotliwość do takiego stopnia, aby ujrzeć stały kolor bez migotania.
- Wstaw funkcje sinus o różnych częstotliowściach do pozostałych kanałów (zielonego i niebieskiego), aby uzyskać ciekawe efekty.
gl_FragCoord
GLSL daje nam nie tylko domyślny output vec4 gl_FragColor, ale również domyślny input w postaci vec4 gl_FragCoord, który przechowuje współrzędne piksela (inaczej: fragmentu), nad którym aktualnie pracuje wątek - dzięki vec4 gl_FragCoord wiemy, gdzie wątek pracuje wewnątrz kanwy. Nie nazywamy go uniformem, ponieważ jego wartość różni się między wątkami. Zamiast tego gl_FragCoord nazywamy varying (z ang. "zmieniający się", "różniący się").
W powyższy kodzie, normalizujemy współrzędne fragmentu poprzez podzielenie go przez rozdzielczość kanwy. W ten sposó otrzymujemy wartości z przedziału od 0.0 do 1.0, co ułatwia zmapowanie współrzędnych x i y do, odpowiednio, czerwonego i zielonego kanału.
W świecie shaderów nie mamy zbyt dużo sposóbów debuggowania poza przypisywaniem jaskrawych kolorów do zmiennych i wyciągania wniosków o działaniu shadera, na podstawie tego, co widzimy. Odkryjesz, że programowania GLSL jest często jak wkładanie miniaturowych statków do butelki - jest to trudne, ale tez piękne i satysfakcjonujące.

Czas przetestować nasze rozumienie kodu:
-
Czy jesteś w stanie powiedzieć, gdzie na naszej kanwie znajduje się fragment o znormalizowanych współrzędnych
(0.0, 0.0)? -
Co z framgentami o znormalizowanych współrzędnych
(1.0, 0.0),(0.0, 1.0),(0.5, 0.5)i(1.0, 1.0)? -
Czy jesteś w stanie użyć uniforma
u_mousewiedząc, że wartości są nieznormalizowane? Spróbuj użyć go do manipulacji kolorem za pomocą ruchów myszki. - Czy jesteś sobie w stanie wyobrazić ciekawy sposób zmieniania koloru, łącząc współrzędne
u_mousezu_time?
Po wykonaniu tych ćwiczeń, zapewne zastanawiasz się, gdzie jeszcze można użyć twoich nowych shaderowych mocy. W następnym rozdziale zobaczymy jak stworzyć swój własny shader w three.js, Processing i openFrameworks.