Wprowadzenie do filtrów niestandardowych (tzw. CSS Shader)

Filtry niestandardowe, czyli tzw. Shadery CSS, pozwalają wykorzystać możliwości cieniowania WebGL w Twojej zawartości DOM. Ponieważ w obecnej implementacji używane są praktycznie takie same narzędzia do cieniowania, jak te stosowane w WebGL, trzeba cofnąć się i nieco lepiej zrozumieć terminologię 3D i trochę procesu graficznego.

W załączeniu przesyłam nagranie prezentacji dostarczonej przeze mnie niedawno do LondonJS. W tym filmie omówię terminologię 3D, co musisz wiedzieć, jakie są różne typy zmiennych i jak już dziś zacząć korzystać z filtrów niestandardowych. Warto też chwycić slajdy, aby samodzielnie przetestować prezentacje.

Wprowadzenie do Shaderów

Już wcześniej wspomnieliśmy wprowadzenie do cieniowania, z którego dokładnie wynika, czym są cieniowanie i jak można ich używać z punktu widzenia WebGL. Jeśli nie masz doświadczenia z cieniowaniem, musisz przeczytać go, zanim pójdziesz dalej, ponieważ wiele pojęć i języków zależy od obecnej terminologii cieniowania WebGL.

Mając to na uwadze, włączmy filtry niestandardowe i zabierajmy się do roboty!

Włączanie filtrów niestandardowych

Filtry niestandardowe są dostępne zarówno w Chrome, jak i w wersji Canary oraz Chrome na Androida. Wejdź na stronę about:flags i wyszukaj „CSS Shaders”, włącz je i ponownie uruchom przeglądarkę. Wszystko gotowe!

Składnia

Filtry niestandardowe rozszerzają zestaw filtrów, które możesz już stosować do elementów DOM, takich jak blur lub sepia. Eric Bidelman napisał świetne narzędzie do zabawy, które warto wypróbować.

Aby zastosować filtr niestandardowy do elementu DOM, użyj następującej składni:

.customShader {
    -webkit-filter:

    custom(
        url(vertexshader.vert)
        mix(url(fragment.frag) normal source-atop),

    /* Row, columns - the vertices are made automatically */
    4 5,

    /* We set uniforms; we can't set attributes */
    time 0)
}

Jak widać, deklarujemy cieniowanie wierzchołków i fragmentów, liczbę wierszy i kolumn, na które ma być podzielony element DOM, a potem wszelkie uniformy, które chcemy przekazać.

Warto jeszcze wspomnieć, że używamy funkcji mix() wokół cieniowania fragmentów z trybem mieszania (normal) i trybem złożonym (source-atop). Spójrzmy na sam program do cieniowania fragmentów, aby dowiedzieć się, dlaczego potrzebujemy funkcji mix().

Wysyłanie Pixela

Jeśli znasz narzędzia do cieniowania WebGL, zauważysz, że w filtrach niestandardowych trochę się różni. W przypadku jednego z nich nie tworzymy tekstur, których nasz cieniowanie fragmentów używa do wypełnienia pikseli. Zawartość DOM z zastosowanym filtrem jest automatycznie mapowana na teksturę, co oznacza dwie rzeczy:

  1. Ze względów bezpieczeństwa nie możemy wysyłać zapytań o wartości kolorów poszczególnych pikseli tekstury DOM.
  2. Nie określamy (przynajmniej w przypadku obecnych implementacji) ostatecznego koloru piksela, czyli gl_FragColor jest niedostępny. Zamiast tego zakłada się, że chcesz renderować zawartość DOM, a musisz manipulować jej pikselami pośrednio za pomocą css_ColorMatrix i css_MixColor.

W związku z tym nasz „Hello World” cieniowania fragmentów wygląda mniej więcej tak:

void main() {
    css_ColorMatrix = mat4(1.0, 0.0, 0.0, 0.0,
                            0.0, 1.0, 0.0, 0.0,
                            0.0, 0.0, 1.0, 0.0,
                            0.0, 0.0, 0.0, 1.0);

    css_MixColor = vec4(0.0, 0.0, 0.0, 0.0);

    // umm, where did gl_FragColor go?
}

Każdy piksel zawartości DOM jest mnożony przez parametr css_ColorMatrix, który w powyższym przypadku nie działa jako matryca tożsamości ani nie zmienia żadnych wartości RGBA. Gdybyśmy chcieli zachować czerwone wartości, użyjemy css_ColorMatrix w ten sposób:

// keep only red and alpha
css_ColorMatrix = mat4(1.0, 0.0, 0.0, 0.0,
                        0.0, 0.0, 0.0, 0.0,
                        0.0, 0.0, 0.0, 0.0,
                        0.0, 0.0, 0.0, 1.0);

Po pomnożeniu wartości pikseli 4D (RGBA) przez macierz otrzymasz zmanipulowaną wartość piksela z drugiej strony, w tym przypadku wyzerujesz zielone i niebieskie komponenty.

Kolor css_MixColor jest używany głównie jako kolor podstawowy, który można połączyć z zawartością DOM. Mieszanie odbywa się za pomocą trybów mieszania znanych z pakietów artystycznych: nakładki, ekranu, rozmycia kolorów, ostrego światła itd.

Te dwie zmienne mogą manipulować pikselami na wiele sposobów. Zapoznaj się ze specyfikacją efektów filtra, aby lepiej zrozumieć współdziałanie trybów mieszania i trybów złożonych.

Tworzenie Vertex

W WebGL ponosimy pełną odpowiedzialność za tworzenie punktów 3D naszej siatki. W przypadku filtrów niestandardowych wystarczy określić liczbę wierszy i kolumn, a przeglądarka automatycznie podzieli zawartość DOM na kilka trójkątów:

Tworzenie Vertex
Obraz podzielony na wiersze i kolumny

Każdy z tych wierzchołków jest przekazywany do cieniowania wierzchołków w celu manipulacji. To oznacza, że w razie potrzeby możemy przenosić je w przestrzeni 3D. Już niedługo będziesz mieć możliwość robienia wspaniałych efektów.

Efekt akordeonu
Obraz zniekształcony przez efekt akordeonu

Animowanie za pomocą Shaderów

Dodanie animacji do cieniowania sprawia, że są one zabawne i angażujące. Aby to zrobić, użyj przejścia (lub animacji) w kodzie CSS w celu zaktualizowania jednolitych wartości:

.shader {
    /* transition on the filter property */
    -webkit-transition: -webkit-filter 2500ms ease-out;

    -webkit-filter: custom(
    url(vshader.vert)
    mix(url(fshader.frag) normal source-atop),
    1 1,
    time 0);
}

    .shader:hover {
    -webkit-filter: custom(
    url(vshader.vert)
    mix(url(fshader.frag) normal source-atop),
    1 1,
    time 1);
}

W powyższym kodzie należy pamiętać, że w trakcie przenoszenia czas ten zostanie zmniejszony z 0 do 1. Wewnątrz programu do cieniowania można zadeklarować jednolity format time i wykorzystać jego obecną wartość:

    uniform float time;

uniform mat4 u_projectionMatrix;
attribute vec4 a_position;

void main() {
    // copy a_position to position - attributes are read only!
    vec4 position = a_position;

    // use our time uniform from the CSS declaration
    position.x += time;

    gl_Position = u_projectionMatrix * position;
}

Zacznij grać!

Filtry niestandardowe to świetna zabawa, a niesamowite efekty, jakie możesz uzyskać, są trudne (a w niektórych przypadkach nawet niemożliwe) bez nich. Wciąż jesteśmy na początku drogi i wciąż się zmieniło, ale dodanie ich może nieco uatrakcyjnić Twoje projekty, więc warto spróbować?

Dodatkowe materiały