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

Os filtros personalizados, ou sombreadores CSS, como eram chamados, permitem usar o poder dos sombreadores do WebGL com seu conteúdo do DOM. Como, na implementação atual, os sombreadores usados são praticamente os mesmos do WebGL, é necessário entender a terminologia de 3D e um pouco do pipeline de gráficos.

Incluí uma versão gravada de uma apresentação que fiz recentemente à LondonJS. No vídeo, examinarei uma visão geral da terminologia 3D que você precisa entender, quais são os diferentes tipos de variáveis que você encontrará e como você pode começar a brincar com os filtros personalizados hoje mesmo. Você também deve pegar os slides para brincar com as demonstrações por conta própria.

Introdução aos sombreadores

Já escrevi uma introdução aos sombreadores que vai explicar o que são os sombreadores e como eles podem ser usados do ponto de vista do WebGL. Se você nunca lidou com sombreadores, saiba que essa é uma leitura obrigatória antes de você ir muito além, porque muitos dos conceitos dos filtros personalizados e da linguagem dependem da terminologia existente do sombreador WebGL.

Então, vamos ativar os filtros personalizados e vamos lá!

Como ativar filtros personalizados

Os filtros personalizados estão disponíveis no Chrome, no Canary e no Chrome para Android. Acesse about:flags, procure "Somadores de CSS", ative-os e reinicie o navegador. Pronto!

A sintaxe

Os filtros personalizados ampliam o conjunto de filtros que você já pode aplicar, como blur ou sepia, aos seus elementos DOM. Eric Bidelman escreveu uma ótima ferramenta de recreação para essas pessoas, que você precisa conferir.

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ê verá que declaramos nossos sombreadores de vértice e fragmento, o número de linhas e colunas em que queremos que nosso elemento DOM seja dividido e os uniformes que queremos transmitir.

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

Envio de pixel

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

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

Isso significa que o "Hello World" dos sombreadores de fragmento tem a seguinte aparência:

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 DOM é multiplicado por css_ColorMatrix, que no caso acima não faz nada igual à matriz de identidade e não muda nenhum dos valores RGBA. Se quiséssemos manter os valores vermelhos, usaríamos um css_ColorMatrix como este:

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

Ao multiplicar os valores de pixel 4D (RGBA) pela matriz, você obtém um valor de pixel manipulado do outro lado e, neste caso, zera os componentes verde e azul.

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

Há muitas maneiras pelas quais essas duas variáveis podem manipular os pixels. Consulte a especificação do recurso Efeitos de filtro para entender melhor como os modos de combinação e composição interagem.

Criação de vértices

No WebGL, nós assumimos total responsabilidade pela criação dos pontos 3D da nossa malha. No entanto, nos filtros personalizados, você só precisa especificar o número de linhas e colunas que deseja e o navegador dividirá automaticamente o seu conteúdo DOM em vários triângulos:

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

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

Um efeito de acordeão
Uma imagem sendo deformada por um efeito de acordeão

Como animar com sombreadores

Trazer animações aos sombreadores é 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);
}

Observe que o código acima diminui o tempo de 0 para 1 durante a transição. Dentro do sombreador, podemos declarar o uniforme time e usar o valor atual:

    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 e os efeitos incríveis que você pode criar são difíceis (e, em alguns casos, impossíveis) sem eles. Ainda estamos no início, e as coisas estão mudando, mas adicioná-las adicionará um pouco de espetáculo aos seus projetos. Então, por que não experimentá-los?

Outros recursos