Thursday, March 2, 2017

ios - How to implement color changing fragment shader?


I have a background of a given size and filled with a given color. I want to change it with an animation effect, starting from the center and spread out until it extends the whole background. The new color should fade / blend smooth into the existing color from the background. Some kind of radial gradient that changes the color and then spreads out over the whole background.


I am working with SpriteKit on iOS and I am really sure that the best way to implement this is to do this with fragment shaders which are new to iOS 8 SpriteKit SDK. I have done some work with shaders and understand how they work but I am asking for help more on the mathematics behind this.



Answer



Here's how I would do this. First, make sure you have the object's UVs or world coords (which you can pass through from your vertex shader) available to you. If it's just a background, you could also just use fragment coords (gl_FragCoord).


For instance, let's say we're using UV coords. A fragment shader with only: gl_FragColor = vec4(vec3(uv.x),1.0); will look something like:


X UV



And similarly, gl_FragColor = vec4(vec3(uv.y),1.0); will look like:


Y UV


If you're using world coords or fragment coords, you can still scale and translate to normalize to (0,0) -> (1, 1), but we're going to need to scale and translate again anyway so you can skip that step. Make your coords instead range from (-1.0, -1.0) to (1.0, 1.0) to make the next steps easier, like so: uv = 2.0 * uv - 1.0;


Now before we go any further, let's make a super plain radial gradient. Recall that the equation of a circle is x² + y² = r². So using the transformed UVs as a coordinate system, a unit circle (r = 1) can be made with gl_FragColor = vec4(vec3(uv.x * uv.x + uv.y * uv.y),1.0); and will look something like:


R UV


The next step is animation. To do this, you just need to play around with the "r" value and change the radius. You don't need to worry about clamping color values between 0.0 and 1.0 because that happens automatically anyway.


All together it would look something like this:


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

Of course you can simplify a lot of things and play around much more with the variables and constants. Here's a live demo of me doing just that: https://www.shadertoy.com/view/4sjXWh


Edit:


If you want to do this with different colors, fortunately there's an easy solution! Pass in your colors as uniforms (or hardcode them in the actual fragment shader) and change the last line to this:



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

color1 and color2 are both vec4s. Here's that in action: https://www.shadertoy.com/view/XsjSW1


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...