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



Sunday, February 22, 2015

2.5D Sun In Depth Part 2: The Edge Ring




The Edge Ring:

The edge ring is the orange model in the image above. The shader is much simpler than the sun surface so this will be a shorter entry.

The Shader

There is 1 main texture and 2 tiling parameters for it, 3 fire textures with tiling parameters, and a factor parameter to ramp the whole thing up and down.

Tags {
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
}


There are more tags and extra bits of text at the beginning because it is a blended shader and needs to have a bit more information about how it is supposed the be drawn.

The Queue is when in the rendering pipeline this material should be drawn. A value of transparent makes it drawn after all off the opaque stuff has been drawn.

Setting IgnoreProjector to True keeps projector entities from trying to draw on this surface.

Setting RenderType to Transparent is useful for shader replacement which I'm not really worried about here. More info about subshader tags is available in the unity manual.

LOD 200
ZWrite Off
ZTest LEqual
Blend SrcAlpha OneMinusSrcAlpha


LOD has to do with limiting what shaders can be used from script. Idealy you would make a a few shaders with different LODs and fallback to them if performance was an issue.
ZWrite Off: don't write to the depth buffer.
ZTest LEqual: only draw if this surface is in front of opaque geometry (if it's Z value is less than or equal to the Z value of the depth buffer)
Blend SrcAlpha OneMinusSrcAlpha: This is for alpha blending, more blending modes are described in the unity manual

half4 mainTex = tex2D (_MainTex, IN.uv_MainTex * _MainTiling1.xy + _Time.y * _MainTiling1.zw );
half4 mainTex2 = tex2D (_MainTex, IN.uv_MainTex * _MainTiling2.xy + _Time.y * _MainTiling2.zw );

In these lines I just grab the main texture twice with different tiling parameters. The values for the first tiling parameter are (3,1,-0.01,0) this will tile the texture 3 times horizontally, once vertically, and scroll it ever so slowly horizontally. The values for the second parameter are (5,1,0.01,0) which tiles it a little more and scrolls it in the opposite direction.



half4 fireTex1 = tex2D (_FireTex1, IN.uv_MainTex * _Tiling1.xy + _Time.y * _Tiling1.zw ); 
half4 fireTex2 = tex2D (_FireTex2, IN.uv_MainTex * _Tiling2.xy + _Time.y * _Tiling2.zw ); 
half4 fireTex3 = tex2D (_FireTex3, IN.uv_MainTex * _Tiling3.xy + _Time.y * _Tiling3.zw );

In these lines I grab the 3 fire textures with their tiling and scrolling properties. these textures all tile multiple times horizontally and scroll up vertically with slight horizontal scrolling to help hide repeating patterns.  The first fire texture is the same as the wave texture from the sun surface, the second is similar but modifies slightly, and the third is like some stringy fire.




mainTex = ( mainTex2 + mainTex ) * 0.5;

I Add the 2 main textures together and multiply them by 0.5. This would be the same as if they were lerped together with 0.5 blending.

float4 edge = mainTex * mainTex;

I wanted a tighter edge so I multiplied the main texture by itself and saved it off to a variable. This can be made adjustable by using pow( mainTex, _SomeParameter ) and you will be able to adjust the tightness in the material, however the pow() function is more instructions than just multiplying things together multiple times.

mainTex *= fireTex1.xxxx + fireTex2.xxxx + fireTex3.xxxx * 0.3;

With the edge saved off I multiply the main texture by the sum of all my fire textures, the last fire texture was a little too much so I multiplied it down by 0.3;

mainTex += edge;

Add the edge back into the main texture.

mainTex.xyz *= _Factor;

Multiply the color by the factor parameter and we are done!

o.Albedo = 0.0;
o.Emission = mainTex;
o.Alpha = saturate( mainTex.w );

This is another unlit material so the main texture goes into the Emission and the blend mode needs an alpha so I saturate the main texture's alpha and put it there. The alpha needs to be saturated because when the edge was added to the main texture there is a possibility that the alpha could go over 1 and when the alpha is out of the 0-1 range the blending gets super wonky.

Below is the final result, keep in  mind that this will have the sun surface behind it.

The final result of the sun edge shader


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

Thursday, February 19, 2015

2.5D Sun


A friend of mine is working on a Rouge-like game, which was explained to me as a type of game where you die...  Or something.  Here's the link to his games blog so I don't butcher the explanation of it: http://theheavensgame.tumblr.com/ 

Anyway it's set in space so he needs some spacey effects and I thought it was a good opportunity to make a good looking sun.  Usually things like planets and stars have layers of volume materials which give them different looks depending on the viewing angle.  Think about how the edge of the earth looks due to the layers of ozone and atmosphere.  These are tricky and sometimes expensive to render because they have to work when viewed from every angle, but in this situation the sun only needs to be viewed from the front so I can set up a minimum number of layers depending on where I want to see certain visual elements.


The meat and potatoes is a squashed hemisphere to give the sun a little depth, There's an image of the sun I picked off of google with flow map that slowly distorts some fire textures.  There's also a look up texture that moves through the z component of the flow map. (flow in xy, height in z)  There is a ring around the edge to give it that ozoney Fresnel look.  There's also some wave textures and ray textures that scroll outwards from this ring.  The solar flares are 1 mesh with a bunch of outward pointing quads.  There is a vertex shader on them that pushes them on their tangent (outwards because the tops of the texture are out and the bottoms of the texture are in) and fades them in and out.  The flare vertex code looks like this:

fracTime = frac( _Time.y * yourSpeed ) ;
position.xyz += fracTime * v.tangent.xyz * yourDistance; //push the flares outwards
color.w = smoothstep( 0.0, 0.2, fracTime ) * smoothstep( 1.0, 0.5, fracTime ); // fade the flares in and out over the cycle

The whole thing is covered by a heat haze and glow which ties everything together nicely!

The final result looks like this: Sun Demo


Wednesday, February 18, 2015

Material Tool

I'm working on a image to material type of tool that fits somewhere between Crazybump and Bitmap2Material.  Giving you more control and options than Crazybump but probably not as robust as B2M.

So far I have...
Height from diffuse.
Normal from height with light direction from diffuse.
Ambient occlusion from height and normal.
Edge/height from normal.
Some options to high pass the diffuse and remove highlights and super dark shadows.
Spec and Roughness creation using the same features as the diffuse options.
A mostly physically based preview.
Seamless tiling of maps using height map bias.  I'll look into splat map tiling too.

The 2 aforementioned products probably already have most of the market share so this will probably be a free tool.  Its made in unity so the import options are limited but I did manage to get tga import working.  So far the it imports jpg, png, tga with the help of a nice library I found, and bmp.  I haven't looked too much into exporting but png and jpg work for sure and I will try to get tga and bmp working.

I also added a nice post process with some kind of inverse kawase depth of field blur which I might make a post about since it works pretty durn good and I haven't seen any articles illustrating the exact technique I used.