# unsmooth blending with deferred shading and light volumes

So I implemented deferred Shading with a single full screen light pass (picture 2) (by passing the light-data as an array to the shader) and deferred shading with point light volumes (picture 1) (by rendering the back faces of spheres with the radius of the light). To "add" the color of two separate lights i used blending

glCullFace(GL_FRONT);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);

(while in the single light pass i just added the light values to the rgb color) Else both light calculations are the same. Yet i get those weird "lines" where one pixel is being lit by more than one light source, whereas the single pass results in a smooth transition. I guess the problem is the blending equation, but i can't find a solution. The resulting color of the light volume shader looks like this

//d is the normal/light direction equation
//sp is the specular value
//att is the attenuation
//diff is the material color
color = vec4(diff * light_color * (d + sp) * att, 0.0);

The described values should be right.

• It looks to me like you're dealing with quantization error, since in one version you're rounding everything to an 8-bit value after each blend, but in the other you're doing all your lighting calculations in floating point precision and rounding only the final output. – DMGregory Apr 5 at 12:06
• @DMGregory re-edited it and posted the answer below. So there is no other possibility than the one i described? – Sprinklerkopf Apr 5 at 19:06

I solved the problem by using an extra fbo for the point lights which's color texture then is passed to the deferred shader and added to the directional lights shading. This actually solved the banding, but is there a smarter way? FBOs cost a lot of bandwidth...

You actually have noticeable banding in both images.

This can occur due to quantization.

Inside your shader, your local variables can be stored at floating point precision, able to represent differences in light intensity to a precision of $$\\pm 2^{-24}\$$ or better. But when you blend that result into a low dynamic range buffer that's storing just 8 bits per channel, you're limited to a precision of $$\\pm 2^{-9}\$$ - that's a lot more coarse, so some rounding has to happen.

Worse, when you render your next pass and try to blend the result, you need to read the previously rounded value, modify it, then round it again - so the rounding errors can stack up and get worse. (And sRGB vs linear conversions complicate this further)

Where a pixel that was just a little too dark to round up buts up against a pixel that was just a little too light to round down, you get a line - a visible step up in colour or intensity.

One robust fix is to use an HDR render target in a floating point format, so you can maintain your precision through multiple rounds of blending, and then resolve to a low dynamic range in a single pass at the end when presenting the finished frame.

This also costs a substantial amount of bandwidth, but it comes with other benefits, like being able to render high-contrast scenes with realistically bright hot spots on lights - something we usually have to compromise on to fit within the LDR limits. Optical effects like exposure adaptation, bloom/glow, lens flares, after images, and motion blur are all a little easier to implement correctly in HDR, whereas we sometimes have to cheat them to compensate for the clamped intensity range in LDR.

If you can't afford HDR rendering in your case, then another common way to battle banding is to add dithering. This diffuses out the quantization errors so they don't all align in obvious bands and lines. And if you're using a temporal anti-aliasing pass, it can smooth out the resulting noise to give you the smooth blend you wanted. This is how Inside achieves its low-contrast gradients, and the developers have shared some details about their specific dithering techniques.