Quietly working away - light clipping

· by Steve · Read in about 4 min · (740 Words)

As you may have detected, I’ve been oddly quiet for the last few days, mercifully saving the blogosphere the trouble of carrying my enthusiastically spouted verbiage. The reason is that it’s been a rather busy week, with 2 contracts on the go at once and rather less slack time than I had hoped (no editor work this week), so the forums have been skimmed rather more hurriedly than usual.

One of these contracts involves work on OGRE itself, so I can talk freely about it now you can see the results of it in CVS - some enhancements to light scissoring and clipping. When performing additive lighting routines using a lot of lights of limited range, you can end up doing a lot of passes, some of which will be processing fragments that are outside the light’s range and contributing nothing to the end result. Clearly that’s wasted time and will burn at least some cycles in your pixel shader pipeline even if you use higher profiles and do clever things like early-outing. The hardware scissor is one way to eliminate that, and previously we’d used it to limit the fillrate of shadow volumes. Now, we also use it to limit additive lighting passes, meaning that if your light only covers a small square of screen-space, that’s the only area that will be re-rendered for that light. This is done automatically for the built-in additive shadow modes, which render one light per pass, but you can also enable the option more generally or when using integrated shadows by using the light_scissor on option in your pass. If more than one light is being rendered in that pass at once, their scissors are unioned for that pass. There are also extra optimisations to ensure that if the scissor clips everything, say if the object as a whole is visible but the light only covers a part that is offscreen, the render pass is skipped altogether.

This cuts down on your fragment pipeline. The other feature I added helps to reduce the triangle setup overhead, and potentially the fragment pipeline in the case of spotlights. You can optionally have a set of user clip planes set up for you to mask off the area affected by a light, so that when rendering the additive pass for that light, geometry outside the clip planes is discarded at triangle setup time. The clip planes are a cube for point lights, and a pyramid for spotlights, so clearly when combined with the scissor you can find the area rendered is really quite a tight fit for the lit area. Clip planes do come with a cost though - my experiments have shown that using them when the camera is more often very close to or inside light volumes can often be more costly than not using them, so they are not enabled by default, but they can provide a benefit particularly for small pools of light from spotlights. You can turn them on for all per-light passes when using additive shadow routines using SceneManager::setShadowUseLightClipPlanes, or if you’re using integrated shadows (or no shadows) by using light_clip_planes on in your pass. Note however that unlike scissors, this will only do something if the pass receives a single non-directional light as its lighting state, since forming a single convex shape out of multiple lights is likely to not be worth the effort, especially since most cards only support a maximum of 6 hardware accelerated user clip planes.

Obviously with both of these techniques it assumes that your materials include an initial pass before the additive pass(es) - most likely this will be an ambient (with or without occlusion), or hemisphere lighting pass. This initialises the depth buffer and seeds the lighting with the stuff that’s independent of individual scene lights. With integrated shadows, you might also want to collapse a directional light / shadow pass into this as well, since directional lights are always ‘unclippable’ (is that a word? it is now ;)) and are guaranteed to be at the front of the light list so will go to the early passes first if you set your passes up correctly (first pass with max_lights 1, and second pass with start_light 1 plus max_lights and iteration options set based on how many lights you process at once in subsequent passes).

I’m sure that will come in useful to others as well as the customer I did this for. Enjoy!