Introducción a los filtros personalizados (también conocidos como sombreadores CSS)

Los filtros personalizados, o sombreadores CSS, como solían llamarse, te permiten usar la potencia de los sombreadores de WebGL con tu contenido del DOM. Dado que en la implementación actual los sombreadores utilizados son prácticamente los mismos que los de WebGL, debes dar un paso atrás y comprender cierta terminología 3D y un poco de la canalización de gráficos.

Incluí una versión grabada de una presentación que dije recientemente a LondonJS. En el video, explicaré una descripción general de la terminología 3D que debes comprender, cuáles son los diferentes tipos de variables que encontrarás y cómo puedes comenzar a jugar con los filtros personalizados hoy mismo. También debes tomar las diapositivas para poder jugar con las demostraciones.

Introducción a sombreadores

Anteriormente, escribí una introducción a los sombreadores que te brindará un buen desglose de qué son los sombreadores y cómo puedes usarlos desde el punto de vista de WebGL. Si nunca trataste con sombreadores, es una lectura obligatoria antes de avanzar mucho más, porque muchos de los conceptos y lenguajes de filtros personalizados dependen de la terminología existente del sombreador de WebGL.

Dicho esto, habilitemos los filtros personalizados y a disfrutar.

Habilitación de filtros personalizados

Los filtros personalizados están disponibles en Chrome y Canary, así como en Chrome para Android. Simplemente ve a about:flags, busca "Sombreadores de CSS", habilítalos y reinicia el navegador. Ya estás listo para continuar.

Sintaxis

La sección Filtros personalizados amplía el conjunto de filtros que ya puedes aplicar, como blur o sepia, a tus elementos del DOM. Eric Bidelman escribió una excelente herramienta de zona de juegos para esas personas, que deberías consultar.

Para aplicar un filtro personalizado a un elemento del DOM, debes usar la siguiente sintaxis:

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

Verás que declaramos nuestros sombreadores de vértices y fragmentos, la cantidad de filas y columnas en las que queremos que se divida nuestro elemento DOM y, luego, los uniformes que queremos pasar.

Por último, debemos destacar que usamos la función mix() en torno al sombreador de fragmentos con un modo de combinación (normal) y un modo compuesto (source-atop). Veamos el sombreador de fragmentos para ver por qué necesitamos una función mix().

Reconocimiento de píxeles

Si estás familiarizado con los sombreadores de WebGL, notarás que, en los Filtros personalizados, las cosas son un poco diferentes. Por un lado, no creamos las texturas que nuestro sombreador de fragmentos usa para completar los píxeles. En su lugar, el contenido del DOM al que se aplicó el filtro se asigna automáticamente a una textura, lo que implica dos cosas:

  1. Por motivos de seguridad, no podemos consultar valores de color de píxeles individuales de la textura del DOM.
  2. Nosotros no establecemos el color de píxel final (al menos en las implementaciones actuales), es decir, no aplicamos gl_FragColor. En su lugar, se supone que querrás renderizar el contenido del DOM, y lo que puedes hacer es manipular sus píxeles indirectamente con css_ColorMatrix y css_MixColor.

Eso significa que nuestro Hello World de sombreadores de fragmentos se ve más de la siguiente manera:

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 píxel del contenido del DOM se multiplica por css_ColorMatrix, que, en el caso anterior, no hace nada como su matriz de identidad y no cambia ninguno de los valores RGBA. Si quisiéramos, por ejemplo, mantener los valores rojos, usaríamos un css_ColorMatrix como el siguiente:

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

Con suerte, cuando multiplicas los valores de píxeles 4D (RGBA) por la matriz, se obtiene un valor de píxeles manipulado del otro lado y, en este caso, uno que pone en cero los componentes verdes y azules.

css_MixColor se usa principalmente como un color de base que deseas combinar con el contenido del DOM. La mezcla se realiza a través de los modos de fusión con los que ya estarás familiarizado con los paquetes de arte: superposición, pantalla, esquivar colores, luz intensa y más.

Existen muchas maneras en las que estas dos variables pueden manipular los píxeles. Consulta la especificación de Efectos de filtro para entender mejor cómo interactúan los modos de combinación y compuesto.

Creación de Vertex

En WebGL, asumimos la responsabilidad total de la creación de los puntos 3D de nuestra malla, pero en Filtros personalizados, lo único que debes hacer es especificar el número de filas y columnas que deseas, y el navegador dividirá automáticamente tu contenido del DOM en varios triángulos:

Creación de Vertex
Una imagen que se divide en filas y columnas

Luego, cada uno de esos vértices se pasa a nuestro sombreador de vértices para su manipulación, lo que significa que podemos comenzar a moverlos en un espacio 3D según sea necesario. Pronto podrás crear efectos increíbles.

Un efecto de acordeón
Una imagen deformada por un efecto de acordeón

Cómo animar con sombreadores

La incorporación de animaciones a tus sombreadores es lo que los hace divertidos y atractivos. Para ello, usa una transición (o animación) en tu CSS para actualizar los 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);
}

Por lo tanto, lo que debes observar en el código anterior es que el tiempo se reducirá de 0 a 1 durante la transición. Dentro del sombreador, podemos declarar el time uniforme y usar cualquier valor actual:

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

¡Comienza a jugar!

Los Filtros personalizados son muy divertidos, y los increíbles efectos que puedes crear son difíciles (y, en algunos casos, imposibles) sin ellos. Aún estamos en las primeras etapas y las cosas están cambiando bastante, pero agregarlas agregará un poco de espectáculo a tus proyectos, así que ¿por qué no los intentas?

Recursos adicionales