Saturday, February 21, 2015

2.5D Sun In depth Part 1: The Sun

Some people are asking for more info on this asset.  Some people have asked for the asset outright.  While I can't give anyone the whole thing as then Dave wouldn't have an awesome unique sun for his game, I will explain it more in-depth and share the shaders so everyone can learn and make similar assets.  I'll start off with the sun surface and do an entry for each piece.



First off, Here is a better exploded view of the geometry.  From top to bottom is the haze, the ring, the sun, and the flares.  These are pushed much closer together before I export them from max.

The Sun:

The sun is the light blue piece of geometry in the picture above


There is a main texture (_MainTex) which is the color of the sun that I got off of a google image search.
2 fire textures (_FireTex1, _FireTex2) with tiling attributes (_Tiling1, _Tiling2).
A flow texture (_FlowTex) with a speed parameter (_FlowSpeed).
A wave texture (_WaveTex) and it's tiling parameter (_WaveTiling).
And finaly a factor or brightness parameter (_Factor) to control the intensity of the overall shader.

Something you may notice is that I only pass the uv's for the main texture into the pixel shader. The reason for this is because I tend not to use Unity's built in texture transforms. This eats up a lot of interpolators which I would rather have for other things. An interpolator is something that passes information from the vertex shader to the pixel shader. When you see the struct Input... each of those is an interpolator. Usually you can have something like 8 and I have run out on occasion.

float2 uvCoords = IN.uv_MainTex;

Lots of texture look ups are going to use the MainTex uv coords so I just store that off at the begining.

half4 mainTex = tex2D (_MainTex, uvCoords);

The main texture is the sun overlay. The sun overlay texture has the color of the sun in it's rgb channels and a mask to mask out where the fire will go. 
 

float4 flowTex = tex2D (_FlowTex, uvCoords);
flowTex.xy = flowTex.xy * 2.0 - 1.0;
flowTex.y *= -1.0;

The flow texture stores the vectors for direction the suns flames will travel in its x and y channels.  This was made in Crazybump using sun color map.  A normal map and a flow map are very similar since they are both vectors ;)  The z and w channels store a set of texture coords that will be used for the waves.  The z channel is just some low frequency noise and the w is the height from the normal generated by Crazybump.  This map is small and set to be uncompressed.  When dealing with flows and textures as texture coords it's better to have higher color fidelity than resolution.  I flip the y because it wasn't flowing the right way.  This could be done in photoshop before hand as well.


half4 waveTex = tex2D (_WaveTex, flowTex.zw * _WaveTiling.xy + _Time.y * _WaveTiling.zw );

Now we are going to grab that wave texture using the Coords that were stored in the flow map z and w component. The tiling parameters take care of the uv transforms in the pixel shader. Something I do A lot in my shaders is this:

uvCoords * _Tiling.xy + _Time.y * _Tiling.zw

The x and y of the tiling parameter work the same way as Unity's default tiling transforms and the z and w work the same as Unity's offset transforms with the added bonus that it is multiplied by the time which makes the texture scroll over time. This is also the only way to transform a texture who's uv coords derive from another texture.

half wave = waveTex.x * 0.5 + 0.5;

The wave texture is then brightened up as it will later be multiplied into the main texture. this is an optional step.

Other maps used for the sun

float scaledTime = _Time.y * _FlowSpeed + flowTex.z; float flowA = frac( scaledTime ); float flowBlendA = 1.0 - abs( flowA * 2.0 - 1.0 ); flowA -= 0.5; float flowB = frac( scaledTime + 0.5 ); float flowBlendB = 1.0 - abs( flowB * 2.0 - 1.0 ); flowB -= 0.5;

A big chunk! First we save of a variable for the scaled time since it is used twice. The flow texture's z component is added to the time to give it a little temporal distortion and keep the transition from A to B and back again from happening all at once. The flowA and flowB bits are very standard flow map bits of code. Check out Valve's extensive pdf on flow mapping in portal and left for dead for a better understanding of the technique.

half4 fireTex1 = tex2D (_FireTex1, uvCoords * _Tiling1.xy + _Time.y * _Tiling1.zw + ( flowTex.xy * flowA * 0.1 ) );
half4 fireTex2 = tex2D (_FireTex2, uvCoords * _Tiling2.xy + _Time.y * _Tiling2.zw + ( flowTex.xy * flowB * 0.1 ) );

half4 finalFire = lerp( fireTex1, fireTex2, flowBlendB );

Now we are finally looking up the fire textures.  I am using the tiling params to adjust to the repetition and the scrolling and adding the flow texture's x and y channels in multiplied by the flow amounts for each texture.  Then I lerp the 2 textures together based on flowBlendB which was calculated before hand. You actually don't even need flowBlendA.

finalFire = lerp( mainTex.x, finalFire, mainTex.w );

Now I lerp the main textures x channel into the final fire using the main textures alpha channel as a mask.  The fire will be multiplied into the final overlay texture and if you left it full strength it would be too much.  The mask is white in the mid values of the sun color and dark everywhere else, this means that the brights will stay bright and the darks will stay dark.

half4 Final = mainTex * finalFire * _Factor * wave;

All that's left is to multiply everything together!

o.Albedo = 0.0;
o.Alpha = 1.0;
o.Emission = Final.xyz;

This is really an unlit shader so I just set the Albedo to 0 and let the Emission to all the work. Below is the final result of this shader on the sun mesh.  It's not much to look at on its own but it's a good base for the stuff that will be piled on top of it.

The final result of the sun surface shader

No comments:

Post a Comment