Sign in to follow this  
Ender1618

Foward rendering with multiple lights, the state of affairs.

Recommended Posts

Yet another post asking how to implement multiple lights in a forward rendering shader based engine.

So I have decided I need a forward rendering solution, and will defer deferred shading for another time. So the issue of course is how to deal with multiple lights of varying types. I have come across 3 methods, but am having trouble choosing between them.

BTW I am working in DX9, HLSL, Shader 3 (but want to avoid dynamic branching)

Method 1:
ATI's fixed function emulation style. Keep 1 list of all lights (point, spot, dir clumped into the same list). Have a start index (into the light list) per light type and the number of lights of that type. Then have a separate loop for each light type accumulating the lighting.


struct CLight
{
int iType;
float3 vPos;
float3 vDir;
float4 vAmbient;
float4 vDiffuse;
float4 vSpecular;
float fRange;
float3 vAttenuation; //1, D, D^2;
float3 vSpot; //cos(theta/2), cos(phi/2), falloff
};

CLight lights[MAX_LIGHTS];

int iLightDirIni;
int iLightDirNum;
int iLightPointIni;
int iLightPointNum;
int iLightSpotIni;
int iLightSpotNum;

...

//directional lights
for(int i = 0; i < iLightDirNum; i++)
{
COLOR_PAIR ColOut = DoDirLight(N, V, i+iLightDirIni);
Out.Color += ColOut.Color;
Out.ColorSpec += ColOut.ColorSpec;
}
//point lights
for(int i = 0; i < iLightPointNum; i++)
{
COLOR_PAIR ColOut = DoPointLight(vPosition, N, V,i+iLightPointIni);
Out.Color += ColOut.Color;
Out.ColorSpec += ColOut.ColorSpec;
}
//spot lights
for(int i = 0; i < iLightSpotNum; i++)
{
COLOR_PAIR ColOut = DoSpotLight(vPosition, N, V,i+iLightSpotIni);
Out.Color += ColOut.Color;
Out.ColorSpec += ColOut.ColorSpec;
}






I am assuming this would only result in static branching.

Method 2:

Use the effects framework to auto-generate variants of the shader, by passing in different values (e.g. number of lights) to the shader function in different techniques. Like so http://mynameismjp.wordpress.com/2009/01/19/teach-your-effects-a-new-trick/. Not sure how to deal with light type variants though.

Method 3:
Multiple blended passes.

So my questions are: What is the detriment to method 1? Concerning method 1, I hear one should avoid static branching since the compiler cant optimize as well, is that true for this case? Wouldn't method 2 result in alot of switching between shaders that method 1 would avoid? Wouldn't method 3 result in alot of redundant geometry transforms (especially for skinned meshes), does this matter?

I know the answer to most of these question is "it depends". But i just wanted to query what people think in general. I wouldn't be dealing with many lights at a time, probably max of 6.

Share this post


Link to post
Share on other sites
Quote:
Original post by Ender1618
So my questions are: What is the detriment to method 1? Concerning method 1, I hear one should avoid static branching since the compiler cant optimize as well, is that true for this case? Wouldn't method 2 result in alot of switching between shaders that method 1 would avoid? Wouldn't method 3 result in alot of redundant geometry transforms (especially for skinned meshes), does this matter?


You're pretty much right on all counts. Any kind of branching (static or dynamic) can be tricky to maintain good performance, and can vary wildly depending on the hardware or even the driver. For #2 creating oodles of shader permutations will avoid branching, but you'll have to do a lot of shader switching which can be costly CPU-wise. Plus the number of permutations can quickly spiral out of control, once you start adding in material parameters and normal maps and shadows...it doesn't take long before it becomes very difficult to manage, even with a custom system in place for generating the shaders. For #3 yeah, you have to re-submit and re-transform your meshes for each light. This can really start to hurt if you have complex, skinned meshes and normal maps. Plus you have to re-sample your albedo textures/normal maps, AND you have to pay the extra bandwidth cost of blending to the framebuffer. Basically they're all different levels of trade-of between performance and complexity, with neither being ideal in both respects. This is why everyone and their mother is using a deferred renderer these days.

Share this post


Link to post
Share on other sites
In practice you can combine method 1 and 2 to decent effect. Use method 1 and also use method 2 for hotspots(eg if a lot of materials have only one directional light). Although I would split the arrays for light types to save constant registers and simplify things.

(dynamic looping is not too bad and if your hardware has enough bandwidth to make defered feasable I bet it can do dynamic loops).

Even if you have defered, you will still need (some) forward rendering/lighting for translucent objects.

Also it is a good idea to check if you are bandwidth or shader bound... (especially on less expensive hardware with cheap memory).

No point worrying about extra shader instructions if you are completely limited by memory bandwidth.

David

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this