Inleiding tot aangepaste filters (ook wel CSS-shaders genoemd)

Met aangepaste filters, of CSS Shaders zoals ze vroeger werden genoemd, kunt u de kracht van WebGL's shaders gebruiken met uw DOM-inhoud. Omdat in de huidige implementatie de gebruikte shaders vrijwel dezelfde zijn als die in WebGL, moet je een stapje terug doen en wat 3D-terminologie en een klein stukje van de grafische pijplijn begrijpen.

Ik heb een opgenomen versie toegevoegd van een presentatie die ik onlangs bij LondonJS heb afgeleverd. In de video doorloop ik een overzicht van de 3D-terminologie die je moet begrijpen, wat de verschillende typen variabelen zijn die je tegenkomt en hoe je vandaag nog met aangepaste filters kunt gaan spelen. Je moet ook de dia's pakken , zodat je zelf met de demo's kunt spelen.

Inleiding tot Shaders

Ik heb eerder een inleiding tot shaders geschreven, waarin u een goed overzicht krijgt van wat shaders zijn en hoe u ze kunt gebruiken vanuit WebGL-oogpunt. Als u nog nooit met shaders te maken heeft gehad, is dit een verplichte lezen voordat u veel verder gaat, omdat veel van de Custom Filters-concepten en -taal afhangen van de bestaande WebGL-shader-terminologie.

Dus met dat gezegd, laten we aangepaste filters inschakelen en verder gaan!

Aangepaste filters inschakelen

Aangepaste filters zijn beschikbaar in zowel Chrome en Canary als in Chrome voor Android. Ga gewoon naar about:flags en zoek naar "CSS Shaders", schakel ze in en start de browser opnieuw. Nu ben je klaar om te gaan!

De syntaxis

Aangepaste filters breidt de set filters uit die u al kunt toepassen, zoals blur of sepia , op uw DOM-elementen. Eric Bidelman heeft daarvoor een geweldig speelhulpmiddel geschreven, dat je zeker eens moet bekijken.

Om een ​​aangepast filter op een DOM-element toe te passen, gebruikt u de volgende syntaxis:

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

Je zult hieruit zien dat we onze vertex- en fragment-shaders declareren, het aantal rijen en kolommen waarin we ons DOM-element willen laten opsplitsen, en vervolgens alle uniformen waar we doorheen willen.

Een laatste punt dat we hier willen benadrukken is dat we de functie mix() gebruiken rond de fragmentarcering met een overvloeimodus ( normal ) en een samengestelde modus ( source-atop ). Laten we eens kijken naar de fragment-shader zelf om te zien waarom we zelfs een mix() -functie nodig hebben.

Pixel-duwen

Als u bekend bent met de shaders van WebGL, zult u merken dat de zaken bij Aangepaste filters een beetje anders zijn. Ten eerste creëren we niet de textuur(en) die onze fragmentarcering gebruikt om de pixels in te vullen. In plaats daarvan wordt de DOM-inhoud waarop het filter is toegepast automatisch toegewezen aan een textuur, en dat betekent twee dingen:

  1. Om veiligheidsredenen kunnen we geen individuele pixelkleurwaarden van de DOM-textuur opvragen
  2. We stellen (althans in de huidige implementaties) de uiteindelijke pixelkleur niet zelf in, dwz gl_FragColor is verboden terrein. In plaats daarvan wordt aangenomen dat je de DOM-inhoud wilt weergeven, en wat je kunt doen is de pixels ervan indirect manipuleren via css_ColorMatrix en css_MixColor .

Dat betekent dat onze Hello World of fragment shaders er ongeveer zo uitziet:

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

Elke pixel van de DOM-inhoud wordt vermenigvuldigd met de css_ColorMatrix , die in het bovenstaande geval niets doet omdat het de identiteitsmatrix is ​​en geen van de RGBA-waarden verandert. Als we bijvoorbeeld gewoon de rode waarden zouden willen behouden, zouden we een css_ColorMatrix als deze gebruiken:

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

Hopelijk kun je zien dat als je de 4D (RGBA) pixelwaarden vermenigvuldigt met de matrix, je een gemanipuleerde pixelwaarde uit de andere kant krijgt, en in dit geval een die de groene en blauwe componenten op nul zet.

De css_MixColor wordt voornamelijk gebruikt als basiskleur die u, nou ja, wilt mengen met uw DOM-inhoud. Het mixen gebeurt via de overvloeimodi die je kent uit kunstpakketten: overlay, scherm, kleur ontwijken, hard licht enzovoort.

Er zijn tal van manieren waarop deze twee variabelen de pixels kunnen manipuleren. Bekijk de Filtereffecten-specificatie om beter inzicht te krijgen in de interactie tussen de overvloei- en samengestelde modi.

Vertex-creatie

In WebGL nemen we de volledige verantwoordelijkheid voor het maken van de 3D-punten van onze mesh, maar in Custom Filters hoeft u alleen maar het gewenste aantal rijen en kolommen op te geven en de browser zal uw DOM-inhoud automatisch opsplitsen in een aantal driehoeken :

Hoekpunt creatie
Een afbeelding die wordt opgesplitst in rijen en kolommen

Elk van deze hoekpunten wordt vervolgens doorgegeven aan onze hoekpuntarcering voor manipulatie, en dat betekent dat we ze naar behoefte in de 3D-ruimte kunnen verplaatsen. Het duurt niet lang voordat je een aantal fantastische effecten kunt maken!

Een accordeoneffect
Een beeld dat wordt vervormd door een accordeoneffect

Animeren met Shaders

Het toevoegen van animaties aan uw shaders maakt ze leuk en boeiend. Om dat te doen, gebruikt u eenvoudig een overgang (of animatie) in uw CSS om uniforme waarden bij te werken:

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

Het ding om op te merken in de bovenstaande code is dat de tijd tijdens de overgang van 0 naar 1 gaat afnemen. Binnen de shader kunnen we de uniforme time declareren en gebruiken wat de huidige waarde ervan is:

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

Ga spelen!

Aangepaste filters zijn erg leuk om mee te spelen, en de verbluffende effecten die je kunt creëren zijn moeilijk (en in sommige gevallen onmogelijk) zonder deze. Het is nog vroeg en de dingen zijn behoorlijk aan het veranderen, maar als je ze toevoegt, voeg je een beetje showbizz toe aan je projecten, dus waarom zou je ze niet eens proberen?

Aanvullende bronnen