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

(in UnityLightingCommon.cginc )

stores a couple of UnityLight, depending on the type of lightmap

(in ) stores a couple of UnityLight, depending on the type of lightmap struct UnityGIInput

(in UnityLightingCommon.cginc )

stores various other info needed to calculate GI, which are used in many functions

(in ) stores various other info needed to calculate GI, which are used in many functions function UNITY_BRDF_GI

(in UnityPBSLighting.cginc )

it's used in fragForwardBaseInternal to calculate the indirect contribution to the BRDF).

To do that it calls BRDF_Unity_Indirect

(in ) it's used in to calculate the indirect contribution to the BRDF). To do that it calls function BRDF_Unity_Indirect

(in UnityPBSLighting.cginc :

adds the result of UNITY_BRDF_PBS_LIGHTMAP_INDIRECT to the colour passed in

(in : adds the result of to the colour passed in function UNITY_BRDF_PBS_LIGHTMAP_INDIRECT

(in UnityPBSLighting.cginc )

is defined to BRDF2_Unity_PBS (but a comment says one can also use BRDF1_Unity_PBS for better quality)

(in ) is defined to (but a comment says one can also use for better quality) functions BRDF2_Unity_PBS or BRDF1_Unity_PBS ,

which we saw in the previous article. In this case it calculates the indirect contribution

or , which we saw in the previous article. In this case it calculates the indirect contribution function FragmentGI

(in UnityStandardCore.cginc )

fills necessary data, also from the reflection probes, and then passes it to UnityGlobalIllumination

(in ) fills necessary data, also from the reflection probes, and then passes it to function UnityGlobalIllumination :

(4 versions, with different signatures) passes data to UnityGI_Base and UnityGI_IndirectSpecular

: (4 versions, with different signatures) passes data to and function UnityGI_Base

(in UnityGlobalIllumination.cginc )

samples and decodes the lightmaps, mixes with realtime attenuation, applies occlusion

(in ) samples and decodes the lightmaps, mixes with realtime attenuation, applies occlusion function UnityGI_IndirectSpecular ,

(again in UnityGlobalIllumination.cginc )

calculates the reflections, correct if box projection is active, applies occlusion

also useful to get a more complete picture are:

struct UnityIndirect

(in UnityLightingCommon.cginc )

only contains a diffuse and a specular colour

(in ) only contains a diffuse and a specular colour struct UnityLight

(in UnityLightingCommon.cginc )

stores colour, direction and NdotL of a light

(in ) stores colour, direction and NdotL of a light texcubes unity_SpecCube0 and unity_SpecCube1 : reflection probes

and : reflection probes struct Unity_GlossyEnvironmentData : stores roughness and reflection UVs

: stores roughness and reflection UVs function ResetUnityGI : cleans out a UnityGI struct

: cleans out a UnityGI struct function ResetUnityLight : cleans a UnityLight struct

: cleans a UnityLight struct function ShadeSHPerPixel : sample Spherical Harmonics per pixel

: 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.