Last time we looked at the Physically Based lighting function in the Standard Shader.
This time we'll chase down how the Global Illumination contribution is calculated. It takes some work, because the code which does the key calculations is behind layers of quality choices, which are encoded as defines.
So let's pick all the functions and structs which refer to global illumination, in the code we already looked at in these past weeks, and track those down.
In UnityStandardCore.cginc
, 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);
In our fragment forward base function, FragmentGI
is used to calculate the global illumination data: "gi
", which is then passed onto UNITY_BRDF_PBS
and UNITY_BRDF_GI
(which are defines which stand for different functions, depending on the quality level chosen).
In UnityStandardBRDF.cginc
, BRDF1_Unity_PBS
:
half4 BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness,
half3 normal, half3 viewDir,
UnityLight light, UnityIndirect gi)
{
[...]
half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm)
+ specularTerm * light.color * FresnelTerm (specColor, lh)
+ surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv);
return half4(color, 1);
}
this is the UNITY_BRDF_PBS
part, it takes gi
, and uses it to calculate the shaded pixel's colour.
There are a couple defines, of which at least one needs to be defined:
LIGHTMAP_ON
DYNAMICLIGHTMAP_ON
And a bunch extra, which influence what code will be skipped, or to which function out of a few alternatives another define will be bound to:
DIRLIGHTMAP_SEPARATE
DIRLIGHTMAP_COMBINED
UNITY_BRDF_PBS_LIGHTMAP_INDIRECT
UNITY_BRDF_GI
UNITY_SHOULD_SAMPLE_SH
UNITY_SPECCUBE_BLENDING
UNITY_SPECCUBE_BOX_PROJECTION
_GLOSSYREFLECTIONS_OFF
UNITY_SPECCUBE_BOX_PROJECTION
The flow of the global illumination data is pretty much this, from the basic structs to the chosen functions behind the defines:
- struct
UnityGI
(inUnityLightingCommon.cginc
)
stores a couple of UnityLight, depending on the type of lightmap - struct
UnityGIInput
(inUnityLightingCommon.cginc
)
stores various other info needed to calculate GI, which are used in many functions - function
UNITY_BRDF_GI
(inUnityPBSLighting.cginc
)
it's used infragForwardBaseInternal
to calculate the indirect contribution to the BRDF).
To do that it callsBRDF_Unity_Indirect
- function
BRDF_Unity_Indirect
(inUnityPBSLighting.cginc
:
adds the result ofUNITY_BRDF_PBS_LIGHTMAP_INDIRECT
to the colour passed in - function
UNITY_BRDF_PBS_LIGHTMAP_INDIRECT
(inUnityPBSLighting.cginc
)
is defined toBRDF2_Unity_PBS
(but a comment says one can also useBRDF1_Unity_PBS
for better quality) - functions
BRDF2_Unity_PBS
orBRDF1_Unity_PBS
,
which we saw in the previous article. In this case it calculates the indirect contribution - function
FragmentGI
(inUnityStandardCore.cginc
)
fills necessary data, also from the reflection probes, and then passes it toUnityGlobalIllumination
- function
UnityGlobalIllumination
:
(4 versions, with different signatures) passes data toUnityGI_Base
andUnityGI_IndirectSpecular
- function
UnityGI_Base
(inUnityGlobalIllumination.cginc
)
samples and decodes the lightmaps, mixes with realtime attenuation, applies occlusion - function
UnityGI_IndirectSpecular
,
(again inUnityGlobalIllumination.cginc
)
calculates the reflections, correct if box projection is active, applies occlusion
also useful to get a more complete picture are:
- struct
UnityIndirect
(inUnityLightingCommon.cginc
)
only contains a diffuse and a specular colour - struct
UnityLight
(inUnityLightingCommon.cginc
)
stores colour, direction and NdotL of a light - texcubes
unity_SpecCube0
andunity_SpecCube1
: reflection probes - struct
Unity_GlossyEnvironmentData
: stores roughness and reflection UVs - function
ResetUnityGI
: cleans out a UnityGI struct - function
ResetUnityLight
: cleans a UnityLight struct - function
ShadeSHPerPixel
: sample Spherical Harmonics per pixel - function
Unity_GlossyEnvironment
: uses the roughness to look up the appropriate texcube LOD
This should be enough to get you an idea of the hooks one needs to be careful of, in order not to break global illumination, when modifying the standard shader.
See you next time, I'll probably write about either shadows subsystem, or occlusion. Ping me at @shadercat if you're interested in some specific subsystem, so I know what to write about next.
Next: Shadow subsystem overview
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.