Deferred Lighting Project

This project was completed as part of the 'Advanced Real-Time Rendering module' at the University of Staffordshire. The primary goal of this project was to implement deferred lighting as well as normal mapping and a variety of post-processing effects including depth of field, sharpness, chromatic abberation and other colourspace effects.

A demo of the renderer can be seen below:

GBuffer

A GBuffer is essential for deferred rendering. The structure of the GBuffer is dependant on the exact requirements of the solution. For mine, I wanted to support normal mapping and material properties such as individual specularity values. Additionally, I wanted to avoid requiring an additional texture for world position as this would require additional draw calls for each object to be rendered. As such, when world position was needed in lighting calculations, I made use of the depth buffer and simply unpacked the data. This also saves an additional texture read.

One of my aims was to use a render of the scene as the albedo texture for some objects. I also wanted this render to be lit and for the object using the render as an albedo to be lit. This necesitated that I compute lighting for some objects multiple times.

The structure of my GBuffer can be seen below:

Post-Processing

In order to create a depth of field effect, the rendered scene must be blurred. For this, I used a Gaussian Blur shader that operates on a half resolution texture of the rendered scene. This reduces the computational expense of the blur effect and also creates a more exagerated blur with fewer passes.

    
      //main post Post-Processing
      float depth = depthTx.Sample(samLinear, IN.Tex).r;

      depth = (depth - 0.99f) * 100.f;
      float blurFactor = GetBlurFactor(depth);
      
      
      float2 rOffset = float2(0.005f, 0.005f); 
      float2 gOffset = float2(-0.002f, -0.002f); 
      float2 bOffset = float2(0.003f, 0.003f);
  
    
      float2 vecFromCentre = IN.Tex - float2(0.5f, .5f);
      float distanceFromCentre = length(vecFromCentre);
      
      float tex1r = tx1.Sample(samLinear, saturate(IN.Tex + rOffset * distanceFromCentre * abberationStrength)).r;
      float tex1g = tx1.Sample(samLinear, saturate(IN.Tex + gOffset * distanceFromCentre * abberationStrength)).g;
      float tex1b = tx1.Sample(samLinear, saturate(IN.Tex + bOffset * distanceFromCentre * abberationStrength)).b;
      
      float tex2r = tx2.Sample(samLinear, saturate(IN.Tex + rOffset * distanceFromCentre * abberationStrength)).r;
      float tex2g = tx2.Sample(samLinear, saturate(IN.Tex + gOffset * distanceFromCentre * abberationStrength)).g;
      float tex2b = tx2.Sample(samLinear, saturate(IN.Tex + bOffset * distanceFromCentre * abberationStrength)).b;
  
      float4 tex1 = float4(tex1r, tex1g, tex1b, 1.0f);
      float4 tex2 = float4(tex2r, tex2g, tex2b, 1.0f);
      
      float4 finalColor = lerp(tex1, tex2, saturate(blurFactor));
      
      //saturation
      float luminance = finalColor.x*0.2125+finalColor.y*0.7153+finalColor.z*0.07121;
      float3 saturationColor = lerp(float3(luminance, luminance,luminance), finalColor.xyz, float3(saturation,saturation,saturation));
     
      //contrast
      float3 contrastColor = contrast * (saturationColor - float3(0.5f, 0.5f, 0.5f)) + float3(0.5f, 0.5f, 0.5f) + brightness;
  
      //apply contrast and brightness
      finalColor.xyz = contrastColor;
      
      //gama correction
      finalColor.xyz = pow(finalColor, 1.2);
  
      if(useACES==1)
      {
          finalColor.xyz = ACESFilm(finalColor.xyz);
      }
      
    return finalColor;

    //sharpness
    float width, height;
    
    tx1.GetDimensions(width, height);
    float2 texelSize = float2(1.0 / width, 1.0 / height);
    float2 offsets[9] =
    {
        float2(-texelSize.x, -texelSize.y), 
        float2(0, -texelSize.y), 
        float2(texelSize.x, -texelSize.y), 
        float2(-texelSize.x, 0), 
        float2(0, 0), 
        float2(texelSize.x, 0), 
        float2(-texelSize.x, texelSize.y), 
        float2(0, texelSize.y),
        float2(texelSize.x, texelSize.y) 
    };

    float4 sharpenedColor = float4(0, 0, 0, 0);
    for (int i = 0; i < 9; i++)
    {
        sharpenedColor += tx1.Sample(samLinear, IN.Tex + offsets[i]) * kernel[i];
    }

    float4 originalColor = tx1.Sample(samLinear, IN.Tex);
    float4 finalColor = lerp(originalColor, sharpenedColor, sharpness);
    
	return finalColor;
  
    
  

Who Did What?

ImGUI File Browser by AirGuanZ

When Was it Made?

This project was worked on from December 2024 - February 2025

What Went Well?

My impelementation of deferred lighting achieves everything that I wished to achieve. It is flexible in nature, shown by the fact that any number of additional lights and objects can be added to the scene and behave properly.

What Could Be Better?

The project would benefit from the implementation of Physically Based Shading and GLTF loading. However, I have previously implemented both of these in my Vulkan Cloud Volumetrics Project. Additionally, I could have implemented more robust depth of field techniques and included more material information in my GBuffer.

 

 

 

 

 

 

 

 

 

Social Media

 

Rhys Elliott 2023. contact@rhyselliott.com