Introdução aos filtros personalizados (também conhecidos como sombreadores de CSS)

Os filtros personalizados, ou shaders CSS, como costumavam ser chamados, permitem que você use o poder dos shaders do WebGL com seu conteúdo DOM. Como na implementação atual os sombreadores usados são praticamente os mesmos do WebGL, você precisa dar um passo atrás e entender um pouco da terminologia 3D e um pouco do pipeline gráfico.

Incluí uma versão gravada de uma apresentação que fiz recentemente para a LondonJS. No vídeo, explico a terminologia 3D que você precisa entender, os diferentes tipos de variáveis que vai encontrar e como começar a usar os filtros personalizados. Também é recomendável baixar os slides para testar as demonstrações.

Introdução aos sombreadores

Eu já escrevi uma introdução aos shaders, que vai explicar o que são os shaders e como usá-los do ponto de vista do WebGL. Se você nunca trabalhou com shaders, é necessário ler este artigo antes de continuar, porque muitos dos conceitos e da linguagem dos filtros personalizados dependem da terminologia de shaders do WebGL.

Então, vamos ativar os filtros personalizados e continuar!

Como ativar filtros personalizados

Os filtros personalizados estão disponíveis no Chrome, no Canary e no Chrome para Android. Basta acessar about:flags e pesquisar "CSS Shaders", ativar e reiniciar o navegador. Pronto!

A sintaxe

Os filtros personalizados ampliam o conjunto de filtros que você já pode aplicar, como blur ou sepia, aos elementos do DOM. Eric Bidelman escreveu uma ótima ferramenta de teste para isso.

Para aplicar um filtro personalizado a um elemento DOM, use a seguinte sintaxe:

.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)
}

Você vai notar que declaramos os shaders de vértice e de fragmento, o número de linhas e colunas em que queremos dividir o elemento DOM e, em seguida, os uniformes que queremos transmitir.

Uma última coisa a destacar é que usamos a função mix() em torno do sombreador de fragmentos com um modo de mesclagem (normal) e um modo composto (source-atop). Vamos analisar o sombreador de fragmentos para entender por que precisamos de uma função mix().

Empuxe de pixel

Se você já conhece os shaders do WebGL, vai notar que as coisas são um pouco diferentes nos filtros personalizados. Por exemplo, não criamos as texturas que nosso sombreador de fragmentos usa para preencher os pixels. Em vez disso, o conteúdo do DOM com o filtro aplicado é mapeado para uma textura automaticamente, o que significa duas coisas:

  1. Por motivos de segurança, não podemos consultar valores de cor de pixel individuais da textura do DOM.
  2. Não definimos a cor final do pixel (pelo menos nas implementações atuais), ou seja, gl_FragColor está fora dos limites. Em vez disso, presume-se que você vai renderizar o conteúdo do DOM, e o que você precisa fazer é manipular os pixels indiretamente usando css_ColorMatrix e css_MixColor.

Isso significa que nosso Hello World de sombreadores de fragmento fica mais ou menos assim:

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?
}

Cada pixel do conteúdo do DOM é multiplicado pelo css_ColorMatrix, que, no caso acima, não faz nada, já que é a matriz de identidade e não muda nenhum dos valores RGBA. Se quisermos manter apenas os valores vermelhos, usaremos um css_ColorMatrix assim:

// 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);

Você pode notar que, ao multiplicar os valores de pixel 4D (RGBA) pela matriz, você obtém um valor de pixel manipulado do outro lado e, neste caso, um que zere os componentes verde e azul.

O css_MixColor é usado principalmente como uma cor de base que você quer misturar com o conteúdo do DOM. A mistura é feita pelos modos de mesclagem que você já conhece dos pacotes de arte: sobreposição, tela, reserva de cor, luz dura e assim por diante.

Há muitas maneiras de manipular os pixels com essas duas variáveis. Confira a especificação dos efeitos de filtro para entender melhor como os modos de mesclagem e composto interagem.

Criação de vértices

No WebGL, assumimos toda a responsabilidade pela criação dos pontos 3D da malha, mas, nos filtros personalizados, basta especificar o número de linhas e colunas que você quer, e o navegador vai dividir automaticamente o conteúdo do DOM em vários triângulos:

Criação de vértices
Uma imagem sendo dividida em linhas e colunas

Cada um desses vértices é transmitido para nosso sombreador de vértice para manipulação, o que significa que podemos começar a movê-los no espaço 3D conforme necessário. Não vai demorar muito para você criar efeitos incríveis!

Um efeito acordeão
Uma imagem sendo distorcida por um efeito acordeão

Como animar com sombreadores

Adicionar animações aos seus shaders é o que os torna divertidos e envolventes. Para fazer isso, basta usar uma transição (ou animação) no CSS para atualizar valores uniformes:

.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);
}

O que você precisa notar no código acima é que o tempo vai passar de 0 para 1 durante a transição. No sombreador, podemos declarar o time uniforme e usar o valor atual dele:

    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;
}

Comece a jogar!

Os filtros personalizados são muito divertidos de usar, e os efeitos incríveis que você pode criar são difíceis (e, em alguns casos, impossíveis) sem eles. Ainda é cedo, e as coisas estão mudando bastante, mas adicionar essas informações vai dar um toque de show business aos seus projetos. Por que não tentar?

Outros recursos