Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I have a shader that computes lighting for each light.

PointLight PointLights[10];

uniform const float NumPointLights;

for(int i = 0; i < NumPointLights; i++)
{
lightVec = PointLights[i].Position - pos;
lightDist = length(lightVec);
    // Do other stuff with light here, etc.
}

If I set NumPointLights to 2, the loop still iterates 10 times, and discards the result of the last 8 iterations.

Should I set a compiler flag that will permit the loop to actually loop only 8 times, or should I make 10 shaders, a shader for each number of lights that can be rendered?

On some small pieces of geometry I occaisionally need 10 lights. 90% of the time I only need 1-2 lights. How do I proceed?

share|improve this question
    
I'm still not sure why, and if someone can explain why I'll accept their answer, but recompiling my code just now generated a shader that now does not loop all 10 times anymore, and in fact loops the number of times that is specified by NumPointLights. –  Olhovsky Mar 24 '11 at 22:05
    
Likely answers to the question in your edit are: "you made a mistake", "you changed compiler options", "you modified the code" and "it works now so who cares?". Your new question is unanswerable as-is, because you haven't provided the code before and after (and if you did that you should make a new question anyway). Just accept an answer to the original question ("How do I proceed?"). –  Andrew Russell Mar 27 '11 at 10:28
    
I think that you're right Andrew. I've accepted an answer. –  Olhovsky Mar 27 '11 at 12:31
add comment

2 Answers

up vote 14 down vote accepted

Your dilemma hints at a larger problem that's well-known to the graphics programming community, commonly referred to as "combinatorial shader explosion." As the name implies, it's usually considered in the context of very large numbers of shader permutations, but the basic principle is the same. Solutions geared toward solving the overarching problem are industrial-strength, and as far as I'm aware, can be categorized into two main approaches:

  1. So-called "Ubershaders"
  2. Code generators

Ubershaders

The term ubershader refers to a shader which has conditional code to handle an extremely large number of permutations with regard to feature support. Likely candidates include variations on light counts/types, animation (number of bones? Dual quaternion skinning?), vertex formats, shadow map sampling and more. Because dynamic branching in shaders both pushes shader limits, and is not particularly performant, these variations are usually controlled by preprocessor directives, varying the values of preprocessor definitions (e.g. #define USE_PCF_SHADOW_FILTERING 1, #define NUM_PCF_SAMPLES 16, etc).

Code generators

Code generators, on the other hand, actually assemble the body of the shader from the ground up. An example of such an approach would be Shawn Hargreaves' shader fragment system.

As Shawn mentions in his article:

If you have five, ten, or even fifty shaders, this system is probably not for you. If you have thousands, however, automation is your friend.

I would say this advice applies to ubershaders as well.

As I mentioned above, these solutions are industrial-strength. Both have their pros and cons, and both are very complex to manage. Importantly, you need to know ahead of time which shader combinations your engine is going to potentially require, or you'll end up compiling them on-the-fly; this is obviously undesirable, because if you don't have the shader handy, compilation takes a considerable amount of time.


Bonus: Deferred rendering

You should also be aware that, in the case of light and shadow complexity specifically, deferred rendering approaches also help cut down on shader permutations.

Imagine the following simple approach (expressed in pseudo-code):

for ( each mesh )
{
  draw mesh with ambient lighting only;
  for ( each light )
  {
    additively blend single light's diffuse + specular contributions \
      for this mesh into frame buffer;
  }
}

Now your shader only needs to handle one light, and you invoke it as many times as necessary. Unfortunately, this naive approach does not scale very well in terms of performance — with deferred shading, you draw each mesh only once, and then draw each light only once.

An in-depth explanation of deferred shading is beyond the scope of this answer, but you can start looking here for more info.

It's similarly possible to calculate shadows in a "deferred" manner, which is much simpler, and plays nicely with traditional ("forward") rendering — you don't need deferred shading to implement deferred shadows.

share|improve this answer
1  
In the shader model 5 (at least in directx11) you can also solve this problem by dynamic shader linking. We was able to transfer 80% of uber shader code to dynamic shader linking. It is much cleaner and nicer. –  Notabene Mar 24 '11 at 19:14
add comment

Ah performance optimisation! The first question is, of course, what are you limited by? Are you pixel-shader limited?

If you are, then obviously you should spend some time on this. If you are not, then simply calculate 10 lights and be done with it.

What I would probably do is implement just two shaders. One that calculates 3 lights (which is fairly traditional), and one that can do an arbitrary number of lights. That way you can work on optimising 90% of your scene, without losing the ability to do fancy lighting in special cases.

Also, you might find my answer here about HLSL conditionals interesting. How are you determining that it is iterating 10 times?

Also, here are three more resources that are somewhat related.

share|improve this answer
    
Interesting reads, thankyou. To answer your questions, I am determining that it is interating 10 times by inference from the fact that my frame rate is 20fps when I set NumPointLights to 10 or to 2. If I reduce the size of the PointLights struct to 2-3 then the frame rate dramatically increases to about 70fps. Clearly it's iterating through the size of the PointLights struct and clearly I'm pixel shader bound. –  Olhovsky Mar 24 '11 at 21:48
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.