Wie implementiere ich einen Farbwechsel-Fragment-Shader?

7

Ich habe einen Hintergrund einer bestimmten Größe und bin mit einer bestimmten Farbe gefüllt. Ich möchte es mit einem Animationseffekt ändern, der von der Mitte aus beginnt und sich ausbreitet, bis er den gesamten Hintergrund erweitert. Die neue Farbe sollte vom Hintergrund aus sanft in die vorhandene Farbe übergehen. Eine Art radialer Farbverlauf, der die Farbe ändert und sich dann über den gesamten Hintergrund ausbreitet.

Ich arbeite mit SpriteKit unter iOS und bin mir wirklich sicher, dass der beste Weg, dies zu implementieren, darin besteht, dies mit Fragment-Shadern zu tun, die neu im iOS 8 SpriteKit SDK sind. Ich habe einige Arbeiten mit Shadern gemacht und verstehe, wie sie funktionieren, aber ich bitte um Hilfe, mehr über die Mathematik dahinter.

Simon Kemper
quelle
Was hast du versucht?
Konzept3d
Noch nichts. Ich habe gerade mit Blooming-FX und "Plasma FX" gearbeitet, aber ich kann mir wirklich keine Vorstellung von einem Ausgangspunkt dafür machen ...
Simon Kemper
Vielleicht finden Sie meine Frage und ihre Antwort auf stackoverflow hilfreich. Es wird erläutert, wie ein nahezu perfekter Farbverlauf erzielt wird (linear, radial, elliptisch, Verschiebung, Skalierung und Drehung je nach Eingabe). Kann auch auf einen Multi-Stop-Gradienten erweitert werden.
Wondra
Auch eine gute Quelle, um einen Ausgangspunkt zu finden! Vielen Dank!
Simon Kemper

Antworten:

11

Hier ist, wie ich das machen würde. Stellen Sie zunächst sicher, dass Ihnen die UVs oder Weltkoordinaten des Objekts (die Sie von Ihrem Vertex-Shader aus durchlaufen können) zur Verfügung stehen. Wenn es sich nur um einen Hintergrund handelt, können Sie auch nur Fragmentkoordinaten ( gl_FragCoord) verwenden.

Nehmen wir zum Beispiel an, wir verwenden UV-Koordinaten. Ein Fragment-Shader mit nur: gl_FragColor = vec4(vec3(uv.x),1.0);sieht ungefähr so ​​aus:

X UV

Und ähnlich gl_FragColor = vec4(vec3(uv.y),1.0);wird es aussehen wie:

Y UV

Wenn Sie Weltkoordinaten oder Fragmentkoordinaten verwenden, können Sie trotzdem skalieren und übersetzen, um auf (0,0) -> (1, 1) zu normalisieren. Wir müssen jedoch trotzdem erneut skalieren und übersetzen, damit Sie überspringen können dieser Schritt. Stellen Sie stattdessen sicher, dass Ihre Koordinaten zwischen (-1,0, -1,0) und (1,0, 1,0) liegen, um die nächsten Schritte zu vereinfachen.uv = 2.0 * uv - 1.0;

Bevor wir weiter gehen, machen wir einen supereinfachen radialen Gradienten. Denken Sie daran, dass die Gleichung eines Kreises x² + y² = r² ist. Wenn Sie also die transformierten UVs als Koordinatensystem verwenden, kann ein Einheitskreis (r = 1) erstellt werden, der ungefähr so gl_FragColor = vec4(vec3(uv.x * uv.x + uv.y * uv.y),1.0);aussieht:

R UV

Der nächste Schritt ist die Animation. Dazu müssen Sie nur mit dem Wert "r" herumspielen und den Radius ändern. Sie müssen sich keine Gedanken über das Festklemmen von Farbwerten zwischen 0,0 und 1,0 machen, da dies ohnehin automatisch geschieht.

Alles in allem würde es ungefähr so ​​aussehen:

uniform vec2 screenSize;
uniform float time;

void main(void) {
    // Scale to UV space coords
    vec2 uv = gl_FragCoord.xy / screenSize;

    // Transform to [(-1.0, -1.0), (1.0, 1.0)] range
    uv = 2.0 * uv - 1.0;

    // Have something to vary the radius (can also just be a linear counter (time))
    float wave = sin(time);

    // Calculate how near to the center (0.0) or edge (1.0) this fragment is
    float circle = uv.x * uv.x + uv.y * uv.y;

    // Put it all together
    gl_FragColor = vec4(vec3(circle + wave),1.0);
}

Natürlich können Sie viele Dinge vereinfachen und viel mehr mit den Variablen und Konstanten herumspielen. Hier ist eine Live-Demo von mir, die genau das tut: https://www.shadertoy.com/view/4sjXWh

Bearbeiten:

Wenn Sie dies mit verschiedenen Farben tun möchten, gibt es zum Glück eine einfache Lösung! Übergeben Sie Ihre Farben als Uniformen (oder codieren Sie sie im eigentlichen Fragment-Shader fest) und ändern Sie die letzte Zeile in folgende:

gl_FragColor = mix(color1, color2, circle + wave);

color1und color2sind beide vec4s. Hier ist das in Aktion: https://www.shadertoy.com/view/XsjSW1

Yousef Amar
quelle
1
Brillant! Das ist ein guter Punkt für mich, um loszulegen! Aber anstatt den Farbverlauf in einer Schleife zu animieren, benötige ich jedes Mal einen neuen Farbverlauf, wenn ich die Farbe ändere und diese Animation über dem alten Farbverlauf starte. Wie Farbverlaufswellen. Ich denke, ich werde diesen Shader als eine Art Maskierung verwenden und ihn auf Sprites anwenden. Und jedes Mal, wenn ich die Farbe ändere, füge ich ein neues Sprite mit dieser Farbe hinzu ...
Simon Kemper
1
Nicht sicher, ob diese beiden ersten Beispiele beide gl_FragColor = vec4 (vec3 (uv.x), 1.0) sein können; ? (Nicht pingelig zu sein - diese Antwort hat mir auch geholfen!)
@Poldie Du hast absolut recht; Das ist ein Tippfehler. Fest!
Yousef Amar
@SimonKemper Sie können die gewünschten Farben auch als Uniformen übergeben und nach Bedarf ändern, wenn ein Animationszyklus beendet ist (indem Sie die timeVariable außerhalb des Shaders überprüfen ). Ich habe meiner Antwort etwas hinzugefügt, um Ihnen zu zeigen, was ich meine. Ich hoffe, Sie finden das nützlich!
Yousef Amar
2

Da ich mit SpriteKit unter iOS arbeite, fehlen der Shader-Unterstützung in SpriteKit noch einige Dinge, um einen animierten Farbverlaufs-Shader in SpriteKit zu implementieren.

Für diejenigen, die in die gleiche Situation geraten, habe ich jedoch einen performanteren Weg gefunden, um mit dem Sprite-Kit animierte Verläufe zu erstellen. Anstatt mit Shadern zu arbeiten, habe ich ein einfaches Bild mit einem verschwommenen Kreis erstellt ... So:

Gradient Sprite

und mit ein paar Zeilen SpriteKit-Code erhalten Sie schöne und gleichmäßige Farbverläufe mit Farbverläufen, beginnend von der Mitte bis über den gesamten Bildschirm!

// Create a Sprite with Gradient-Texture
SKSpriteNode* gradientSpriteNode = [SKSpriteNode spriteNodeWithImageNamed:@"SSGradientMap.png"];

// Set the color and scale properties
gradientSpriteNode.color = color;
gradientSpriteNode.colorBlendFactor = 1.0;
[gradientSpriteNode setScale:0.1];

// Add it to our parent node
[self addChild:gradientSpriteNode];

// Add it to our container
[self.gradientNodes addObject:gradientSpriteNode];

// Create and configure a scale up action
SKAction* scaleAction = [SKAction scaleTo:3.0 duration:3.0];
scaleAction.timingMode = SKActionTimingEaseInEaseOut;

// Run that action and attach a completion block
[gradientSpriteNode runAction:scaleAction completion:^{

    // Create a random color
    float r = [SSHelper randomWithLowerFloat:0. andUpperFloat:1.];
    float g = [SSHelper randomWithLowerFloat:0. andUpperFloat:1.];
    float b = [SSHelper randomWithLowerFloat:0. andUpperFloat:1.];
    UIColor* randomColor = [UIColor colorWithRed:r green:g blue:b alpha:1.0];

    // Create a new gradient ontop of the last one
    [self addGradientWithColor:randomColor];

    // If more than 2 gradient sprites inside our container the last one can be removed
    if (self.gradientNodes.count > 2) {
        SKSpriteNode* gradientNode_ = [self.gradientNodes firstObject];
        [gradientNode_ removeFromParent];
        [self.gradientNodes removeObject:gradientNode_];
    }
}];

Die Idee ist, das Sprite zum Bildschirm hinzuzufügen, aber auf Null zu verkleinern. Eine Animation skaliert es, bis es den gesamten Bildschirm erweitert. Dann fängst du wieder an, aber das Sprite wird mit einer anderen Farbe gemischt.

Simon Kemper
quelle