Yadef, Yet Another DEFerred renderer

Hello everyone, in the following post i will try to explain what it is, and how it works, as well as the current optimisation techniques i use and what should/might come in the future.

So lets start.

What is deferred shading?

In short, the system defers the lighting calcultion to a point where all not translucent geometry has already been renderer.
The upsides are:

  • lighting calculation have only to be applied to fragments visible.
  • geometries have to be rendered only once, and not once for each light (compared to singlepass lighting)

The downside are:

  • Increases gpu memory required
  • Adds some overhead (Render target clearing, FBO switching)
  • Since all information about scenegraph layout is lost, no more per object lighting
  • A single light calculation method is applied to all fragments

Owh, two upsides and quite a few downsides, so it it actually worth it?

There is no single answer to that question. It depends on amount of lightsources and the amount of translucent objects.

How does it work?

Note, there are different gbuffer layout used, this is what i have currently, but that might change in the future.

The GBuffer:

In the first pass, the geometry is rendererd front to back as usual, writing WorldPosition, Normal, Diffuse and Specular to the buffers.

As a first optimisation technique the stencil buffer also gets increased for every writen fragment


world position, normal, calculated lighting, diffuse, final limage

In the second pass, calculate the lights.

Ambient lights gets summed up and used as clear color. That was easy.

Directional lights are calculated in a full screen pass. This stages uses the stencil buffer to calculated only the frags that are ‘solid’. Benefits quite a bit from the previous mentioned stencil optimisation.

Point and Directional lights. In simple terms: for each point light, render a sphere. In combination with stencil check, front face culling and depth test set to GreaterEquals i get quite a nice performance here.

For point lights and directional lights i have created also a Batched* technique, that uses the same technique as above, but renders a large number of lights with one drawcall.

Whats next?

  • Lots of testing, caps checking, writing fallbacks.
    Shaders should be optimized.
  • Integration of shadows? Eighter with the current FPP way, or shadowing the solid stuff separately using stencil again as masking.
  • PostProcessing in general.
  • Lots of other stuff
  • Evaluate to possibility/usage of intermediate processing. (Fade Diffuse to gray for example) No tech benefit but it would allow some artistic freedom.
  • Since i have a full HDR pipeline, i am still missing the last stage to resolve that. Automatically or manual. Not yet sure how it will impact
  • I am quite sure i still have a error during the normal calculation in the example shader.

Can i haz?

If you are prepared to use a not tested not yet fully integrated library that does only bring support for a very basic shader. Go ahead. Source can be found on github.

The library is still far from featurecomplete so be prepared that the api might change anytime

NOTE:
Currently it is not possible to build the project. I had to make a small change to the engine itself. Eighter the change gets merged, or i am removing the automatic check.

8 Likes

Did you consider to reconstruct world position from depth instead of rendering the world pos buffer?

1 Like

One of the things i am going to test. In theory a tradeof
128bit texture fetch vs 24bit texture fetch+calculations

Don’t know what the limiting factor is currently. So i can’t even make an educated guess. I am already thinking of adding a small glsl code generator for accessing the gbuffer and offer different (maybe custom) layouts.

Yep it would save some GPU bandwidth.

I have plans to make the filter post processor do that in some way. Act as a back buffer cache that would allow to share Normal buffer, Depth (already the case), or anything you would need in a post process.
And make the main back buffer pass render those buffers as MRTs.
This could also be combined with shader nodes to dynamically add a render target to a material.

Switched to reconstruct world pos from depth. 10k pointlights are in range :wink:

3 Likes

cool :wink:

Fun starts when you start playing with shadows…

For the Opaque queue it is fairly straightforward. Its more the question how the translucent stuff should be handled.
But since i am working on a medieval type of game, translucents are quite rare :wink:

1 Like

Well i see some approaches:

The first is you just dont. care about them and use forward for them.
The second would be a dithering between the layers. Works fine enough for 2 layers and have seen it in multiple games.
The third could be either multiple (amount configurable?) gbuffers that are used one after another, or a similar fixed layer count system. The pro here would be to have a better controll how it renders, and adjust it as necessary per individual game.

Progress for today: added specular lighting.

The current implementation allows to have different colors for albedo and specular. I know in reality i would not know lots of materials that shine in another color.
But i tought i implement it that way to have some artistic liberty, and not all games might follow realism in their art concept.
On the other hand, the gbuffer holding the specular color, can be re- abused to hold metalness/roughness if i once want to implement pbr. (As long as i don’t have a flexible gbuffer layout system)

8 Likes

Well done :clap: