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 uniform
ami 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.
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 uniform
em, 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_mouse
wiedzą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_mouse
zu_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.