Search code examples
c++opengllightingdeferred-rendering

Deferred renderer with light volumes produce strange banding


I have a deferred renderer that only calculates lighting equations when the current fragment is in range of a light source. I do this by calculating the size of a light volume in my application and send this with other light information to the shaders. I then check the distance between the fragment and lightPos (per light) and use the light's volume as a treshold.

For simplicity's sake I use a linear equation (quadratic equations generate way too large light volumes) for light attenuation. All the lighting equations work fine, but when I use multiple lights I sometimes see strange circle borders as if the distance check causes the light calculations to prematurely stop causing a sudden change in lighting. You can see this effect in the image below:

Deferred renderer banding

The fragment shader code is as follows:

vec3 position = texture(worldPos, fs_in.TexCoords).rgb;        
vec3 normal = texture(normals, fs_in.TexCoords).rgb;
normal = normalize(normal * 2.0 - 1.0);
vec3 color = texture(albedo, fs_in.TexCoords).rgb;
float depth = texture(worldPos, fs_in.TexCoords).a;
// Add global ambient value
fragColor = vec4(vec3(0.1) * color, 0.0);
for(int i = 0; i < NR_LIGHTS; ++i)
{
    float distance = abs(length(position - lights[i].Position.xyz));
    if(distance <= lights[i].Size)
    {
        vec3 lighting;                
        // Ambient            
        lighting += lights[i].Ambient * color;            
        // Diffuse
        vec3 lightDir = normalize(lights[i].Position.xyz - position);
        float diffuse = max(dot(normal, lightDir), 0.0);
        lighting += diffuse * lights[i].Diffuse * color;
        // Specular
        vec3 viewDir = normalize(viewPos - position);
        vec3 reflectDir = reflect(-lightDir, normal);
        float spec = pow(max(dot(viewDir, reflectDir), 0.0), 8);
        lighting += spec * lights[i].Specular;

        // Calculate attenuation
        float attenuation = max(1.0f - lights[i].Linear * distance, 0.0);
        lighting *= attenuation;
        fragColor += vec4(lighting, 0.0);
    }
}
fragColor.a = 1.0;    

The attenuation function is a linear function of the distance between the fragment position and each light source.

In this particular scene I use a linear attenuation value of 0.075 of which I generate the light's size/radius as:

Size = 1.0 / Linear;

some observations

When I remove the distance check if(distance <= lights[i].Size) I don't get the weird border issue.

If I visualize the lighting value of a single light source and visualize the distance as distance/lights.Size I get the following 2 images:

Deferred renderer banding

which looks as if the light radius/distance-calculations and light attenuation are similar in radius.

When I change the distance check equation to if(distance <= lights[i].Size * 2.0f) (as to drastically increase the light's radius) I get significantly less border banding, but if I look close enough I do see them from time to time so even that doesn't completely remove the issue.

I have no idea what is causing this and I am out of options at the moment.


Solution

  • This section:

        vec3 lighting;                
        // Ambient            
        lighting += lights[i].Ambient * color;   
    

    You are never initializing lighting before you add to it. I think this can cause undefined behaviour. Try to change it to:

        // Ambient            
        vec3 lighting = lights[i].Ambient * color;