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

Paul Lewis

Mit benutzerdefinierten Filtern, früher CSS-Shader genannt, können Sie die Shader von WebGL für Ihre DOM-Inhalte verwenden. Da in der aktuellen Implementierung die verwendeten Shader praktisch mit denen in WebGL identisch sind, müssen Sie einen Schritt zurücktreten und sich mit einigen 3D-Begriffen und der Grafikpipeline vertraut machen.

Ich habe eine aufgezeichnete Version einer Präsentation beigefügt, die ich vor Kurzem bei LondonJS gehalten habe. Im Video gebe ich einen Überblick über die 3D-Terminologie, die Sie kennen sollten, die verschiedenen Variablentypen, die Sie finden, und wie Sie noch heute mit benutzerdefinierten Filtern beginnen können. Sie sollten sich auch die Folien herunterladen, damit Sie die Demos selbst ausprobieren können.

Einführung in Shader

Ich habe bereits eine Einführung in Shader geschrieben, in der ich erkläre, was Shader sind und wie Sie sie aus WebGL-Sicht verwenden können. Wenn Sie noch nie mit Shadern gearbeitet haben, sollten Sie sich diese Informationen durchlesen, bevor Sie fortfahren. Viele der Konzepte und Begriffe für benutzerdefinierte Filter basieren auf der WebGL-Shader-Terminologie.

Aktivieren wir also benutzerdefinierte Filter und fahren wir fort.

Benutzerdefinierte Filter aktivieren

Benutzerdefinierte Filter sind sowohl in Chrome und Canary als auch in Chrome für Android verfügbar. Rufe einfach about:flags auf, suche nach „CSS-Shader“, aktiviere sie und starte den Browser neu. Jetzt kannst du loslegen.

Syntax:

Mit benutzerdefinierten Filtern können Sie die Anzahl der Filter erweitern, die Sie bereits auf Ihre DOM-Elemente anwenden können, z. B. blur oder sepia. Eric Bidelman hat ein tolles Playground-Tool für diese Zwecke entwickelt, das Sie sich ansehen sollten.

Wenn Sie einen benutzerdefinierten Filter auf ein DOM-Element anwenden möchten, verwenden Sie die folgende Syntax:

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

Wie Sie sehen, deklarieren wir hier unsere Vertex- und Fragment-Shader, die Anzahl der Zeilen und Spalten, in die unser DOM-Element unterteilt werden soll, und dann alle Uniforms, die wir weitergeben möchten.

Wir verwenden die mix()-Funktion im Fragment-Shader mit einem Mischungsmodus (normal) und einem Kompositionsmodus (source-atop). Sehen wir uns den Fragment-Shader selbst an, um zu verstehen, warum wir überhaupt eine mix()-Funktion benötigen.

Pixel Pushing

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

  1. Aus Sicherheitsgründen können wir keine einzelnen Pixelfarbwerte der DOM-Textur abfragen.
  2. Wir legen die endgültige Pixelfarbe (zumindest in den aktuellen Implementierungen) nicht selbst fest, d.h. gl_FragColor ist nicht zulässig. Es wird vielmehr davon ausgegangen, dass Sie den DOM-Inhalt rendern möchten, und Sie können die Pixel indirekt über css_ColorMatrix und css_MixColor bearbeiten.

Das bedeutet, dass unser Hello World-Fragment-Shader in etwa so aussieht:

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 der css_ColorMatrix multipliziert. Da es sich dabei im obigen Fall um die Identitätsmatrix handelt, geschieht nichts und keiner der RGBA-Werte wird geändert. Wenn wir beispielsweise nur die roten Werte beibehalten möchten, verwenden wir eine css_ColorMatrix so:

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

Sie sehen hoffentlich, dass Sie durch Multiplizieren der 4D-Pixelwerte (RGBA) mit der Matrix einen manipulierten Pixelwert auf der anderen Seite erhalten, in diesem Fall einen, bei dem die grünen und blauen Komponenten auf Null gesetzt werden.

Die css_MixColor wird hauptsächlich als Grundfarbe verwendet, die Sie mit Ihren DOM-Inhalten mischen möchten. Die Mischung erfolgt über die Mischmodi, die Sie aus Art-Paketen kennen: Overlay, Screen, Color Dodge, Hard Light usw.

Es gibt viele Möglichkeiten, wie diese beiden Variablen die Pixel beeinflussen können. In der Spezifikation für Filtereffekte erfahren Sie mehr darüber, wie die Modi „Mischmodus“ und „Kompositmodus“ zusammenwirken.

Vertex Creation

In WebGL sind wir für die Erstellung der 3D-Punkte des Meshs verantwortlich. Bei benutzerdefinierten Filtern müssen Sie lediglich die gewünschte Anzahl von Zeilen und Spalten angeben. Der Browser gliedert dann automatisch Ihre DOM-Inhalte in eine Reihe von Dreiecken auf:

Vertex-Erstellung
Ein Bild, das in Zeilen und Spalten unterteilt ist

Jeder dieser Vertex wird dann zur Bearbeitung an unseren Vertex-Shader übergeben. Das bedeutet, dass wir sie nach Bedarf im 3D-Raum verschieben können. Bald schon könnt ihr tolle Effekte erstellen.

Akkordeoneffekt
Ein Bild, das durch einen Akkordeoneffekt verzerrt wird

Mit Shadern animieren

Shader mit Animationen sind unterhaltsam und ansprechend. Dazu verwenden Sie einfach einen Übergang (oder eine Animation) in Ihrem CSS, 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 obigen Code ist zu beachten, dass die Zeit während der Umstellung von 0 auf 1 abnimmt. Im Shader können wir die Uniform 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;
}

Jetzt spielen

Mit benutzerdefinierten Filtern kannst du tolle Effekte erzielen, die ohne sie nur schwer (und in einigen Fällen gar nicht) zu erreichen wären. Es ist noch früh und die Dinge ändern sich ziemlich, aber wenn Sie sie hinzufügen, verleihen Sie Ihren Projekten ein wenig Glamour. Warum also nicht mal ausprobieren?

Zusätzliche Ressourcen