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


Paul Klee - Color Chart (1931)

Kolory

Nie mieliśmy zbytnio okazji porozmawiać o typach wektorowych w GLSL. Zanim przejdziemy dalej, ważne jest żebyśmy dowiedzieli się o nich więcej, a temat kolorów może być w tym bardzo pomocny.

Jeżeli paradgymat programowania obiektowego jest ci bliski, to prawdopodobnie zauważyłeś, że proces ekstrakcji danych z wektorów wygląda podobnie jak ekstracja danych z struct'ów w C.

vec3 red = vec3(1.0,0.0,0.0);
red.x = 1.0;
red.y = 0.0;
red.z = 0.0;

Definiowanie kolor za pomocą notacji x, y i z jest trochę mylące, prawda? Właśnie dlatego istnieją inne sposoby dostępu do tej samej informacji, ale za pomocą innych nazw. Wartości .x, .y i .z mogą być również uzyskane za pomocą .r, .g i .b, jak i .s, .t i .p (.s, .t i .p są zwykle używane przy współrzędnych tekstur, które zobaczymy w następnych rozdziałach). Możesz również uzyskać dane wektora za pomocą indeksów [0], [1] i [2].

Następujący kod przedstawia wszystkkie sposoby uzysakania tych samych danych:

vec4 vector;
vector[0] = vector.r = vector.x = vector.s;
vector[1] = vector.g = vector.y = vector.t;
vector[2] = vector.b = vector.z = vector.p;
vector[3] = vector.a = vector.w = vector.q;

Ta mnogość metod uzyskiwania tych samych danych jest tylko po to, aby ułatwić pisanie zrozumiałego kodu. Ta elastyczność wbudowana w język shadingowy stanowi też okazję, żebyś zaczął myśleć o współrzędnych koloru i przestrzeni jako wzjamenie zastępowalnych.

Inną świetną własnością typów wektorowych w GLSL jest to, że współrzędne mogą być mieszane w dowolnej kolejności, co ułatwia castowanie i mieszanie wartości. Właśność ta nazywana jest swizzle'owaniem.

vec3 yellow, magenta, green;

// Tworzenie żółtego
yellow.rg = vec2(1.0);  // Przypisanie 1. do kanału czerwonego i zielonego
yellow[2] = 0.0;        // Przypisanie 0. do kanału niebieskiego

// Tworzenie magenty
magenta = yellow.rbg;   // Przypisanie wektora z przestawionym kanałem zielonym i niebieskim

// Tworzenie zielonego
green.rgb = yellow.bgb; // Przypisanie kanału niebieskiego do kanału czerwonego i niebieskiego

Mieszanie koloru

Teraz, gdy już wiesz jak definiuje się kolory, pora na połączenie tego z naszą wcześniejszą wiedzą. W GLSL istnieje bardzo przydatna funkcja mix(), która pozwala mieszać dwie wartości wobec określonego stosunku wyrażonego w procentach. Potrafisz zgadnąć jaki jest zakres procentów? Oczywiście, że 0.0 i 1.0! Czas na wykorzystanie naszego shadingowego karate!

Sprawdź linijkę 18 poniższego kodu i zoabcz jak używamy wartości bezwględnej z sinusa od czasu, aby mieszać colorA i colorB.

Pokaż na co cie stać:

Zabawa z gradientem

Funkcja mix() ma więcej do zaoferowania. Zamiast pojedynczego floata, możemy podać zmienną tego samego typu, co dwa pierwsze argumenty; w naszym wypadku jest to vec3. W ten sposób zdobywamy kontrolę nad stosunkiem mieszania każdego indywidualnego kanału koloru, r, g i b.

Spójrz na poniższy przykład. Tak jak w przykładach z poprzedniego rozdziału, dzięki znormalizowanej współrzędnej x tworzymy gradient i wizualizujemy go za pomocą linii. Aktualnie, wszystkie kanały leżą na tej samej linii.

Odkomentuj linjkę 25 i zobacz, co się stanie. Następnie odkomentuj linijki 26 i 27. Pamiętaj, że linie wizualizują, w jakim stosunku kanały (R, G, B) kolorów colorA i colorB są obecne w ostatecznym gradiencie.

Prawdopodobnie rozpoznajesz trzy shaping functions, które używamy w linijkach 25 i 27. Baw się nimi! Czas pokazać swoje umiejętności z poprzednich rozdziałów, tworząc interesujące gradienty. Spróbuj następujących ćwiczeń:

William Turner - The Fighting Temeraire (1838)

HSB

Nie da się mówić o kolorze bez poruszenia tematu przestrzeni barw. Jak prawdopodobnie wiesz, istnieją też inne sposoby reprezentacji koloru poza RGB (z kanałem czerwonym, zielonym i niebieskim).

HSB oznacza Hue (pol. "barwa"), Saturation (pol. "nasycenie") i Brightness (pol. "jasność) i jest o wiele bardziej intuicyjną reprezentacją koloru niż RGB. Czasem Brightness nazywany jest "Value", stąd zamiast HSB można spotkać się też ze skrótem HSV. Przyjrzyj się funkcjom rgb2hsv() i hsv2rgb() w następującym kodzie:

Mapując pozycję na osi x do barwy i pozycję na osi y do jasności, otrzymujemy spektrum widzialnych kolorów. O wiele bardziej intuicyjnie wybiera się kolor HSB niż RGB.

HSB we współrzędnych biegunowych

Oryginalnie, HSB miało być reprezentowane z pomocą współrzędnych biegunowych (opartych na kącie i promieniu), a nie kartezjańskich (opartych na x i y). By zmapować naszą funkcję HSB do współrzędnych biegunowych, musimy otrzymać kąt i dystans od centrum kanwy do współrzędnej piksela. W tym celu użyjemy funkcje length() i atan(y,x) (który jest odpowiednikiem atan2(y,x) w GLSL).

Pamiętaj: vec2, vec3 i vec4 traktowane są jak wektory nawet jeśli reprezentują kolor. Zaczniemy traktować kolory i wektory bardzo podobnie.

Uwaga: Jest weięcej funkcji geometrycznych poza length jak: distance(), dot(), cross, normalize(), faceforward(), reflect() i refract(). Ponadto, GLSL ma specjalne funkcje do porównywania wektorów: lessThan(), lessThanEqual(), greaterThan(), greaterThanEqual(), equal() i notEqual().

Gdy już zdobędziemy kąt i promień, musimy znormalizować ich wartości do zakresu od 0.0 do 1.0. W linijce 27, atan(y,x) zwróci kąt w radianach między -PI a PI (-3.14 a 3.14), więc musimy podzielić tę liczbę przez TWO_PI (zdefiniowane na górze kodu), uzyskując wartości między -0.5 i 0.5, które, przez proste dodawanie, mapujemy dalej do zakresu od 0.0 do 1.0. Promień ma długość 0.5 (ponieważ liczymy odległość od środka kanwy), więc musimy podwoić ten zakres (mnożąc przez 2), by uzyskać 1.0.

Jak widzisz, sedno leży w transformowaniu i mapowaniu zakresów do 0.0 i 1.0.

Spróuj poniższych ćwiczeń:

William Home Lizars - Red, blue and yellow spectra, with the solar spectrum (1834)

Uwaga o funkcjach i ich argumentach

Zanim przeskoczysz do następnego rozdziału, zatrzymajmy się na chwilę. Wróć do funkcji hsb2rgb z poprzedniego interaktywnego przykładu. Zauważysz in przed typem argumentu. Jest to kwalifikator (ang. "qualifier") i akurat ten oznacza, że zmienna jest tylko do odczytu. W przyszłości zobaczymy, że jest również możliwe, by poprzedzić argumenty kwalifikatorami out i inout. Kwalifikator out określa, że argument jest tylko do zapisu (ang. "write-only"), natomiast inout działa podobnie jak przekazywanie argumentu przez referencje, co umożliwia również modyfikowanie go.

int newFunction(in vec4 aVec4,      // read-only
                out vec3 aVec3,     // write-only
                inout int aInt);    // read-write

Może w to nie uwierzysz, ale mamy wszystkie składniki potrzbne do tworzenia fajnych rysunków. W następnym rozdziale nauczymy się jak połączyć wszystkie poznane tricki, by stworzyć geometryczne formy/kształty przez blendowanie przestrzeni. Dobrze słyszysz... blendowanie przestrzeni.