Thursday, February 26, 2015

2.5D Sun In Depth Part 3: The Flares


The Flares:

The flares are the purple bottom most model in the above picture.

The model is a bunch of evenly spaced quads all aligned facing outward from the center of the sun.  The vertex color of each quad has been randomly set in 3ds Max before export to offset the animation cycle for each quad.


The textures were referenced from the ink section of CG Textures.  I built an alpha (as many of the inks are different colors) and the used a gradient map to make the color.

This  is a super simple pixel shader with a little bit of vertex shader magic to give it some motion.  People often underestimate the animation that can be done in a vertex shader.  I remember when Raven added vertex modulation to the Unreal material editor long before Epic added it standard.  I got into a lot of trouble making crazy vertex animations back then.  To see some real vertex shader magic check out Jonathan Lindquist's GDC presentation about the work he's done on Fortnite.  I would recommend the video if you have access to the GDC vault.

The Shader

Cull Off

This is one of 2 parameters that is different from the sun edge shader and it's purpose is to make sure that all faces of the material are drawn, front and back faces alike.  The reason for this parameter is so I can flip some of the quads left and right to get some variation and not have it go invisible because it's facing the wrong way.

Blend SrcAlpha One

This is one of my favorite blend modes because it takes any alpha blended shader you have and makes it additive.

struct Input {
float2 uv_MainTex;
float fade;
};

I've talked about the struct input in the sun surface post.  Here is where you see the actual usefulness of vertex interpolators.  In that first line I am passing into the pixel shader the uvs for the main texture.  In the second line I am passing in a custom float value.  This is a value that could be expensive to calculate on the pixel level but not need per pixel granularity.

void vert (inout appdata_full v, out Input o) {
UNITY_INITIALIZE_OUTPUT(Input,o);

float scaledTime = _Time.y * 0.03 * ( v.color.x * 0.2 + 0.9 );
float fracTime = frac( scaledTime + v.color.x );
float expand = lerp( 0.6, 1.0, fracTime );
float fade = smoothstep( 0.0, 0.2, fracTime ) * smoothstep( 1.0, 0.5, fracTime );
v.vertex.xyz *= expand;
o.fade = fade;
}

Ahh the vertex shader.  This is where most of the work is done.

inout appdata_full - This means that we are going to be getting all the information we can about the vertex (position, normal, tangent, uv, uv2, and color) and passing it in with a struct whose name is v.

out Input o - This means that the data we return needs to be in the format of our input struct and it's name is o.

UNITY_INITIALIZE_OUTPUT(Input,o); - This just sets up most of the busy work that the default vertex shader would normally do.  In this case the only thing is does is transform the MainTex uv coordinates.

float scaledTime = _Time.y * 0.03 * ( v.color.x * 0.2 + 0.9 ); - Now lets do some work!  This takes the global shader parameter _Time.y (which is the seconds that have passed since you started playing your level) and then scales it down and multiplies it by the red channel of the vertex color which has been mapped to be between  0.9 and 1.1.  Now each flare is doing it's thing at a slightly different rate!

float fracTime = frac( scaledTime + v.color.x ); - We take the scaled time and add the red channel of the vertex color to it and then take the frac of that.  This creates a repeating gradation from 0-1 that is different for each flare.

float expand = lerp( 0.6, 1.0, fracTime ); - Here we take the fracTime which is 0-1 and lerp it between 0.6 and 1.0.  That means that the scale of the flares will not drop between 0.6.

float fade = smoothstep( 0.0, 0.2, fracTime ) * smoothstep( 1.0, 0.5, fracTime ); - This is for fading the flares on and off over there animation cycle.  The start and 0, go up to 1, and end at 0 and then they repeat!

v.vertex.xyz *= expand; - This is where that vertexes actually move.  Multiplying a vertexe's xyz position is the same as scaling it.  So the model is  getting scaled between 0.6 and 1.0 over the animation cycle, except each flare is doing it at a different time.

o.fade = fade; - Just pass that fade value to the pixel shader so it knows to fade the flare on and off.  This is the last piece of the input struct as UNITY_INITIALIZE_OUTPUT already took care of uv_MainTex.

void surf (Input IN, inout SurfaceOutput o) {
half4 mainTex = tex2D (_MainTex, IN.uv_MainTex);

o.Albedo = 0.0;
o.Emission = mainTex * _Factor;
o.Alpha = mainTex.w * IN.fade;
}

This is as simple as a pixel shader can get.  Multiply the main texture by the _Factor parameter, and multiply the alpha by the fade parameter we passed in from the vertex shader.  And that's all there is to it!  The end result looks like the image below.  Notice how some flares are larger and further out than others.  This is due to adding the vertex color to the time.

Final Sun Flare Material



1 comment: