I have written a lighting shader file that is used as the fragment shader part of a deferred lighting pipeline.

Unfortunately, each iteration seems to be adding 0.5ms to my frame time, so any more than a handful of dynamic lights results in unbearable slowdown.

So clearly I'm doing something stupid but I'm kinda new to optimising HLSL, so anything obvious you can spot would be greatly appreciated. Here is the shader code:

#include "DLLightingInclude.hlsli"

static const float MAX_DIFFUSE_ANGLE = 1.57079637;

Texture2D NormalGB : register(t0);
Texture2D DiffuseGB : register(t1);
Texture2D SpecularGB : register(t2);
Texture2D PositionGB : register(t3);

SamplerState NormalSampler : register(s0);

cbuffer CameraProperties : register(cb0) {
    float3 CameraPosition;

cbuffer LightProperties : register(cb1) {
    float3 LightPosition;
    float LightRadius;
    float3 LightColor;

void GetGBValues(
    in int2 screenPosition,
    in float2 texturePosition,
    out float3 normal,
    out float3 diffuseAlbedo,
    out float3 specularAlbedo,
    out float specularPower,
    out float3 position)

    int3 screenPos3 = int3(screenPosition, 0);
    normal = normalize(NormalGB.Sample(NormalSampler, texturePosition).xyz);
    diffuseAlbedo = DiffuseGB.Load(screenPos3).xyz;
    float4 specularValues = SpecularGB.Load(screenPos3);
    specularAlbedo = specularValues.xyz;
    specularPower = specularValues.w * 50.0f;
    position = PositionGB.Load(screenPos3).xyz;

FS_OUT_SPLIT main(FS_IN input) {
    FS_OUT_SPLIT result;

    float3 N;
    float3 diffuseAlbedo;
    float3 specularAlbedo;
    float specularPower;
    float3 position;

    GetGBValues((int2) input.Position, input.TexCoord, N, diffuseAlbedo, specularAlbedo, specularPower, position);

    float3 L = LightPosition - position;
    float lightDistance = length(L);
    float invAttenuation = max(0.0f, 1.0f - (lightDistance / LightRadius));
    float NdotL = max(dot(N, L), 0.0f);
    float angle = acos(NdotL / (lightDistance * length(N)));

    float3 diffuseTerm = LightColor * diffuseAlbedo * (MAX_DIFFUSE_ANGLE - angle);
    float3 V = CameraPosition - position;
    float3 H = normalize(L + V);
    float3 NdotH = dot(N, H);
    float3 specularTerm = pow(saturate(NdotH), specularPower) * LightColor * specularAlbedo * min(NdotL, 1.0f);

    result.BackBuffer = result.PreBloomBuffer = float4(diffuseTerm * invAttenuation + specularTerm * invAttenuation, 1.0f);

    return result;

And the included file, DLLightingInclude.hlsli, looks like this:

struct VS_IN {
    float3 Position : POSITION;
    float2 TexCoord : TEXCOORD0;

struct VS_OUT {
    float4 Position : SV_POSITION;
    float2 TexCoord : TEXCOORD0;

typedef VS_OUT FS_IN;

struct FS_OUT {
    float4 Color : SV_TARGET0;

struct FS_OUT_SPLIT {
    float4 BackBuffer : SV_TARGET0;
    float4 PreBloomBuffer : SV_TARGET1;

I can't really see anything particularly obvious in what I've written- but like I say, I'm kind of a noob at this.

  • 1
    \$\begingroup\$ Look at disassembly in GPU ShaderAnalyzer. Consider processing more than one light either with light arrays in your cbuffer or by storing light information in structured buffers. On the high-level algorithmic side, consider culling your lights to avoid computing lighting for areas that are irrelevant. Tile-based solutions exist. \$\endgroup\$ – Lars Viklund Jan 28 '16 at 10:12

I can't see anything fundamentally wrong with the shader, but here are a few things I find commonly done wrong with deferred shading that you might be doing.

1: Drawing full screen lights. The beauty of deferred shading is that you can pack your lights into geometry so that you only need to consider a part of the screen when drawing them (Like a cube with 2 times the radius as size). If instead you draw a full screen quad for every light, that will have a serious performance impact.

2: Render target depth. You are moving a lot of data around in deferred shading, so you have an interest in making the footprint of that as small as possible. If you can reduce the number or size of your render targets that can have a pretty big performance impact.

3: Rendering one light at a time rather than batching them. (Edit: You can have a static vertexbuffer and indexbuffer containing vertices for the max number of lights you want to ever render and then just patch the position and color of the lights that are active)

And lastly, you can always look at the assembly of your shader and see how many cycles it will take, so that gives you an easy way to see your own performance and compare. GPU Gems has 2 or 3 great articles on deferred shading and the performance and other issues that come with it.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.