Base Pass

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.

image

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.

image

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.

image

Rendering

image

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.

image

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.

image

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.

image

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

âť“
How are material nodes introduced into the Base Pass?

Like the world position offset part of the vertex shader, the material graph generated codes are executed here

image

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.

image