Gamasutra: The Art & Business of Making Gamesspacer
View All     RSS
August 1, 2014
arrowPress Releases
August 1, 2014
PR Newswire
View All
View All     Submit Event





If you enjoy reading this site, you might also want to check out these UBM Tech sites:


 
Let there be shadow!
by Joey Fladderak on 04/16/14 03:08:00 pm   Featured Blogs

The following blog post, unless otherwise noted, was written by a member of Gamasutraís community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.

 

Original post can be found here.

Shadows in Unity is something that in most cases is a given through the use of surface shaders, but sometimes you don't want to use a surface shader for whatever reason and create your own vertex/fragment shader. The biggest advantage is that everything is in your hands now, but this is also one of the drawbacks because you now have to handle a lot of stuff that Unity conveniently handled for you in a surface shader. Among such things are support for multiple lights and shadows.

Luckily, Unity provides you the means to get this working! The catch? Documentation on this is lacking or even non-existent. I was in the same position as most people and somewhat clueless on how to get shadows in my vertex/fragment shader, I did my fair share of googling and found some clues that didn't quit do the trick, but gave me a good impression on where to search. I also went through a compiled surface shader to see if I could figure out how they did it. All of the research combined and some trying out finally gave me the results I needed: Shadows! And now I will share it with whoever is interested.

Before I begin, I want to make note that as mentioned earlier, Unity solves a lot of cases for you when you are using surface shaders, among such things are the inner workings when you are using deferred or forward rendering. With your own vertex/fragment shaders, you will need to take that into account yourself for some cases. Truth is, I only needed to get this to work with forward rendering and only briefly tested how this works with deferred rendering and although I did not notice anything off, I can't guarantee it will work in all cases, so keep that in mind!

I will start off with showing you the shader that casts (and receives) a nice shadow and break it down, going over the different elements of interest. It's a simple diffuse shader that looks like this:

Shader "Sample/Diffuse" 
{
	Properties 
	{
		_DiffuseTexture ("Diffuse Texture", 2D) = "white" {}
		_DiffuseTint ( "Diffuse Tint", Color) = (1, 1, 1, 1)
	}

	SubShader 
	{
		Tags { "RenderType"="Opaque" }

		pass
		{		
			Tags { "LightMode"="ForwardBase"}

			CGPROGRAM

			#pragma target 3.0
			#pragma fragmentoption ARB_precision_hint_fastest

			#pragma vertex vertShadow
			#pragma fragment fragShadow
			#pragma multi_compile_fwdbase

			#include "UnityCG.cginc"
			#include "AutoLight.cginc"

			sampler2D _DiffuseTexture;
			float4 _DiffuseTint;
			float4 _LightColor0;

			struct v2f
			{
				float4 pos : SV_POSITION;
				float3 lightDir : TEXCOORD0;
				float3 normal : TEXCOORD1;
				float2 uv : TEXCOORD2;
				LIGHTING_COORDS(3, 4)
			};

			v2f vertShadow(appdata_base v)
			{
				v2f o;

				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = v.texcoord;
				o.lightDir = normalize(ObjSpaceLightDir(v.vertex));
				o.normal = normalize(v.normal).xyz;

				TRANSFER_VERTEX_TO_FRAGMENT(o);

				return o; 
			}

			float4 fragShadow(v2f i) : COLOR
			{					
				float3 L = normalize(i.lightDir);
				float3 N = normalize(i.normal);	 

				float attenuation = LIGHT_ATTENUATION(i) * 2;
				float4 ambient = UNITY_LIGHTMODEL_AMBIENT * 2;

				float NdotL = saturate(dot(N, L));
				float4 diffuseTerm = NdotL * _LightColor0 * _DiffuseTint * attenuation;

				float4 diffuse = tex2D(_DiffuseTexture, i.uv);

				float4 finalColor = (ambient + diffuseTerm) * diffuse;

				return finalColor;
			}

			ENDCG
		}		

	} 
	FallBack "Diffuse"
}

If you have ever worked with vertex/fragment shaders you will notice that there isn't much to be noted except for a few macros, but let's address the first things you will need to do to get those shadows.

The first thing you will need to define is the LightMode Pass Tag:

Tags { "LightMode"="ForwardBase"}

This will tell unity that this pass will make use of the main light that will cast our shadow (there's more to this tag, check the link for more info). Unity handles each light in their own pass, so if we want to work with multiple lights, this value in another pass would change to ForwardAdd.

Next to the tag, we also need to define the following:

#pragma multi_compile_fwdbase

This is to ensure the shader compiles properly for the needed passes. As with the tag, for any additional lights in their own pass, fwdbase becomes fwdadd.

To make use of all the needed code/macros to sample shadows in our shader, we will need to include the AutoLight.cginc that holds all the goodness:

#include "AutoLight.cginc"

Now Unity knows all it needs on how to handle the lights, now we just need to get the relevant data to get our shadow to appear and for that we only have to do 3 things:

  1. Make unity generate/include the needed parameters to sample the shadow.
  2. Fill these parameters with values that makes sense.
  3. Get the final values.

To make Unity "generate" the values we need, all we have to do is add the LIGHTING_COORDS macro to our vertex to fragment struct like so:

struct v2f
{
	float4 pos : SV_POSITION;
	float3 lightDir : TEXCOORD0;
	float3 normal : TEXCOORD1;
	float2 uv : TEXCOORD2;
	LIGHTING_COORDS(3, 4)
};

The LIGHTING_COORDS macro defines the parameters needed to sample the shadow map and the light map depending on the light source. The numbers specified are the next 2 available TEXCOORD semantics available. So if I would need a viewing direction for a specular highlight, the struct would look like this:

struct v2f
{
	float4 pos : SV_POSITION;
	float3 lightDir : TEXCOORD0;
	float3 normal : TEXCOORD1;
	float2 uv : TEXCOORD2;
	float3 viewDir : TEXCOORD3;
	LIGHTING_COORDS(4, 5)
};

This is much like defining them yourself, except that now it's guaranteed for Unity that they're using the right values for the right light sources with perhaps also a cookie texture attached to them. If you're curious as to what gets defined exactly, check out the AutoLight.cginc file.

Next up is the vertex shader. Having the values is one thing, but we need them to hold the right data and Unity provides another macro that fills it up with the right data for the right situation, this is done with the TRANSFER_VERTEX_TO_FRAGMENT macro. This macro must be defined before returning the v2f struct, so your vertex shader would look something like this:

v2f vertShadow(appdata_base v)
{
	v2f o;

	o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
	o.uv = v.texcoord;
	o.lightDir = normalize(ObjSpaceLightDir(v.vertex));
	o.normal = normalize(v.normal).xyz;

	TRANSFER_VERTEX_TO_FRAGMENT(o);

	return o; 
}

Not much is to be said about this, other than that it takes care of calculating the light and shadow coordinates for you for the different lights.

At this moment, all we have left is to create our fragment program that is able to use the LIGHT_ATTENUATION macro that returns the correct values we need for our shadow. You can use the attenuation value like you would normally, for diffuse shading I use it in the diffuse term like this in the fragment shader:

float4 fragShadow(v2f i) : COLOR
{					
	float3 L = normalize(i.lightDir);
	float3 N = normalize(i.normal);	 

	float attenuation = LIGHT_ATTENUATION(i) * 2;
	float4 ambient = UNITY_LIGHTMODEL_AMBIENT * 2;

	float NdotL = saturate(dot(N, L));
	float4 diffuseTerm = NdotL * _LightColor0 * _DiffuseTint * attenuation;

	float4 diffuse = tex2D(_DiffuseTexture, i.uv);

	float4 finalColor = (ambient + diffuseTerm) * diffuse;

	return finalColor;
}

And there you have it, everything you need to get that lovely shadow in your vertex/fragment shaders. The LIGHT_ATTENUATION samples the shadowmap and returns the value for you to use. Once again, if you want to know what LIGHT_ATTENUATION exactly does, check out the AutoLight.cginc.

There is still one little thing to be noted however. For Unity to have something cast and/or receive a shadow, you must provide a shadow receiver and caster pass which I didn't provide here. Instead of making them yourself, I simply added a fallback shader that has these passes so I don't have to add them myself and make the shader bigger than it already is. You can of course add this to a .cginc or put them all the way down and never look back at it, but just adding a fallback works just as well for our shadow purpose.

I hope this clears things up a bit for those struggling to get their shaders cast and/or receive shadows. Feel free to leave me a comment or mail me if you have any questions or remarks on this post!


Related Jobs

Bigpoint
Bigpoint — Berlin, Germany
[08.01.14]

Associate / Senior UI Game Developer Scaleform (m/f)
Bigpoint GmbH
Bigpoint GmbH — Hamburg, Germany
[08.01.14]

Game Developer Mobile
Petroglyph Games
Petroglyph Games — Las Vegas, Nevada, United States
[07.31.14]

Unity Engineer
Retro Studios - Nintendo
Retro Studios - Nintendo — Austin, Texas, United States
[07.31.14]

Gameplay Engineer






Comments


Mark Hogan
profile image
Great post, clearly explains everything I wanted to know without getting bogged down in some of the deeper built in stuff. This post seemed to appear the second I needed it to! Nice one!

Joey Fladderak
profile image
Glad it was of help! :)

Jonathan Newberry
profile image
There is one last thing that is important for your shadows to work. The second to the last line ' FallBack "Diffuse" ' is also required in this case for shadows to work. Down the chain of unity fallback shaders you will eventually find either 'Normal-VertexLit.shader' "VertexLit" or 'AlphaTest-VertexLit.shader' "Transparent/Cutout/VertexLit". At the end of these shaders there is a Pass named "Caster" using the Tags { "LightMode" = "ShadowCaster" } and a Pass named "ShadowCollector" with the Tags { "LightMode" = "ShadowCollector" }, this is what Unity uses for the shader replacements in the shadow rendering pre passes. You may need to make your own versions of the shadow passes if you are doing different types of vertex deformations in shader, need support for 2 sided lighting, or you are writing your own shadows. I have not played with any DX11 tessellation but you may also have to do some special things in the shadow pass here too.

Something else that can be helpfull in making your own shaders for Unity is adding "#pragma debug" under the other pragma(s) in a default Unity surface shader. With this you can see some extra source for the surface shader when you open up the compiled shader. This is very helpful to see how unity uses all of it's shader key words and if you wanted to see what goes into a forward add pass to allow you to use extra pixel lights in your shader. You can find all the unity included shaders here: https://unity3d.com/unity/download/archive
Just give the shader a new name in the first line and drop it into your project and start having fun.

Joey Fladderak
profile image
I did say that the fallback shader was needed for the shadow caster/receiver passes, itís down at the post as a last note, but itís not really emphasized so I guess itís easy to look over and I might need to change that. It basically just says ďI put it there so I donít have to worry about itĒ. Youíre very right about advanced uses though! I merely wanted to present the very basics to people. :)
Using the #pragma debug was one of the things that helped me figure out what was going on.
Thanks for this comment, people looking for some more info when they want to dive in a bit deeper will definitely benefit from it!

Jonathan Newberry
profile image
Great post btw. I saw that at the end of the article after re-reading, but it took 24 hours for my post to show up. I Just hope it is helpful to bring extra attention to since I spent an extra few hours figuring it out the other day and it is not well documented.

Patrick Reece
profile image
Very helpful info at just the right time for me! Thanks! I would love to know more about the shadow caster and receiver pass, what does it look like? Does the caster write a depth value in the fragment shader (for a shadow map)? How do fallbacks work? Do you know if #pragma fullforwardshadows works with this technique? Thanks a lot!

Joey Fladderak
profile image
As mentioned by Jonathan Newberry above, eventually the fallback shader will end up at the vertexlit shader variant which is holding the "shadow passes" I would advice you to download the shaders and check out the Normal-VertexLit.shader and check the passes at the bottom. The functions used in those passes can be found in UnityCG.cginc.

Fallbacks are basically back up plans, if a subshader isn't supported it will try to use the next one available provided by the fallback.

I haven't tried out if #pragma fullforwardshadows works, but there's one way to find out! :)

Wes Jurica
profile image
"but sometimes you don't want to use a surface shader for whatever reason and create your own vertex/fragment shader"

What are some of the reasons? I'm just getting into shader programming but maybe making a shadow-able vertex shader would be faster?

Thanks for post!

Joey Fladderak
profile image
Essentially it's just for performance reasons, which is mostly neglect able for the PC. In the end a surface shader will end up as a vertex/fragment shader.

In a surface shader Unity handles a lot of stuff for you for different situations so you don't have to specify that yourself every time you create a shader. Because surface shaders take all that into account, it can contain overhead of stuff you might never use.

So mostly it's a full control reason, you know exactly what's being called for what reason, but I wouldn't worry to much about it. If a surface shader does the trick, stick to that! :)

Jonathan Newberry
profile image
As Joey said. It is not really important on PC. You may want to make your own if you are trying to do something special VFX thing. Since things are handled in stages were you set up your inputs and the lighting and combining happens in the surface effects that are emissive and use lighting can be difficult or inefficient.

Otherwise on mobile where every operation, variable assignment, and buffer can really start to slow down the game the extra work that the surface shader does to move things in and out of the surface function in the fragment shader can be heavier than it needs to be. There is also operations that you can do for lower end and fall back type shaders were you move parts of the lighting to the vertex shader that you cannot do with surface since everything kind of happens in the fragment when using surface shaders.


none
 
Comment: