Einführung in benutzerdefinierte Filter (auch CSS-Shader genannt)

Paul Lewis

Mit benutzerdefinierten Filtern oder CSS-Shadern, wie sie früher genannt wurden, können Sie die Leistungsfähigkeit der WebGL-Shader für Ihren DOM-Inhalt nutzen. Da die in der aktuellen Implementierung verwendeten Shader praktisch dieselben sind wie die in WebGL, müssen Sie einen Schritt zurücktreten und einige 3D-Terminologie und einen kleinen Teil der Grafikpipeline verstehen.

Ich füge eine Aufzeichnung einer Präsentation bei, die ich kürzlich an LondonJS gehalten habe. Im Video erkläre ich Ihnen die 3D-Terminologie, die Sie kennen sollten, welche Variablentypen Sie kennen und wie Sie benutzerdefinierte Filter einsetzen können. Sie sollten auch die Folien zur Hand nehmen, damit Sie die Demos selbst ausprobieren können.

Einführung in Shaders

Ich habe bereits eine Einführung in Shader geschrieben, in der erklärt wird, was Shader sind und wie Sie sie aus WebGL-Perspektive verwenden können. Wenn Sie sich noch nie mit Shadern befasst haben, sollten Sie sich erst einmal informieren, bevor Sie noch weiter gehen, da viele der Konzepte und Sprachen für benutzerdefinierte Filter von der vorhandenen WebGL-Shader-Terminologie abhängen.

Jetzt müssen Sie nur noch benutzerdefinierte Filter aktivieren.

Benutzerdefinierte Filter aktivieren

Benutzerdefinierte Filter sind sowohl in Chrome als auch in Canary und in Chrome für Android verfügbar. Gehen Sie einfach zu about:flags, suchen Sie nach „CSS-Shader“, aktivieren Sie sie und starten Sie den Browser neu. Jetzt kann es losgehen!

Die Syntax

Mit benutzerdefinierten Filtern wird eine Reihe von Filtern erweitert, die Sie bereits auf Ihre DOM-Elemente anwenden können, z. B. blur oder sepia. Eric Bidelman hat ein sehr gutes Playground-Tool entwickelt.

Verwenden Sie die folgende Syntax, um einen benutzerdefinierten Filter auf ein DOM-Element anzuwenden:

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

Sie sehen hier, dass wir die Vertex- und Fragment-Shaders, die Anzahl der Zeilen und Spalten, in die das DOM-Element aufgeschlüsselt werden soll, und alle Uniformen, die wir durchlaufen möchten, deklarieren.

Ein letzter Hinweis: Wir verwenden die mix()-Funktion um den Fragment-Shader mit einem Mischmodus (normal) und einem zusammengesetzten Modus (source-atop). Werfen wir nun einen Blick auf den Fragment-Shader selbst, um zu sehen, warum wir überhaupt eine mix()-Funktion benötigen.

Pixel wird geschoben

Wenn Sie mit den Shadern von WebGL vertraut sind, werden Sie feststellen, dass bei benutzerdefinierten Filtern die Dinge ein wenig anders sind. Zum einen erstellen wir nicht die Texturen, die unser Fragment-Shader zum Füllen der Pixel verwendet. Stattdessen wird der DOM-Inhalt, auf den der Filter angewendet wird, automatisch einer Textur zugeordnet. Das bedeutet zwei Dinge:

  1. Aus Sicherheitsgründen können einzelne Pixel-Farbwerte der DOM-Textur nicht abgefragt werden.
  2. Wir legen die endgültige Pixelfarbe (zumindest in den aktuellen Implementierungen) nicht selbst fest, d.h. gl_FragColor ist nicht erlaubt. Es wird vielmehr angenommen, dass Sie den DOM-Inhalt rendern möchten und dazu die Pixel indirekt über css_ColorMatrix und css_MixColor bearbeiten.

Das bedeutet, dass unsere Hello World of Fragment-Shader etwa so aussehen:

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

Jedes Pixel des DOM-Inhalts wird mit css_ColorMatrix multipliziert, was im obigen Fall keine Auswirkungen auf die Identitätsmatrix hat und keinen der RGBA-Werte ändert. Wenn wir dies möchten, würden wir z. B. die roten Werte beibehalten, die css_ColorMatrix wie folgt verwenden:

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

Wenn Sie die 4D-Pixelwerte (RGBA) mit der Matrix multiplizieren, erhalten Sie hoffentlich einen manipulierten Pixelwert auf der anderen Seite. In diesem Fall werden die grünen und blauen Komponenten auf null gesetzt.

css_MixColor wird hauptsächlich als Grundfarbe verwendet, die Sie gut in den DOM-Inhalt einfügen möchten. Das Mischen erfolgt über die Mischmodi, die du bereits von Art-Paketen kennst: Overlay, Bildschirm, Farbausweichung, hartes Licht und so weiter.

Diese beiden Variablen können die Pixel auf verschiedene Weise beeinflussen. Sehen Sie sich die Spezifikation für Filtereffekte an, um sich ein besseres Bild von der Interaktion zwischen dem Misch- und dem zusammengesetzten Modus zu machen.

Vertex-Erstellung

In WebGL übernehmen wir die volle Verantwortung für die Erstellung der 3D-Punkte unseres Mesh-Netzwerks. Bei benutzerdefinierten Filtern müssen Sie jedoch nur die gewünschte Anzahl der Zeilen und Spalten angeben. Der Browser teilt den DOM-Inhalt dann automatisch in eine Reihe von Dreiecken auf:

Vertex-Erstellung
Ein Bild wird in Zeilen und Spalten aufgeschlüsselt

Jeder dieser Eckpunkte wird dann zur Bearbeitung an unseren Vertex-Shader übergeben, sodass wir beginnen können, sie nach Bedarf im 3D-Raum zu verschieben. Du kannst schon bald einige fantastische Effekte erzielen!

Ein Akkordeon-Effekt
Bild wird durch einen Akkordeoneffekt verzerrt

Animationen mit Shadern

Animationen für Ihre Shader sind das, was sie lustig und ansprechend macht. Verwenden Sie dazu einfach einen Übergang (oder eine Animation) in Ihrem CSS-Code, um einheitliche Werte zu aktualisieren:

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

Im Code oben ist also zu beachten, dass sich die Zeit während der Umstellung von 0 nach 1 verkürzt. Innerhalb des Shaders können wir die einheitliche time deklarieren und den aktuellen Wert verwenden:

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

Fang an zu spielen!

Benutzerdefinierte Filter machen einfach Spaß, und die erstaunlichen Effekte, die Sie erstellen können, sind ohne sie nur schwer (und manchmal gar nicht möglich). Wir stehen erst am Anfang und einiges ändert sich noch, aber wenn Sie sie in Ihre Projekte einbauen, werden sie auch ein wenig Showbiz gebracht. Probieren Sie es doch einfach mal aus.

Weitere Ressourcen