{"id":167,"date":"2009-06-29T09:27:46","date_gmt":"2009-06-29T16:27:46","guid":{"rendered":"http:\/\/gameangst.com\/?p=167"},"modified":"2009-06-29T09:27:58","modified_gmt":"2009-06-29T16:27:58","slug":"the-benefits-of-forward-rendering","status":"publish","type":"post","link":"http:\/\/gameangst.com\/?p=167","title":{"rendered":"The Dark Side of Deferred Shading: Light Groups"},"content":{"rendered":"<p>Much is made of the advantages of deferred shading, but to read most articles on the subject the only disadvantages are translucency and multisample antialiasing. \u00a0This is really doing a disservice to forward rendering, however, as there are many popular techniques in real-time computer graphics that are impractical in a deferred shading engine. \u00a0One of the best examples of these techniques is light include \/ exclude groups.<\/p>\n<p>Although it may not have much basis in reality, being able to specify exactly which lights affect which objects is a powerful tool in lighting design. \u00a0It can be used stylistically to make certain objects stand out from their environments, for example making player avatars artificially brighter than their surroundings. \u00a0It can also be used to compensate for limitations of the real-time lighting model, for example skin can be lit with additional lights to simulate light transmission and subsurface scattering. \u00a0Light groups are also extremely useful in optimizing shadow computation. \u00a0A common problem that must be dealt with when lighting indoor environments is light bleeding through walls. \u00a0Although this can be solved by any number of real-time shadowing techniques, it simply isn&#8217;t affordable on current hardware to cast real-time shadows from every light. \u00a0A cheap and flexible alternative is to use light groups to manually specify that lights in a room only affect objects in the same room.<\/p>\n<p>Implementing light groups is cheap and easy with forward rendering. \u00a0Each light can store a list of meshes that it either explicitly includes or explicitly excludes. \u00a0Since meshes are already being matched with lights on the CPU, it is an easy thing to query the include \/ exclude list for each light and discard lights that don&#8217;t affect the current mesh. \u00a0The implementation of light groups in a deferred environment, however, is much less obvious.<\/p>\n<p>One option for supporting light groups in a deferred renderer is to add an additional attribute to the G-Buffer specifying a unique mesh ID. \u00a0The light shaders, during the deferred pass, can read back the mesh ID per pixel and compare that to an include \/ exclude list passed to the shader in a texture or in constant registers. \u00a0Mesh IDs can be allocated on-demand and reused cleverly across the frame, so it is possible that as few as 8 bits are needed for them in the G-Buffer. \u00a0Likewise with some careful engineering the light shader can perform the light group classification in as little as one indirect texture fetch per pixel. \u00a0Unfortunately, no amount of optimization in this technique can address its largest drawback. \u00a0The light group classification is performed in the fragment shader which means that a light&#8217;s fragment shader must run even for pixels that aren&#8217;t in the light&#8217;s group. \u00a0That is a huge drawback from the forward implementation which incurs absolutely zero GPU cost for lighting meshes that aren&#8217;t in a light&#8217;s group. \u00a0If one of the principle advantages of light groups is that they allow for optimized, statically-defined shadows, this deferred implementation isn&#8217;t going to cut it.<\/p>\n<p>A second, and far more common, option for supporting light groups with deferred shading is to define only a fixed number of light groups. \u00a0With this approach meshes and lights can be associated with multiple light groups, and lights only affect meshes with which they share membership in a group. \u00a0The number of light groups is usually selected to be less than or equal to 8, and the stencil buffer is written during the G-Buffer pass as a per-pixel mask of the groups each mesh belongs to. \u00a0During the deferred pass lights use the stencil test hardware to discard any pixels that aren&#8217;t in any of the light&#8217;s groups. \u00a0This technique for supporting light groups has several advantages over the previous technique. \u00a0It requires no additional storage in the G-Buffer, it adds no additional cost to the light shaders, and it has the potential at least to discard pixels that aren&#8217;t in a particular light&#8217;s groups prior to executing the light&#8217;s fragment shader.<\/p>\n<p>There are some downsides, however. \u00a0The stencil buffer can be a precious commodity in a deferred renderer. \u00a0Engines using stencil shadows will need to reserve a piece of it and most deferred renders will also use a few bits of the stencil buffer to mask and quickly cull pixels that are unlit or outside a light&#8217;s depth bounds. \u00a0One consequence of this is that the number of bits available in the stencil buffer for a light group mask is usually less than 8 (4 to 6 in my experience). \u00a0Six unique light groups are better than none, but the limit does make it much more difficult to use light groups for statically defined shadows. \u00a0Assigning light groups to rooms in a level to prevent light bleeding through walls becomes a form of the computationally challenging <a href=\"http:\/\/en.wikipedia.org\/wiki\/Graph_coloring\">graph coloring<\/a> problem.<\/p>\n<p>Another consequence of this technique&#8217;s reliance on the stencil buffer is that since its stencil test logic is complicated and changes with every light, it may disable or dramatically reduce the effectiveness of the GPU&#8217;s pre-fragment shader stencil cull. \u00a0On most hardware early stencil cull is accomplished by storing a low resolution, low bit depth cache of the stencil buffer. \u00a0Blocks of between 16 and 64 pixels are reduced to a single bit that represents a conservative average of the stencil function across all pixels in the block. \u00a0This cache is then consulted prior to running the fragment shader to quickly discard pixels which are guaranteed to fail the stencil test. \u00a0Given this implementation of early stencil in the hardware, there are two options for utilizing it with light groups.<\/p>\n<p>The first option is to sort all lights by their light group mask and refresh the early stencil cache between lights when the mask changes. \u00a0This provides pre-fragment shader culling for light groups (and anything else you might be using the stencil buffer for), but it requires a potentially expensive refresh of the early stencil cache. \u00a0It also adds a new criterion on which to sort lights, and there are plenty of other deferred shading techniques that have their own competing sort order requirements.<\/p>\n<p>The second option is to give up on pre-fragment shader culling for light groups and instead use the early stencil cache solely for stencil shadows and light depth bound culling. \u00a0To do this one would simply mask out and ignore the stencil bits representing light groups when populating the early stencil cache. \u00a0With this option light groups no longer have any impact on performance&#8211;no cost, no benefit. \u00a0This option may sound like a defeat, but even it isn&#8217;t available on all platforms. \u00a0The Xbox 360, for example, can&#8217;t apply a mask during the construction of the early stencil cache, so there is no way to exclude light groups from early stencil cull. \u00a0The best option on that platform without refreshing early stencil frequently is to make the early stencil test much more conservative. \u00a0That means that the presence of light groups will actually allow some pixels that would have been culled due to stencil shadows or light depth bounds to be processed by the fragment shader, and light groups become an unpredictable performance cost.<\/p>\n<p>On the Xbox 360 and Playstation 3, at least, the early stencil hardware is well documented and fully exposed to developers. \u00a0On the PC, however, the concept of early stencil cull is entirely hidden from developers and implemented transparently within the driver. \u00a0This makes sense since early stencil cull is an optimization very specific to individual hardware implementations and has no impact on the correctness of the output, but it makes relying on early stencil cull for performance extremely dangerous. \u00a0Even when you know a lot about your rendering pipeline and your game&#8217;s data, it isn&#8217;t clear what the optimal use of early stencil cull is for light groups. \u00a0Drivers don&#8217;t have enough information to make the best choice on your behalf, and when the stencil configuration gets too complicated they are likely to just disable early stencil cull entirely! \u00a0So a scary third option for implementing light groups with the stencil buffer is that you lose all early stencil cull during your deferred pass and stencil shadows and light depth bound culling get a lot more expensive.<\/p>\n<p>I&#8217;m a huge fan of deferred techniques, but it is important to remember that they are not a one-for-one replacement for forward rendering. \u00a0Forward rendering creates a tight coupling between the geometry and the frame buffer; its greatest weakness perhaps, but also its greatest strength. \u00a0When evaluating a move from forward to deferred rendering, don&#8217;t overlook the fact that light groups and a host of similar mesh-centric features aren&#8217;t going to be available anymore.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Much is made of the advantages of deferred shading, but to read most articles on the subject the only disadvantages are translucency and multisample antialiasing. \u00a0This is really doing a disservice to forward rendering, however, as there are many popular techniques in real-time computer graphics that are impractical in a deferred shading engine. \u00a0One of [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[12,6],"_links":{"self":[{"href":"http:\/\/gameangst.com\/index.php?rest_route=\/wp\/v2\/posts\/167"}],"collection":[{"href":"http:\/\/gameangst.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/gameangst.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/gameangst.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/gameangst.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=167"}],"version-history":[{"count":20,"href":"http:\/\/gameangst.com\/index.php?rest_route=\/wp\/v2\/posts\/167\/revisions"}],"predecessor-version":[{"id":169,"href":"http:\/\/gameangst.com\/index.php?rest_route=\/wp\/v2\/posts\/167\/revisions\/169"}],"wp:attachment":[{"href":"http:\/\/gameangst.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=167"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/gameangst.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=167"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/gameangst.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=167"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}