Présentation des filtres personnalisés (ou nuanceurs CSS)

Les filtres personnalisés, ou nuanceurs CSS, vous permettent d'exploiter la puissance des nuanceurs WebGL avec votre contenu DOM. Étant donné que les nuanceurs utilisés dans l'implémentation actuelle sont pratiquement identiques à ceux de WebGL, vous devez prendre du recul et comprendre la terminologie 3D et un peu le pipeline graphique.

J'ai joint la version enregistrée d'une présentation que j'ai récemment faite à LondonJS. Dans cette vidéo, je vous présente la terminologie 3D que vous devez comprendre, les différents types de variables que vous rencontrerez et la façon dont vous pouvez commencer à utiliser les filtres personnalisés dès aujourd'hui. Nous vous conseillons également de prendre les diapositives pour pouvoir suivre vous-même les démonstrations.

Présentation des nuanceurs

J'ai déjà écrit une introduction aux nuanceurs qui vous expliquera en quoi consistent les nuanceurs et comment les utiliser du point de vue WebGL. Si vous n'avez jamais travaillé avec les nuanceurs, cette lecture est obligatoire avant d'aller plus loin, car de nombreux langages et concepts liés aux filtres personnalisés dépendent de la terminologie des nuanceurs WebGL existante.

Ceci étant dit, activons les filtres personnalisés et explorons tout ce que vous devez faire !

Activation des filtres personnalisés

Les filtres personnalisés sont disponibles dans Chrome et Canary, ainsi que dans Chrome pour Android. Il vous suffit d'accéder à about:flags, de rechercher "CSS Shaders", de les activer et de redémarrer le navigateur. Et voilà !

La syntaxe

Les filtres personnalisés étendent l'ensemble des filtres que vous pouvez déjà appliquer (blur ou sepia, par exemple) à vos éléments DOM. Eric Bidelman a écrit un excellent outil de jeu pour eux, que vous devriez essayer.

Pour appliquer un filtre personnalisé à un élément DOM, utilisez la syntaxe suivante:

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

À partir de là, nous déclarons nos nuanceurs de sommets et de fragments, le nombre de lignes et de colonnes dans lesquelles nous voulons que notre élément DOM soit décomposé, et enfin les variables uniformes que nous voulons transmettre.

Une dernière chose à souligner ici est que nous utilisons la fonction mix() autour du nuanceur de fragments avec un mode de combinaison (normal) et un mode composite (source-atop). Examinons le nuanceur de fragments lui-même pour comprendre pourquoi nous avons même besoin d'une fonction mix().

Pixel push

Si vous connaissez les nuanceurs WebGL, vous remarquerez que les filtres personnalisés ont un aspect légèrement différent. D'une part, nous ne créons pas la ou les textures que notre nuanceur de fragments utilise pour remplir les pixels. Au contraire, le contenu DOM auquel le filtre est appliqué est mappé automatiquement à une texture, ce qui signifie deux choses:

  1. Pour des raisons de sécurité, nous ne pouvons pas interroger les valeurs de couleur de pixel individuelles de la texture du DOM.
  2. Nous ne définissons pas nous-mêmes la couleur finale du pixel (du moins dans les implémentations actuelles), car gl_FragColor n'est pas autorisé. Nous partons du principe que vous souhaitez afficher le contenu DOM, et que vous devez manipuler ses pixels indirectement via css_ColorMatrix et css_MixColor.

Cela signifie que l'application Hello World des nuanceurs de fragments ressemble davantage à ceci:

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

Chaque pixel du contenu DOM est multiplié par le css_ColorMatrix, qui, dans le cas ci-dessus, n'a aucun effet en tant que matrice d'identité et ne modifie aucune des valeurs RVBA. Si nous voulions conserver les valeurs en rouge, par exemple, nous utiliserions un css_ColorMatrix comme ceci:

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

Vous pouvez voir qu'en multipliant les valeurs de pixels 4D (RVBA) par la matrice, vous obtenez une valeur de pixel manipulée de l'autre côté, et dans ce cas, une valeur qui met à zéro les composants vert et bleu.

css_MixColor est principalement utilisé comme couleur de base à associer à votre contenu DOM. Pour ce faire, vous devez utiliser les modes de fusion que vous avez l'habitude d'utiliser: superposition, écran, esquive des couleurs, lumière crue, etc.

Ces deux variables peuvent manipuler les pixels de nombreuses façons. Nous vous invitons à consulter la spécification des effets de filtre pour mieux gérer l'interaction des modes de combinaison et de composite.

Création de sommets

Dans WebGL, nous assumons l'entière responsabilité de la création des points 3D de notre maillage. Toutefois, dans les filtres personnalisés, il vous suffit de spécifier le nombre de lignes et de colonnes souhaité. Le navigateur décomposera automatiquement votre contenu DOM en plusieurs triangles:

Création de sommets
Image divisée en lignes et en colonnes

Chacun de ces sommets est ensuite transmis à notre nuanceur de sommets pour être manipulé, ce qui signifie que nous pouvons commencer à les déplacer dans l'espace 3D selon nos besoins. Il n'y a pas longtemps avant de pouvoir réaliser de fabuleux effets !

Un effet accordéon
Image déformée par un effet accordéon

Créer des animations avec les nuanceurs

Intégrer des animations à vos nuanceurs est ce qui les rend amusantes et attrayantes. Pour ce faire, il vous suffit d'utiliser une transition (ou une animation) dans votre code CSS pour mettre à jour les valeurs 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);
}

La chose à noter dans le code ci-dessus est que le temps va passer de 0 à 1 pendant la transition. Dans le nuanceur, nous pouvons déclarer la variable uniforme time et utiliser sa valeur actuelle:

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

À vous de jouer !

Les filtres personnalisés sont très amusants, et les effets incroyables que vous pouvez créer sont difficiles (voire impossibles) sans eux. Nous n'en sommes qu'aux prémices et les choses changent un peu, mais en les ajoutant, vous ajouterez un peu de showbiz à vos projets, alors pourquoi ne pas essayer ?

Autres ressources