Introduzione ai filtri personalizzati (noti anche come Shader CSS)

Paul Lewis

I filtri personalizzati, o Shader CSS, come prima erano chiamati, consentono di sfruttare la potenza degli mesh di WebGL con i contenuti DOM. Poiché nell'attuale implementazione gli Shader utilizzati sono praticamente uguali a quelli di WebGL, è necessario fare un passo indietro e comprendere la terminologia 3D e un po' della pipeline grafica.

Ho incluso una versione registrata di una presentazione che ho recentemente inviato a LondonJS. Nel video spiego una panoramica della terminologia 3D che è necessario comprendere, quali sono i diversi tipi di variabili che incontrerai e come puoi iniziare a usare i filtri personalizzati oggi stesso. Inoltre, ti consigliamo di afferrare le slide per provare le demo in autonomia.

Introduzione agli Shader

In precedenza ho scritto un'introduzione per gli Shader in cui viene fornita un'analisi dettagliata di cosa sono gli Shader e come utilizzarli dal punto di vista di WebGL. Se non avete mai avuto a che fare con gli shabby, è necessario leggere prima di andare avanti, dato che molti dei concetti e dei linguaggi dei filtri personalizzati sono basati sulla terminologia degli Shadr WebGL esistente.

Detto questo, attiviamo i filtri personalizzati e poi lasciamo il campo.

Attivazione dei filtri personalizzati

I filtri personalizzati sono disponibili sia in Chrome che nella versione Canary e in Chrome per Android. Vai su about:flags e cerca "shader CSS", attivali e riavvia il browser. Ora puoi iniziare.

La sintassi

La funzionalità Filtri personalizzati espande l'insieme di filtri che puoi già applicare, ad esempio blur o sepia, agli elementi DOM. Eric Bidelman ha scritto un ottimo strumento virtuale proprio per questo, che dovresti consultare.

Per applicare un filtro personalizzato a un elemento DOM, utilizza la seguente sintassi:

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

Da qui puoi vedere che dichiariamo i nostri cursori di vertici e frammenti, il numero di righe e colonne in cui vogliamo che venga scomposto l'elemento DOM e poi le eventuali uniformi che vogliamo passare.

Un'ultima cosa da sottolineare è che utilizziamo la funzione mix() intorno allo strumento di fusione dei frammenti con una modalità di fusione (normal) e una modalità composita (source-atop). Diamo un'occhiata allo strumento di shadowing dei frammenti per capire perché è necessaria anche una funzione mix().

Spinta di pixel

Se conosci gli Shadr di WebGL, noterai che nei filtri personalizzati le cose sono leggermente diverse. Per prima cosa, non creiamo le texture utilizzate dal nostro strumento di ombreggiatura dei frammenti per riempire i pixel. Piuttosto, i contenuti DOM a cui viene applicato il filtro vengono automaticamente mappati a una texture, il che significa due cose:

  1. Per motivi di sicurezza, non possiamo eseguire query sui singoli valori dei colori dei pixel della texture DOM
  2. Non impostiamo noi stessi il colore finale dei pixel (almeno nelle implementazioni attuali), ad esempio gl_FragColor non è consentito. Si presume invece che tu voglia eseguire il rendering dei contenuti DOM e manipolare i relativi pixel indirettamente tramite css_ColorMatrix e css_MixColor.

Questo significa che il nostro Hello World di fragment Shader ha un aspetto più simile a questo:

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

Ogni pixel dei contenuti del DOM viene moltiplicato per css_ColorMatrix, che nel caso precedente non fa nulla in quanto è la matrice di identità e non modifica nessuno dei valori RGBA. Se volessi, diciamo, mantieni solo i valori rossi, useresti un css_ColorMatrix come questo:

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

Puoi vedere che moltiplicando i valori dei pixel 4D (RGBA) per la matrice ottieni un valore di pixel manipolato dall'altro lato, e in questo caso azzera i componenti verde e blu.

css_MixColor viene utilizzato principalmente come colore di base che vuoi combinare con i contenuti DOM. Il mix viene eseguito tramite le modalità di fusione che conoscerai con i pacchetti di artwork: overlay, schermo, schermatura del colore, luce intensa e così via.

Queste due variabili possono manipolare i pixel in molti modi. Ti consigliamo di consultare la specifica degli effetti di filtro per avere una gestione migliore dell'interazione delle modalità di fusione e composita.

Creazione di Vertex

In WebGL ci assumiamo la responsabilità completa della creazione dei punti 3D del nostro mesh, ma nei filtri personalizzati è sufficiente specificare il numero di righe e colonne desiderato e il browser suddivide automaticamente il contenuto DOM in un gruppo di triangoli:

Creazione di vertici
Un'immagine suddivisa in righe e colonne

Ognuno di questi vertici viene poi trasmesso al nostro Vertex Shapers per la manipolazione, il che significa che possiamo iniziare a spostarli nello spazio 3D di cui abbiamo bisogno. Preparati a creare effetti davvero fantastici!

Un effetto a fisarmonica
Un'immagine distorta da un effetto a fisarmonica

Animazione con Shader

L'introduzione di animazioni per gli utenti è ciò che li rende divertenti e coinvolgenti. A tale scopo, utilizza semplicemente una transizione (o un'animazione) nel CSS per aggiornare i valori uniformi:

.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 cosa da notare nel codice sopra riportato è che il tempo di attesa si ridurrà da 0 a 1 durante la transizione. All'interno delloshar possiamo dichiarare l'uniforme time e utilizzare il suo valore corrente:

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

Inizia a giocare!

I filtri personalizzati sono molto divertenti e gli incredibili effetti che puoi creare sono difficili (e in alcuni casi impossibili) senza questi filtri. Siamo ancora all'inizio e le cose stanno cambiando molto, ma la loro aggiunta aggiungerà un po' di showbiz ai tuoi progetti, quindi perché non provarli?

Altre risorse