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:
- Por motivos de segurança, não podemos consultar valores de cor de pixel individuais da textura do DOM.
- 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 usandocss_ColorMatrix
ecss_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:
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!
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?