Since we have already discussed the depth prepass in depth, we will skip it and jump straight into the base pass.
The base pass is responsible for writing lighting-related data to the GBuffer, a necessary step in a deferred rendering pipeline.
- The Theory of Deferred Rendering
- Implementation
- Basic
- FGBufferData
- Rendering
- Vertex Shader
- More data loaded
- World Position Offset Node
- Velocity Calculation
- Pixel Shader
- Material Graph Code
- Encode GBuffer Data
The Theory of Deferred Rendering
If you are not familiar with the concept of Deferred Rendering, there are many websites that explain it very well. I recommend you to take a look at the following links:
Implementation
Basic
Just like the Base Pass/GBuffer Pass of almost all engines, the core of Unreal Engine in this step is to read the information of the geometry, combine it with the shader and parameters of the material, and then generate FGBufferData
, which is ultimately encoded into multiple render targets.
FGBufferData
FGBufferData
contains all the fields that may be used.
Note that not all fields will be used depending on the type of Mesh and the type of Material.
Rendering
Vertex Shader
The vertex shader looks similar to Vertex Shader of Depth Prepass.
Let’s talk about some different points:
More data loaded
To pass vertex color data to the pixel shader, and to pass tangent data to the pixel shader, we need to load more information than in the depth pre-pass.
These data are not passed directly through vertex input parameters, but rather fetched manually from the vertex data component buffer.
World Position Offset Node
If you connect nodes to the World Position Offset pin, your node will be translated into HLSL code and executed.
As an example, this is the translated codes:
float3 GetMaterialWorldPositionOffsetRaw(FMaterialVertexParameters Parameters)
{
MaterialFloat Local1 = (View.GameTime * 6.28318548);
MaterialFloat Local2 = sin(Local1);
return Local2;;
}
float3 GetMaterialWorldPositionOffset(FMaterialVertexParameters Parameters)
{
BRANCH
if (ShouldEnableWorldPositionOffset(Parameters))
{
return ClampWorldPositionOffset(Parameters, GetMaterialWorldPositionOffsetRaw(Parameters));
}
return float3(0, 0, 0);
}
Velocity Calculation
Towards the end of the main function, it calculates the previous world position and applies the space transform once more to obtain the previous screen position.
In the pixel shader, this value will be used to calculate the real 3D space velocity, and save into GBuffers’ velocity buffer.
float3 Velocity = Calculate3DVelocity(MaterialParameters.ScreenPosition, BasePassInterpolants.VelocityPrevScreenPosition);
float4 EncodedVelocity = EncodeVelocityToTexture(Velocity);
GBuffer.Velocity = EncodedVelocity;
Pixel Shader
The basic process of the pixel shader can be treat as:
- Evaluate the code generated from the material graph
- Save the data to the GBuffer
Material Graph Code
Like the world position offset part of the vertex shader, the material graph generated codes are executed here
The generated code looks like:
void CalcPixelMaterialInputs(in out FMaterialPixelParameters Parameters, in out FPixelMaterialInputs PixelMaterialInputs)
{
PixelMaterialInputs.Normal = MaterialFloat3(0.00000000,0.00000000,1.00000000);
// ...
// Now the rest of the inputs
MaterialFloat3 Local0 = lerp(MaterialFloat3(0.00000000,0.00000000,0.00000000),Material.PreshaderBuffer[1].yzw,Material.PreshaderBuffer[1].x);
MaterialFloat Local1 = (View.GameTime * 6.28318548);
MaterialFloat Local2 = sin(Local1);
PixelMaterialInputs.EmissiveColor = Local0;
//...
}
Encode GBuffer Data
You may want to know what each channel of the GBuffer A/B/C/D/E textures represents when you view the output of the Base Pass using tools like RenderDoc.
The following diagram illustrates how the information is encoded in our Cube example.
Please note: Unreal Engine compresses the data as much as possible before outputting it to MRT to save texture memory usage.