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:
- spot lights
- directional lights
- point lights
- area lights (best, but only baked in Unity. See "Area Lights" in Unreal's talk at Siggraph 2014).
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 helpersUnityGlobalIllumination
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
inFORWARD_DELTA
Tags:
- LightMode = ShadowCaster
Hidden:
Internal-ScreenSpaceShadows
Cbuffer:
UnityShadows
inUnityShaderVariables
References
Next: do ping me on twitter if you have specific questions about the various subsystems.
Comments? Give me a shout at @shadercat.
To get the latest post updates subscribe to the ShaderCat newsletter.
You can support my writing on ShaderCat's Patreon.