[HLSL] Multiple light types?

Started by
3 comments, last by InvalidPointer 12 years, 5 months ago
How do i write to handle dynamic lighting of different source types?

I mean i know i can use branching like

for (int i = 0; i < NUM_LIGHTS; i++)
{
if (LightType == DIRECTIONAL)
...
if (LightType == POINT)
...
if (LightType == SPOT)
...
}


But there has to be a more optimal way.

This thread talks about treating directional and point lights as spot lights with different parameters. Is that recommended? If not then what do i do?
"Spending your life waiting for the messiah to come save the world is like waiting around for the straight piece to come in Tetris...even if it comes, by that time you've accumulated a mountain of shit so high that you're fucked no matter what you do. "
Advertisement
you can do it like you have it up there, using the "if" statements. but instead of having all the lighting code in the shader, you can use functions, stored in the effect file, and just call the functions in the if statements, if that's what your wondering.
How do i write to handle dynamic lighting of different source types?
That's the problem with light management. You really have to bonk your head on the problem and keep going until something cracks, hoping it's not you.
A more efficient way (I'd say standard) to do that is to keep directional lights in an array, point in another, spot in another. So, there's no "light type" information, it is just taken from granted as you have put it in the right place since the start (edit: that's the whole point of the thread you linked).
However, this still scales poorly. Spotlights are ok... but what about spotlights projecting a texture? What if a light is a "filler" and should cast no shadows? We end with a tree of light types and sub-types... uh, that might require some planning.

Perhaps you have heard about this thing called deferred shading. I'm not much of a fan. But I admit its ability to separate lighting stages from the surface computation is ... ok, totally awesome. Suddenly, the shaders don't need to mind about lighting anymore, you can apply lights as a post processing effect! Of course you get a few other issues but it's absolutely worth looking into it.

Previously "Krohm"

I have lights as a single, all encompassing class type that has appropriate member variables for every attribute a light might have, (whether it be spot, parallel, whatever), with an enum which shows which type it is. When I draw, for each object I pick out the brightest n number of lights (n can be dynamically altered, and also the number of frames which pass before the light list is updated, so slower moving objects are updated less often).

The basic light class can be cast to a POD struct, I have a struct type for point, spot and parallel lights. I iterate through the list of lights, checking their type and casting to the appropriate structs, which I put into an array for each type and send each array to the GPU. I also send a counter of the number of lights of each type.

In the .fx file, I can then treat each light type separately, (common functions like working out specular intensity are #included from a separate .fx file called 'lights.fx') and use a for loop to iterate over however many lights I have of each type. This lets me keep any 'if' statements in the main program and out of the shader code. There's a little memory overhead as I have to have redundant space in the GPU constant buffers (as I'll always have less lights that I have reserved buffer space for), but I'm never going to have many light sources compared to the number of other things like vertex buffers and textures.
Argh argh argh as a shader programmer the example code makes me want to claw my eyes out :| Achieving acceptable performance in graphics is all about batching, and CPUs are vastly better at rearranging things like this. For starters, I'd suggest making 'light bins' for your CPU representation; basically small queues of lights keyed to the 'type' (in this case, point/omni, spot, directional and maybe area if you're getting really fancy) in question. This works even better if you make this data distinction at an even higher level, say thinking about things at the absolute highest level in terms of global lists of lights of each type.

From there, you can basically increment counters to allocate spots in the queues, (don't need to sort them either, which is really nice) then just copy light information into the index you received. This has a number of benefits; it can be combined with the shader permutation ideas suggested above very naturally, and swapping between pass-per-light and something like pass-per-n-lights really just boils down to changing some loop conditions and shader constant update parameters. You could theoretically even combine the two in order to cut down on the number of shader permutations-- have a specialized version that handles four lights of the given type in parallel, and then once you've got less than four lights in the queue in question, do them one at a time.

As for updating the queues, the best way to do this, IMHO, is to recompute the set whenever an entity moves or is placed into the world. This is sort of a dumb trick I saw in a CoDBlOps presentation, and in hindsight it's really obvious.You can also do some of the caching tricks I'll touch on at the end of this too.

Re: doing the lighting, you have a few options. You can either compute lighting for each source individually, as has been mentioned, *or* you can try to merge sources into a low-frequency representation, or any combination thereof. As an example, Valve picked the ~2 most important lights and sent them into the shader for analytical evaluation, sticking the rest into the 'ambient cube' used for precomputed lighting. Computationally, this works out to doing ~6 dot products (one for each face of the cube) on the CPU, multiplying by light color, and then adding the result to the accumulated cube face contribution. Toss that into a shader and index, and voila-- very close to constant-time lighting in practical performance terms.

Epic does something slightly different with current-generation UE3. Unlike Valve, they project *all* lighting information into a low-frequency basis, this time using spherical harmonics. From there, they use some Stupid SH Tricks (EDIT: GDNet keeps eating my link; that should go to www.ppsloan.org/publications/StupidSH36.pdf)[font="arial, sans-serif"][color="#009933"] [/font]to pull out n of the most dominant directional lights for analytical evaluation and take the mostest importantest and feed that into their modulative shadow system, as described here.
clb: At the end of 2012, the positions of jupiter, saturn, mercury, and deimos are aligned so as to cause a denormalized flush-to-zero bug when computing earth's gravitational force, slinging it to the sun.

This topic is closed to new replies.

Advertisement