7
\$\begingroup\$

I am refactoring some parts of the game engine I am working on. This engine is made in C# with XNA.

The part I have trouble with is shader/material and the render queue. In the new version I will give ability to indicate differents render states per pass within a technique (blend state, rasterizer state, ...). In my engine, all renderables have a material and each material is associated to one technique (thus to one or more passes).

Later, when the game will be running, I will add renderable objects to the render queue and then sort it based on the pass used by each material. What I want to achieve is to reduce states changes between draw calls by first grouping same passes together and then passes with nearly identical states to be as closed as possible to minimize changes. But this solution seems to be problematic when it comes to use technique with more than one pass with completely different states. Passes within a same technique can be completely separated in the render queue and not rendered one after the other. If I say that it's because I have always see multi-passes technique render in a loop and all passes rendered immediately like that :

foreach(EffectPass pass in technique.Passes)
{
    /* Update parameters */

    pass.apply();
    graphicsDevice.Draw(...);
}

But I don't know if I can deferred pass rendering from a same technique instead of using them one after the other as in the previous example.

Here is an example of what will probably happen with the solution I try to build:

  • Shader1
    • TechniqueA
      • Pass1 (State A)
      • Pass2 (State C)
  • Shader2
    • TechniqueB
      • Pass1 (State A)
  • Shader3
    • TechniqueC
      • Pass1 (State A)

Now I have three models to draw, Model1, Model2 and Model3, each one use a different technique from the previous list. If I add those three models in the render queue I will end up with the following render queue :

-> Set state A -> Draw Model1(Pass1) -> Draw Model2(Pass1) -> Draw Model3(Pass1) -> Set state C -> Draw Model1(Pass2)

We can see that the Model1 will be drawn two times (two passes for the Technique A) but the draw call are separated by others draw call.

I don't know how to resolve this. What will be a good solution to this problem? How can I design my render queue to reduce state changes as much as possible between draw calls.

EDIT:

After searching a little more, I have found this helpful article : http://realtimecollisiondetection.net/blog/?p=86. It seems to be an elegant way to sort object by depth and materials. And in the material id, I can had a pass id to sort same pass together that will allow to reduce state changes.

Currently my renderer draw objects directly in the backbuffer without lights nor shadows, everything is flat. So I will just loop over my render queue and call draw methods. But in a future I will probably implement deferred rendering.

I don't know a lot about it but I know that I have to render my objects in different render target to gather differents informations (Z-buffer, Normal buffer, G-Buffer, ...). I assume that for z-buffer and normal buffer I will use the same shader on every objects no matter which material they use. But for the G-buffer I will use the materials of each object so that means different shaders with different number of pass. If I use the solution in the previous link, if I have an object with a material that needs to do two or more passes, my render queue will have at least two times the object (one for each pass). But for Z-buffer or normal buffer isn't it useless to have the same object multiple times?

What I see is that I will need to have at least two different render queue : one for G-buffer in which an object can be multiple times depending on the number of pass the material require and another render queue for Z-buffer or other buffer in which each objects are only one time. Am I right?

\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

The draw order often implied by tutorials, where you do something like this:

for each object:
    for each pass:
        apply pass state
        draw object

is actually backwards from how it makes sense to do it in a "real game" context. Rather, you'd be more likely to do something like:

for each pass:
    apply pass state
    for each object (grouped by shader/material/mesh/etc):
        draw object

Multiple per-object passes is almost never what you actually want. Instead, you want to accumulate all the objects that use a certain pass, and then draw all those objects together.

For example, some common passes you'd encounter are:

  • Depth pre-pass
  • Shadow map pass
  • Deferred shading pass
  • Decal pass
  • Lighting pass
  • Forward shading pass
  • Translucency pass
  • Postprocessing pass
  • UI pass

Not every shader will include every one of these passes - for instance, in a deferred renderer, a typical "opaque object" shader might only use the first three passes, a lighting shader would only use the lighting pass, etc.

In cases like this, naturally you want to draw all the objects in the depth pre-pass together, so you'd make a render list that includes all those objects, then apply the pass state once and draw them together. Then you apply the shadow map pass state and draw all the objects that go in the shadow map, and so on.

There are only rare special cases where you actually want per-object passes - for example, subsurface scattering using texture-space diffusion, where you draw the object's lighting into an offscreen render target, blur it, then draw the object on screen using the blurred lighting (3 passes per object). This sort of thing doesn't scale well - it gets very expensive as you add objects - so engine programmers try to avoid it as much as possible.

\$\endgroup\$
10
  • \$\begingroup\$ Thanks for your response, it helped me think about my problem. Meanwhile I have found an interesting article about a method to sort a render queue. I think about implementing it but I have a lot more questions, so I edited my question with more details and the link to the article. \$\endgroup\$ Commented Apr 7, 2014 at 12:47
  • \$\begingroup\$ @Takumi What I call "pass" here is more like what is called "layer" in that article. It would be quite unusual for a material to have more than one deferred pass. Different objects will have different materials, but they will all be drawn once in the deferred pass. And as I mentioned in my answer, many materials will be in some passes but not others. You can still keep everything in one master render queue as long as it's sorted by pass. \$\endgroup\$ Commented Apr 7, 2014 at 20:25
  • 1
    \$\begingroup\$ @cubrman If using the effect framework, IIRC you need to call Apply each time you change parameter values. \$\endgroup\$ Commented Apr 11, 2015 at 1:19
  • 1
    \$\begingroup\$ @cubrman I wouldn't say that. Updating shader parameters is one of the most lightweight state changes, so I wouldn't worry about it too much. You can still minimize the expensive state changes (switching shaders, switching render targets) by grouping your objects by shader. And you can avoid switching textures, vertex buffers etc. (medium expensive) by grouping your objects further by material and mesh, within the grouping by shader. \$\endgroup\$ Commented Apr 11, 2015 at 6:16
  • 1
    \$\begingroup\$ @cubrman Yeah, changing techniques or passes within an effect file does change the shaders. Anytime you switch to a different individual vertex or pixel shader program, that's changing shaders. \$\endgroup\$ Commented Apr 12, 2015 at 20:13

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .