No matter developing shader in NPR or PBR, a well-understanding of the default shading pipeline is important, especially in Unreal Engine where all the shader things are packed in their source codes. To manipulate the default pipeline or create your own lighting model, it needs a well understanding of the engine shading pipeline.
The Mobile BasePass Shader
Codes Without Macros
\Engine\Shaders\Private\MobileBasePassPixelShader.usf This is the path of the UE4 mobile PBR shader. There are a lot of macro there, such as MATERIALBLENDING_..., MATERIAL_SHADINGMODEL_UNLIT, FULLY_ROUGH, NONMETAL, etc.. Let’s just remove all of them and see what the code is like.
To Visualize Each Piece
It’s hard to know what’s going on by just watching the codes. So let’s just go to the usf file return the specific parameter that you want to see, for instance: And go back to the engine, just make some minor changes, such as moving a node in the master material, then click apply, the shader will compile. Here I used a standard PBR material in a simple lighting environment, containing a directional light, a skylight, and a background plane. The complete Lit output is:
ComputeIndirect
The pixel shader starting from Main() function, and at line 643 half3 Color = 0; the color starts adding layer by layer. At first, it is added by IndirectColor, calculated from ComputeIndirect:
This function samples the lightmap for static mesh with baked light. Notice that for mobile it is LQ_TEXTURE_LIGHTMAP. Here I don’t have any lightmaps, the result is just black:
ENABLE_SKY_LIGHT
Then it comes IndirectIrradiance: The IndirectIrradiance is calculated under ENABLE_SKY_LIGHT:
Where,
SkyDiffuseLighting
half3 SkyDiffuseLighting
DiffuseLookup
half3 DiffuseLookup
IndirectIrradiance
IndirectIrradiance IndirectIrradiance will be used later in SpecularIBL
FMobileDirectLighting
Continue scroll down, we will see some common parameters like NoL, H, NoH, after that we see FMobileDirectLighting Lighting.
Where Color += the shadow, directional light color, Lighting.Diffuse and Lighting.Specularand the directional light specular scale. Let’s output the result of it: You see that the shading is almost done except lacking of some sky light in the shadow area. So, what has been done in that two lines of code? Firstly, lets see the FMobileDirectLighting Lighting in \Engine\Shaders\Private\MobileShadingModels.ush.
Lighting.Specular
Calculated in MobileShadingModels.ush, is in the struct of FMobileDirectLighting.
Which is the specular comes from the light, just like Phong.
Looks like:
Lighting.Specular
Lighting.Diffuse
Calculated in MobileShadingModels.ush, is in the struct of FMobileDirectLighting.
Looks like:
Lighting.Diffuse
Below is the part FMobileDirectLighting Lighting in \Engine\Shaders\Private\MobileShadingModels.ush
Lighting.Specular + Lighting.Diffuse will looks like:
CalcSpecular()
Below is the function CalcSpecular() that used above to calculate the specular. \Engine\Shaders\Private\MobileShadingModels.ush
GGX_Mobile()
and GGX_Mobile() is in \Engine\Shaders\Private\MobileGGX.ush
SpecularIBL
Continue scrolling down, here it comes SpecularIBL.
This is the specular created by image-based lighting, showing when your skylight sets to Which looks like: SpecularIBL
Later, SpecularIBL is multiplied with ShadingModelContext.SpecularColor and add on to the Color. Which looks like: SpecularIBL * ShadingModelContext.SpecularColor
Then add it on the Color: Color += SpecularIBL * ShadingModelContext.SpecularColor;
See the metal necklace, it turns from black to silver with the light of cubemap after adding the SpecularIBL. ``
AccumulateLightingOfDynamicPointLight
After the SpecularIBL, there is a function called AccumulateLightingOfDynamicPointLight, which is in charge of some dynamic lights except the directional light. This part calculated the attenuation of lights, NoL, and light color, then multiplied with Lighting.Diffuse + Lighting.Specular
AccumulateLightingOfDynamicPointLight
Then scroll down, there is another ENABLE_SKY_LIGHT macro, which does this:
This line uses of the SkyDiffuseLighting we got in the last ENABLE_SKY_LIGHT macro part before, and multiplied with skylight color, also multiplied with ShadingModelContext.DiffuseColor * MaterialAO. We already seen ShadingModelContext.SpecularColor in the SpecularIBL part, and here we see ShadingModelContext.DiffuseColor. Read below to find what’s their function is:
Which makes the nonmetal part black and metal part lighter, so that when it applied with SpecularIBL, it makes metal part receive more IBL lights:
ShadingModelContext.SpecPreEnvBrdf
ShadingModelContext.SpecularColor
Calculated in MobileShadingModels.ush.
Which is ShadingModelContext.SpecPreEnvBrdf calculated in EnvBRDFApprox
Which ‘means we have plausible Fresnel and roughness behavior for image based lighting’, according to Physically Based Shading on Mobile
ShadingModelContext.SpecularColor
Below is the relative codes of ShadingModelContext.SpecularColor and ShadingModelContext.DiffuseColor in \Engine\Shaders\Private\MobileShadingModels.ush
EnvBRDFApprox
Below is the function GetEnvBRDF() in \Engine\Shaders\Private\MobileShadingModels.ush
Below is EnvBRDFApprox() in \Engine\Shaders\Private\BRDF.ush. You can read Physically Based Shading on Mobile to see about this approximate environment BRDF method.
Add Emission
The last step is to adding emission on the Color if the material has this input.
Next Step
Once finished the disassembly of the codes, we know what each part looks like and what it functions, we can start to rebuild it on other platforms such as Maya, SP, 3dMax, etc. Also, if you want to make custom non physically based shading, it’s important to know the PBR pipeline first, so that you know where you should revise and write your own bits, and how to insert a custom lighting model.