2
\$\begingroup\$

I would like to support a few different lighting models in my deferred renderer, such as: Phong OrenNayar and Cook-Torrance, so a small limited set to support a bit more than just a single lighting model.

What i came up with:
The most obvious way seems to simply branch in the shader based on some information stored in a texture during the geometry pass. I have come to learn however that branching in shaders is generally a bad idea (at least that's what i read everywhere). So I came up with a branch-less version by using the stencil buffer to mask each of the few shaders, and rendering each light multiple times, once for each lighting model. (The stencil buffer could be filled during the geometry pass and then stored in R8, and with some trickery you can use it as well for light masking.)

Now the question is, what is the best way to do this? Did I miss any better techniques? I could not find many usefull sources online.

\$\endgroup\$
  • \$\begingroup\$ Branching in shaders has gotten substantially better, especially when you expect large contiguous areas to all take the same branch — most of the problems arise when the branch decisions change in a random-ish way so a single shading quad has to take all the branches in turn. That said, implementing separate lighting models like this is not done very often these days. Most game tech is moving toward physically based rendering, where we describe the surface characteristics in parameters, like say a roughness value that blends between dusty Oren-Nayar-like shading and smooth Phong-like shading. \$\endgroup\$ – DMGregory Sep 20 '17 at 11:10
  • \$\begingroup\$ does this seems like a good reference? learnopengl.com/#!PBR/Theory. \$\endgroup\$ – Aedoro Sep 20 '17 at 12:09
  • 3
    \$\begingroup\$ Does it help you? Then yes it is. Does it not help you? Then no it isn't. Whether a resource is useful for your project's needs and your preferred learning style isn't something a stranger can determine for you. ;) \$\endgroup\$ – DMGregory Sep 20 '17 at 12:11
  • \$\begingroup\$ ALU ops are fast; depending how you implement this you might get the best perf by just setting some uniforms to 0 and otherwise doing the full calculations for each model. \$\endgroup\$ – Maximus Minimus May 30 '18 at 12:53
1
\$\begingroup\$

One way to do it is to have 3 different shaders and simply using the one that you need at the time. There are a number of ways you can do this.

You could have 3 separate files (or just strings in your code) that contain your shaders, compile them and use them as necessary. That's easy to code up the first time, but maintenance becomes a pain because if you change some non-lighting related stuff in one shader you have to remember to change it in all.

So what I've done in the past is have a file or string that contains just the Phong lighting calculations in a function called something like calculateLighting(). Put that into its own file or string. For the Oran-Nayer, also make a function called calculateLighting() with the exact same parameters, and put it into a file or string. Same with the Cook-Torrance shader.

You'll also have your base shader that does everything except the lighting. Where you would normally have the lighting, simply call the function. At runtime you can take the Phong version, concatenate the base shader, and compile it. Then take the Oran-Nayer version, concatenate the base shader and compile it. Do the same thing with the Cook-Torrance version. Now, when you need to change something in the base shader, it take affect in all 3.

\$\endgroup\$
  • \$\begingroup\$ The missing ingredient here is how to work with deferred rendering, where the pass that renders the light is sampling from a G-buffer rather than executing separately for each mesh. \$\endgroup\$ – DMGregory Oct 6 '17 at 3:50
  • \$\begingroup\$ Is there a reason why you can't use this technique for the pass that samples the G-buffer? \$\endgroup\$ – user1118321 Oct 6 '17 at 4:25
  • \$\begingroup\$ It's a little complicated there. You need some method for, say, shader variant A to know that these G-buffer texels are supposed to be lit with shader variant B, because they belonged to an object using B-type lighting. It might entail multiple rendering passes per light, on for each shader variant you've described (though one could perhaps try to detect in advance which parts of the screen/depth space contain which lighting needs to exclude as much redundant work as possible) \$\endgroup\$ – DMGregory Oct 6 '17 at 10:08
  • \$\begingroup\$ Ah, I see. I was assuming the entire scene was using the same type of lighting, rather than it changing per object. \$\endgroup\$ – user1118321 Oct 6 '17 at 15:32
0
\$\begingroup\$

Unfortunately, branching based on result read from texture is going to be less efficient regarding shader complexity/occupancy than using the stencil trick that is mentioned.

The stencil trick will be a burden more on the CPU however, as there will be need to render each light multiple times and switch shaders in between. There is also need for a stencil buffer which uses GPU memory space and bandwidth. The stencilling path will be possibly harder to maintain and more error prone, so there is also that.

\$\endgroup\$

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.