How to Explore Unity 5's Shader System Code - V - Shadows code overview

Last time we looked at Global Illumination. This time let's look at Shadows. There's quite a lot of code dealing with shadows, so it'll take at least another post to cover everything.

Shadows in Unity Overview

Shadows are generally rendered with the Fallback shader you specify at the end of your custom shader.

    }
    FallBack "VertexLit"
}

many issues you will encounter (like not having shadows at all) can stem from that.

Beware! If you don't specify a Fallback shader the shadows won't be rendered at all.

That is annoying when you are developing a custom shader and having no Fallback shader is necessary so it's obvious when it gets broken. Otherwise even if your shader is broken it will still render with the Fallback shader and you might not notice.

Types of lights and shadows

Included in Unity are:

In Unity you can have:

  • Hard Shadows (cheaper)
  • Soft Shadows

another type of shadows are ray-traced shadows, but this technique is not included in Unity yet.

Settings

Shadow Distance & Shadow Map resolution

Available in Quality settings.
Higher resolution is higher quality, farther distance decreases quality if it's too much too fit in the resolution you chose.

Cast & Receive shadows

Every Mesh Renderer has these two settings. It's fairly easy for a one to be forgotten, so it's one thing to check if some shadows are not showing up.

Implementation Files

There are quite a few files involved with shadows:

  • UnityStandardShadow.cginc
  • AutoLight.cginc
  • UnityShaderVariables
  • UnityShadowLibrary
  • UnityCG shadowcaster pass helpers
  • UnityGlobalIllumination

In UnityStandardShadow.cginc we can find the vertex and fragment functions:

  • vertShadowCaster
  • fragShadowCaster

they are used in the Standard and StandardSpecular's shader ShadowCaster Pass:

// ------------------------------------------------------------------
		//  Shadow rendering pass
		Pass {
			Name "ShadowCaster"
			Tags { "LightMode" = "ShadowCaster" }

			ZWrite On ZTest LEqual

			CGPROGRAM
			#pragma target 3.0

			// -------------------------------------


			#pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
			#pragma multi_compile_shadowcaster

			#pragma vertex vertShadowCaster
			#pragma fragment fragShadowCaster

			#include "UnityStandardShadow.cginc"

			ENDCG
		}

Types of light, types of shadows

AutoLight.cginc contains different implementations for the functions:

  • SHADOW_ATTENUATION
  • TRANSFER_SHADOW
  • SHADOW_COORDS

depending on whether the light casting the shadow is a point light or a spot light.
It also defines necessary functions for directional lights.

In UnityStandardCore.cginclude, vertForwardBase we have to use TRANSFER_SHADOW to pass the necessary data to the vertex shader.

In fragForwardBaseInternal, same file, we have to use SHADOW_ATTENUATION to calculate the attentuation to give to the GI calculating function (see below).

SHADOW_COORDS is used in the same file, in the VertexOutputBaseSimple struct, which is used by the many functions in the same file, for different levels of BRDF quality.

Global Illumination Shadows

For Global Illumination, the shadows influence is in UnityGlobalIllumination.cginc, in UnityGI_Base:

inline UnityGI UnityGI_Base(UnityGIInput data, half occlusion, half3 normalWorld)
{
	UnityGI o_gi;
	ResetUnityGI(o_gi);

           [...]

			#ifdef SHADOWS_SCREEN
				o_gi.indirect.diffuse = MixLightmapWithRealtimeAttenuation (o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld);
			#endif // SHADOWS_SCREEN

		#elif DIRLIGHTMAP_SEPARATE
			[...]
			#ifdef SHADOWS_SCREEN
				o_gi.light.color = MixLightmapWithRealtimeAttenuation(o_gi.light.color, data.atten, bakedColorTex, normalWorld);
				o_gi.light2.color = MixLightmapWithRealtimeAttenuation(o_gi.light2.color, data.atten, bakedColorTex, normalWorld);
				o_gi.indirect.diffuse = MixLightmapWithRealtimeAttenuation (o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld);
			#endif // SHADOWS_SCREEN

		#else // not directional lightmap
			o_gi.indirect.diffuse = bakedColor;

			#ifdef SHADOWS_SCREEN
				o_gi.indirect.diffuse = MixLightmapWithRealtimeAttenuation (o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld);
			#endif // SHADOWS_SCREEN

the calculations happening in the function MixLightmapWithRealtimeAttenuation:

inline half3 MixLightmapWithRealtimeAttenuation (half3 lightmap, half attenuation, fixed4 bakedColorTex, half3 normalWorld)
{
	// Let's try to make realtime shadows work on a surface, which already contains
	// baked lighting and shadowing from the current light.

#if IMPROVED_BAKED_AND_REALTIME_SHADOW_MIXING
	half shadowStrength = _LightShadowData.x;
	half attenuationUnaffectedByShadowStrength = max(0, attenuation - shadowStrength);

	// Calculate possible value in the shadow by two very distinct ways:
	// 1) by subtracting estimated light contribution from the places occluded by realtime shadow:
	//		a) preserves other baked lights and light bounces
	//		b) eliminates shadows on the geometry facing away from the light
	//		BUT in case of (ShadowStrength < 1) subtracts light in the areas that are not covered by realtime shadow
	// 2) by attenuating lightmap - usually produces results that are:
	// 		a) too dark in region where baked and realtime shadow overlap
	//		b) destroys other baked lights
	//		c) shadows are visible on the geometry facing away from the light
	// 		BUT it handles (ShadowStrength < 1) better
	// Then use min/max arbiter to get a solution.


	// 1) Gives good estimate of illumination as if light would've been shadowed during the bake.
	//    Preserves bounce and other baked lights
	//    No shadows on the geometry facing away from the light
	half ndotl = LambertTerm (normalWorld, _WorldSpaceLightPos0.xyz);
	half3 estimatedLightContributionMaskedByInverseOfShadow = ndotl * (1-attenuationUnaffectedByShadowStrength) * _LightColor0.rgb;
	half3 subtractedLightmap = lightmap - estimatedLightContributionMaskedByInverseOfShadow;

	// 2) Keeps lightmap tint in shadow when ShadowStrength < 1.
	//    Preserves unshadowed areas when ShadowStrength < 1.
	half3 lightmapTint = bakedColorTex.rgb;
	half3 attenuatedLightmap = lightmapTint * attenuation;

	// Arbiter. Pick original lightmap value, if it is the darkest one.
	return max (min(lightmap, attenuatedLightmap), subtractedLightmap);
#else
	// Generally do min(lightmap,shadow), with "shadow" taking overall lightmap tint into account.
	half3 shadowLightmapColor = bakedColorTex.rgb * attenuation;
	half3 darkerColor = min(lightmap, shadowLightmapColor);

	// However this can darken overbright lightmaps, since "shadow color" will
	// never be overbright. So take a max of that color with attenuated lightmap color.
	return max(darkerColor, lightmap * attenuation);
#endif
}

as we mentioned in the last article, this information, in a struct UnityGI is passed to the right BRDF function, according to quality setting, in fragForwardBaseInternal:

UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);

	half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
	c.rgb += UNITY_BRDF_GI (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, occlusion, gi);

which would calculate the final surface colour according to the BRDF.

Other useful info

Defines:
  • SHADOWS_SOFT
  • UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT
  • UNITY_STANDARD_USE_SHADOW_UVS
  • UNITY_STANDARD_USE_DITHER_MASK
  • UNITY_STANDARD_SHADOW_INCLUDED
  • UNITY_NO_SCREENSPACE_SHADOWS
  • SHADOW_COORDS
  • SHADOWS_SCREEN

Depend on:

  • _ALPHATEST_ON
  • _ALPHABLEND_ON
  • _ALPHAPREMULTIPLY_ON
  • UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT
Pragma:
  • fullforwardshadows
  • multi_compile_shadowcaster
  • multi_compile_fwdadd_fullshadows in FORWARD_DELTA
Tags:
  • LightMode = ShadowCaster
Hidden:
  • Internal-ScreenSpaceShadows
Cbuffer:
  • UnityShadows in UnityShaderVariables

References

Next: do ping me on twitter if you have specific questions about the various subsystems.