LearnOpenGL

Translation in progress of learnopengl.com.
git clone https://git.mtkn.jp/LearnOpenGL
Log | Files | Refs

Deferred-Shading.html (34457B)


      1     <h1 id="content-title">Deferred Shading</h1>
      2 <h1 id="content-url" style='display:none;'>Advanced-Lighting/Deferred-Shading</h1>
      3 <p>
      4   The way we did lighting so far was called <def>forward rendering</def> or <def>forward shading</def>. A straightforward approach where we render an object and light it according to all light sources in a scene. We do this for every object individually for each object in the scene. While quite easy to understand and implement it is also quite heavy on performance as each rendered object has to iterate over each light source for every rendered fragment, which is a lot! Forward rendering also tends to waste a lot of fragment shader runs in scenes with a high depth complexity (multiple objects cover the same screen pixel) as fragment shader outputs are overwritten.
      5 </p>
      6 
      7 <p>
      8   <def>Deferred shading</def> or <def>deferred rendering</def> aims to overcome these issues by drastically changing the way we render objects. This gives us several new options to significantly optimize scenes with large numbers of lights, allowing us to render hundreds (or even thousands) of lights with an acceptable framerate. The following image is a scene with 1847 point lights rendered with deferred shading (image courtesy of Hannes Nevalainen); something that wouldn't be possible with forward rendering.
      9 </p>
     10 
     11 <img src="/img/advanced-lighting/deferred_example.png" alt="Example of the power of deferred shading in OpenGL as we can easily render 1000s lights with an acceptable framerate"/>
     12   
     13 <p>
     14   Deferred shading is based on the idea that we <em>defer</em> or <em>postpone</em> most of the heavy rendering (like lighting) to a later stage. Deferred shading consists of two passes: in the first pass, called the <def>geometry pass</def>, we render the scene once and retrieve all kinds of geometrical information from the objects that we store in a collection of textures called the <def>G-buffer</def>; think of position vectors, color vectors, normal vectors, and/or specular values. The geometric information of a scene stored in the <def>G-buffer</def> is then later used for (more complex) lighting calculations. Below is the content of a G-buffer of a single frame:
     15 </p>
     16   
     17   <img src="/img/advanced-lighting/deferred_g_buffer.png" alt="An example of a G-Buffer filled with geometrical data of a scene in OpenGL"/>
     18     
     19 <p>
     20   We use the textures from the G-buffer in a second pass called the <def>lighting pass</def> where we render a screen-filled quad and calculate the scene's lighting for each fragment using the geometrical information stored in the G-buffer; pixel by pixel we iterate over the G-buffer. Instead of taking each object all the way from the vertex shader to the fragment shader, we decouple its advanced fragment processes to a later stage. The lighting calculations are exactly the same, but this time we take all required input variables from the corresponding G-buffer textures, instead of the vertex shader (plus some uniform variables).
     21 </p>
     22     
     23 <p>
     24   The image below nicely illustrates the process of deferred shading.
     25 </p>
     26     
     27     <img src="/img/advanced-lighting/deferred_overview.png" class="clean" alt="Overview of the deferred shading technique in OpenGL"/>    
     28     
     29 <p>
     30   A major advantage of this approach is that whatever fragment ends up in the G-buffer is the actual fragment information that ends up as a screen pixel. The depth test already concluded this fragment to be the last and top-most fragment. This ensures that for each pixel we process in the lighting pass, we only calculate lighting once. Furthermore, deferred rendering opens up the possibility for further optimizations that allow us to render a much larger amount of light sources compared to forward rendering.
     31 </p>
     32     
     33 <p>
     34   It also comes with some disadvantages though as the G-buffer requires us to store a relatively large amount of scene data in its texture color buffers. This eats memory, especially since scene data like position vectors require a high precision. Another disadvantage is that it doesn't support blending (as we only have information of the top-most fragment) and MSAA no longer works. There are several workarounds for this that we'll get to at the end of the chapter.
     35 </p>
     36     
     37 <p>
     38   Filling the G-buffer (in the geometry pass) isn't too expensive as we directly store object information like position, color, or normals into a framebuffer with a small or zero amount of processing. By using <def>multiple render targets</def> (MRT) we can even do all of this in a single render pass.
     39 </p>
     40     
     41 <h2>The G-buffer</h2>
     42 <p>
     43   The <def>G-buffer</def> is the collective term of all textures used to store lighting-relevant data for the final lighting pass. Let's take this moment to briefly review all the data we need to light a fragment with forward rendering:
     44 </p>
     45     
     46 <ul>
     47   <li>A 3D world-space <strong>position</strong> vector to calculate the (interpolated) fragment position variable used for <var>lightDir</var> and <var>viewDir</var>. </li>  
     48   <li>An RGB diffuse <strong>color</strong> vector also known as <def>albedo</def>.</li>
     49   <li>A 3D <strong>normal</strong> vector for determining a surface's slope.</li>
     50   <li>A <strong>specular intensity</strong> float.</li>
     51   <li>All light source position and color vectors.</li>
     52   <li>The player or viewer's position vector.</li>
     53 </ul>
     54     
     55 <p>
     56   With these (per-fragment) variables at our disposal we are able to calculate the (Blinn-)Phong lighting we're accustomed to. The light source positions and colors, and the player's view position, can be configured using uniform variables, but the other variables are all fragment specific. If we can somehow pass the exact same data to the final deferred lighting pass we can calculate the same lighting effects, even though we're rendering fragments of a 2D quad. 
     57 </p>
     58     
     59 <p>
     60   There is no limit in OpenGL to what we can store in a texture so it makes sense to store all per-fragment data in one or multiple screen-filled textures of the G-buffer and use these later in the lighting pass. As the G-buffer textures will have the same size as the lighting pass's 2D quad, we get the exact same fragment data we'd had in a forward rendering setting, but this time in the lighting pass; there is a one on one mapping.
     61 </p>
     62 
     63 <p>
     64   In pseudocode the entire process will look a bit like this:
     65 </p>
     66       
     67 <pre><code>
     68 while(...) // render loop
     69 {
     70     // 1. geometry pass: render all geometric/color data to g-buffer 
     71     <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, gBuffer);
     72     <function id='13'><function id='10'>glClear</function>Color</function>(0.0, 0.0, 0.0, 1.0); // keep it black so it doesn't leak into g-buffer
     73     <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     74     gBufferShader.use();
     75     for(Object obj : Objects)
     76     {
     77         ConfigureShaderTransformsAndUniforms();
     78         obj.Draw();
     79     }  
     80     // 2. lighting pass: use g-buffer to calculate the scene's lighting
     81     <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0);
     82     lightingPassShader.use();
     83     BindAllGBufferTextures();
     84     SetLightingUniforms();
     85     RenderQuad();
     86 }
     87 </code></pre>
     88     
     89 <p>
     90   The data we'll need to store of each fragment is a <strong>position</strong> vector, a <strong>normal</strong> vector, a <strong>color</strong> vector, and a <strong>specular intensity</strong> value. In the geometry pass we need to render all objects of the scene and store these data components in the G-buffer. We can again use <def>multiple render targets</def> to render to multiple color buffers in a single render pass; this was briefly discussed in the <a href="https://learnopengl.com/Advanced-Lighting/Bloom" target="_blank">Bloom</a> chapter.
     91 </p>
     92       
     93 <p>
     94   For the geometry pass we'll need to initialize a framebuffer object that we'll call <var>gBuffer</var> that has multiple color buffers attached and a single depth renderbuffer object. For the position and normal texture we'd preferably use a high-precision texture (16 or 32-bit float per component). For the albedo and specular values we'll be fine with the default texture precision (8-bit precision per component). Note that we use <var>GL_RGBA16F</var> over <var>GL_RGB16F</var> as GPUs generally prefer 4-component formats over 3-component formats due to byte alignment; some drivers may fail to complete the framebuffer otherwise.
     95 </p>
     96       
     97 <pre><code>
     98 unsigned int gBuffer;
     99 <function id='76'>glGenFramebuffers</function>(1, &gBuffer);
    100 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, gBuffer);
    101 unsigned int gPosition, gNormal, gColorSpec;
    102   
    103 // - position color buffer
    104 <function id='50'>glGenTextures</function>(1, &gPosition);
    105 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, gPosition);
    106 <function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
    107 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    108 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    109 <function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPosition, 0);
    110   
    111 // - normal color buffer
    112 <function id='50'>glGenTextures</function>(1, &gNormal);
    113 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, gNormal);
    114 <function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
    115 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    116 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    117 <function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gNormal, 0);
    118   
    119 // - color + specular color buffer
    120 <function id='50'>glGenTextures</function>(1, &gAlbedoSpec);
    121 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, gAlbedoSpec);
    122 <function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGBA, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    123 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    124 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    125 <function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gAlbedoSpec, 0);
    126   
    127 // - tell OpenGL which color attachments we'll use (of this framebuffer) for rendering 
    128 unsigned int attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
    129 glDrawBuffers(3, attachments);
    130   
    131 // then also add render buffer object as depth buffer and check for completeness.
    132 [...]
    133 </code></pre>
    134       
    135 <p>
    136   Since we use multiple render targets, we have to explicitly tell OpenGL which of the color buffers associated with <var>GBuffer</var> we'd like to render to with <fun>glDrawBuffers</fun>. Also interesting to note here is we combine the color and specular intensity data in a single <code>RGBA</code> texture; this saves us from having to declare an additional color buffer texture. As your deferred shading pipeline gets more complex and needs more data you'll quickly find new ways to combine data in individual textures. 
    137 </p>
    138       
    139 <p>
    140  Next we need to render into the G-buffer. Assuming each object has a diffuse, normal, and specular texture we'd use something like the following fragment shader to render into the G-buffer:
    141 </p>
    142     
    143 <pre><code>
    144 #version 330 core
    145 layout (location = 0) out vec3 gPosition;
    146 layout (location = 1) out vec3 gNormal;
    147 layout (location = 2) out vec4 gAlbedoSpec;
    148 
    149 in vec2 TexCoords;
    150 in vec3 FragPos;
    151 in vec3 Normal;
    152 
    153 uniform sampler2D texture_diffuse1;
    154 uniform sampler2D texture_specular1;
    155 
    156 void main()
    157 {    
    158     // store the fragment position vector in the first gbuffer texture
    159     gPosition = FragPos;
    160     // also store the per-fragment normals into the gbuffer
    161     gNormal = normalize(Normal);
    162     // and the diffuse per-fragment color
    163     gAlbedoSpec.rgb = texture(texture_diffuse1, TexCoords).rgb;
    164     // store specular intensity in gAlbedoSpec's alpha component
    165     gAlbedoSpec.a = texture(texture_specular1, TexCoords).r;
    166 }  
    167 </code></pre>
    168       
    169 <p>
    170    As we use multiple render targets, the layout specifier tells OpenGL to which color buffer of the active framebuffer we render to. Note that we do not store the specular intensity into a single color buffer texture as we can store its single float value in the alpha component of one of the other color buffer textures. 
    171 </p>
    172       
    173           
    174 <warning>
    175   Keep in mind that with lighting calculations it is extremely important to keep all relevant variables in the same coordinate space. In this case we store (and calculate) all variables in world-space.
    176 </warning>
    177       
    178 <p>
    179   If we'd now were to render a large collection of backpack objects into the <var>gBuffer</var> framebuffer and visualize its content by projecting each color buffer one by one onto a screen-filled quad we'd see something like this:
    180 </p>
    181       
    182       <img src="/img/advanced-lighting/deferred_g_buffer.png" alt="Image of a G-Buffer in OpenGL with several backpacks"/>
    183       
    184 <p>
    185   Try to visualize that the world-space position and normal vectors are indeed correct. For instance, the normal vectors pointing to the right would be more aligned to a red color, similarly for position vectors that point from the scene's origin to the right. As soon as you're satisfied with the content of the G-buffer it's time to move to the next step: the lighting pass.
    186 </p>
    187       
    188 <h2>The deferred lighting pass</h2>
    189 <p>
    190   With a large collection of fragment data in the G-Buffer at our disposal we have the option to completely calculate the scene's final lit colors. We do this by iterating over each of the G-Buffer textures pixel by pixel and use their content as input to the lighting algorithms. Because the G-buffer texture values all represent the final transformed fragment values we only have to do the expensive lighting operations once per pixel. This is especially useful in complex scenes where we'd easily invoke multiple expensive fragment shader calls per pixel in a forward rendering setting.
    191 </p>
    192         
    193 <p>
    194   For the lighting pass we're going to render a 2D screen-filled quad (a bit like a post-processing effect) and execute an expensive lighting fragment shader on each pixel:
    195 </p>
    196         
    197 <pre><code>
    198 <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    199 <function id='49'>glActiveTexture</function>(GL_TEXTURE0);
    200 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, gPosition);
    201 <function id='49'>glActiveTexture</function>(GL_TEXTURE1);
    202 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, gNormal);
    203 <function id='49'>glActiveTexture</function>(GL_TEXTURE2);
    204 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, gAlbedoSpec);
    205 // also send light relevant uniforms
    206 shaderLightingPass.use();
    207 SendAllLightUniformsToShader(shaderLightingPass);
    208 shaderLightingPass.setVec3("viewPos", camera.Position);
    209 RenderQuad();  
    210 </code></pre>
    211         
    212 <p>
    213   We bind all relevant textures of the G-buffer before rendering and also send the lighting-relevant uniform variables to the shader.
    214 </p>
    215         
    216 <p>
    217   The fragment shader of the lighting pass is largely similar to the lighting chapter shaders we've used so far. What is new is the method in which we obtain the lighting's input variables, which we now directly sample from the G-buffer:
    218 </p>
    219         
    220 <pre><code>
    221 #version 330 core
    222 out vec4 FragColor;
    223   
    224 in vec2 TexCoords;
    225 
    226 uniform sampler2D gPosition;
    227 uniform sampler2D gNormal;
    228 uniform sampler2D gAlbedoSpec;
    229 
    230 struct Light {
    231     vec3 Position;
    232     vec3 Color;
    233 };
    234 const int NR_LIGHTS = 32;
    235 uniform Light lights[NR_LIGHTS];
    236 uniform vec3 viewPos;
    237 
    238 void main()
    239 {             
    240     // retrieve data from G-buffer
    241     vec3 FragPos = texture(gPosition, TexCoords).rgb;
    242     vec3 Normal = texture(gNormal, TexCoords).rgb;
    243     vec3 Albedo = texture(gAlbedoSpec, TexCoords).rgb;
    244     float Specular = texture(gAlbedoSpec, TexCoords).a;
    245     
    246     // then calculate lighting as usual
    247     vec3 lighting = Albedo * 0.1; // hard-coded ambient component
    248     vec3 viewDir = normalize(viewPos - FragPos);
    249     for(int i = 0; i &lt; NR_LIGHTS; ++i)
    250     {
    251         // diffuse
    252         vec3 lightDir = normalize(lights[i].Position - FragPos);
    253         vec3 diffuse = max(dot(Normal, lightDir), 0.0) * Albedo * lights[i].Color;
    254         lighting += diffuse;
    255     }
    256     
    257     FragColor = vec4(lighting, 1.0);
    258 }  
    259 </code></pre>
    260         
    261 <p>
    262   The lighting pass shader accepts 3 uniform textures that represent the G-buffer and hold all the data we've stored in the geometry pass. If we were to sample these with the current fragment's texture coordinates we'd get the exact same fragment values as if we were rendering the geometry directly. Note that we retrieve both the <var>Albedo</var> color and the <var>Specular</var> intensity from the single <var>gAlbedoSpec</var> texture.
    263 </p>
    264         
    265 <p>
    266   As we now have the per-fragment variables (and the relevant uniform variables) necessary to calculate Blinn-Phong lighting, we don't have to make any changes to the lighting code. The only thing we change in deferred shading here is the method of obtaining lighting input variables.
    267 </p>
    268         
    269 <p>
    270   Running a simple demo with a total of <code>32</code> small lights looks a bit like this:
    271 </p>
    272         
    273         <img src="/img/advanced-lighting/deferred_shading.png" class="clean" alt="Example of Deferred Shading in OpenGL"/>
    274                     
    275 <p>
    276   One of the disadvantages of deferred shading is that it is not possible to do <a href="https://learnopengl.com/Advanced-OpenGL/Blending" target="_blank">blending</a> as all values in the G-buffer are from single fragments, and blending operates on the combination of multiple fragments. Another disadvantage is that deferred shading forces you to use the same lighting algorithm for most of your scene's lighting; you can somehow alleviate this a bit by including more material-specific data in the G-buffer.
    277 </p>
    278           
    279 <p>
    280   To overcome these disadvantages (especially blending) we often split the renderer into two parts: one deferred rendering part, and the other a forward rendering part specifically meant for blending or special shader effects not suited for a deferred rendering pipeline. To illustrate how this works, we'll render the light sources as small cubes using a forward renderer as the light cubes require a special shader (simply output a single light color).
    281 </p>
    282       
    283 <h2>Combining deferred rendering with forward rendering</h2>
    284 <p>
    285   Say we want to render each of the light sources as a 3D cube positioned at the light source's position emitting the color of the light. A first idea that comes to mind is to simply forward render all the light sources on top of the deferred lighting quad at the end of the deferred shading pipeline. So basically render the cubes as we'd normally do, but only after we've finished the deferred rendering operations. In code this will look a bit like this:
    286 </p>
    287   
    288 <pre><code>
    289 // deferred lighting pass
    290 [...]
    291 RenderQuad();
    292   
    293 // now render all light cubes with forward rendering as we'd normally do
    294 shaderLightBox.use();
    295 shaderLightBox.setMat4("projection", projection);
    296 shaderLightBox.setMat4("view", view);
    297 for (unsigned int i = 0; i &lt; lightPositions.size(); i++)
    298 {
    299     model = glm::mat4(1.0f);
    300     model = <function id='55'>glm::translate</function>(model, lightPositions[i]);
    301     model = <function id='56'>glm::scale</function>(model, glm::vec3(0.25f));
    302     shaderLightBox.setMat4("model", model);
    303     shaderLightBox.setVec3("lightColor", lightColors[i]);
    304     RenderCube();
    305 }
    306 </code></pre>
    307   
    308 <p>
    309    However, these rendered cubes do not take any of the stored geometry depth of the deferred renderer into account and are, as a result, always rendered on top of the previously rendered objects; this isn't the result we were looking for.
    310 </p>
    311   
    312   <img src="/img/advanced-lighting/deferred_lights_no_depth.png" class="clean" alt="Image of deferred rendering with forward rendering where we didn't copy depth buffer data and lights are rendered on top of all geometry in OpenGL"/>
    313     
    314 <p>
    315   What we need to do, is first copy the depth information stored in the geometry pass into the default framebuffer's depth buffer and only then render the light cubes. This way the light cubes' fragments are only rendered when on top of the previously rendered geometry.
    316 </p>
    317     
    318 <p>
    319   We can copy the content of a framebuffer to the content of another framebuffer with the help of <fun><function id='103'>glBlitFramebuffer</function></fun>, a function we also used in the <a href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing" target="_blank">anti-aliasing</a> chapter to resolve multisampled framebuffers. The <fun><function id='103'>glBlitFramebuffer</function></fun> function allows us to copy a user-defined region of a framebuffer to a user-defined region of another framebuffer. 
    320 </p>
    321     
    322 <p>
    323   We stored the depth of all the objects rendered in the deferred geometry pass in the <var>gBuffer</var> FBO. If we were to copy the content of its depth buffer to the depth buffer of the default framebuffer, the light cubes would then render as if all of the scene's geometry was rendered with forward rendering. As briefly explained in the anti-aliasing chapter, we have to specify a framebuffer as the read framebuffer and similarly specify a framebuffer as the write framebuffer:
    324 </p>
    325     
    326 <pre><code>
    327 <function id='77'>glBindFramebuffer</function>(GL_READ_FRAMEBUFFER, gBuffer);
    328 <function id='77'>glBindFramebuffer</function>(GL_DRAW_FRAMEBUFFER, 0); // write to default framebuffer
    329 <function id='103'>glBlitFramebuffer</function>(
    330   0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_DEPTH_BUFFER_BIT, GL_NEAREST
    331 );
    332 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0);
    333 // now render light cubes as before
    334 [...]  
    335 </code></pre>
    336     
    337 <p>
    338   Here we copy the entire read framebuffer's depth buffer content to the default framebuffer's depth buffer; this can similarly be done for color buffers and stencil buffers. If we then render the light cubes, the cubes indeed render correctly over the scene's geometry:
    339 </p>
    340     
    341   
    342   <img src="/img/advanced-lighting/deferred_lights_depth.png" class="clean" alt="Image of deferred rendering with forward rendering where we copied the depth buffer data and lights are rendered properly with all  geometry in OpenGL"/>
    343     
    344 <p>
    345   You can find the full source code of the demo <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/8.1.deferred_shading/deferred_shading.cpp" target="_blank">here</a>.
    346 </p>
    347     
    348 <p>
    349   With this approach we can easily combine deferred shading with forward shading. This is great as we can now still apply blending and render objects that require special shader effects, something that isn't possible in a pure deferred rendering context.
    350 </p>
    351     
    352 <h2>A larger number of lights</h2>
    353 <p>
    354   What deferred rendering is often praised for, is its ability to render an enormous amount of light sources without a heavy cost on performance. Deferred rendering by itself doesn't allow for a very large amount of light sources as we'd still have to calculate each fragment's lighting component for each of the scene's light sources. What makes a large amount of light sources possible is a very neat optimization we can apply to the deferred rendering pipeline: that of <def>light volumes</def>.
    355 </p>
    356     
    357 <p>
    358   Normally when we render a fragment in a large lit scene we'd calculate the contribution of <strong>each</strong> light source in a scene, regardless of their distance to the fragment. A large portion of these light sources will never reach the fragment, so why waste all these lighting computations? 
    359 </p>
    360         
    361 <p>
    362   The idea behind light volumes is to calculate the radius, or volume, of a light source i.e. the area where its light is able to reach fragments. As most light sources use some form of attenuation, we can use that to calculate the maximum distance or radius their light is able to reach. We then only do the expensive lighting calculations if a fragment is inside one or more of these light volumes. This can save us a considerable amount of computation as we now only calculate lighting where it's necessary.
    363 </p>
    364     
    365 <p>
    366   The trick to this approach is mostly figuring out the size or radius of the light volume of a light source.
    367 </p>
    368     
    369 <h3>Calculating a light's volume or radius</h3>
    370 <p>
    371   To obtain a light's volume radius we have to solve the attenuation equation for when its light contribution becomes <code>0.0</code>. For the attenuation function we'll use the function introduced in the <a href="https://learnopengl.com/Lighting/Light-casters" target="_blank">light casters</a> chapter:
    372 </p>
    373     
    374     \[F_{light} = \frac{I}{K_c + K_l * d + K_q * d^2}\]
    375     
    376 <p>
    377   What we want to do is solve this equation for when \(F_{light}\) is <code>0.0</code>. However, this equation will never exactly reach the value <code>0.0</code>, so there won't be a solution. What we can do however, is not solve the equation for <code>0.0</code>, but solve it for a brightness value that is close to <code>0.0</code> but still perceived as dark. The brightness value of \(5/256\) would be acceptable for this chapter's demo scene; divided by 256 as the default 8-bit framebuffer can only display that many intensities per component.
    378 </p>
    379     
    380 <note>
    381   The attenuation function used is mostly dark in its visible range. If we were to limit it to an even darker brightness than \(5/256\), the light volume would become too large and thus less effective. As long as a user cannot see a sudden cut-off of a light source at its volume borders we'll be fine. Of course this always depends on the type of scene; a higher brightness threshold results in smaller light volumes and thus a better efficiency, but can produce noticeable artifacts where lighting seems to break at a volume's borders.
    382 </note>
    383     
    384 <p>
    385   The attenuation equation we have to solve becomes:
    386 </p>
    387     
    388     \[\frac{5}{256} = \frac{I_{max}}{Attenuation}\]
    389     
    390 <p>
    391   Here \(I_{max}\) is the light source's brightest color component. We use a light source's brightest color component as solving the equation for a light's brightest intensity value best reflects the ideal light volume radius.
    392 </p>
    393     
    394 <p>
    395   From here on we continue solving the equation:
    396 </p>
    397     
    398     \[\frac{5}{256} * Attenuation = I_{max} \]
    399     
    400     \[5 * Attenuation = I_{max} * 256 \]
    401     
    402     \[Attenuation = I_{max} * \frac{256}{5} \]
    403     
    404      \[K_c + K_l * d + K_q * d^2 = I_{max} * \frac{256}{5} \]
    405     
    406     \[K_q * d^2 + K_l * d + K_c - I_{max} * \frac{256}{5} = 0 \]
    407     
    408 <p>
    409   The last equation is an equation of the form \(ax^2 + bx + c = 0\), which we can solve using the quadratic equation:
    410 </p>
    411     
    412     \[x = \frac{-K_l + \sqrt{K_l^2 - 4 * K_q * (K_c - I_{max} * \frac{256}{5})}}{2 * K_q} \]
    413     
    414 <p>
    415   This gives us a general equation that allows us to calculate \(x\) i.e. the light volume's radius for the light source given a constant, linear, and quadratic parameter:
    416 </p>
    417     
    418 <pre><code>
    419 float constant  = 1.0; 
    420 float linear    = 0.7;
    421 float quadratic = 1.8;
    422 float lightMax  = std::fmaxf(std::fmaxf(lightColor.r, lightColor.g), lightColor.b);
    423 float radius    = 
    424   (-linear +  std::sqrtf(linear * linear - 4 * quadratic * (constant - (256.0 / 5.0) * lightMax))) 
    425   / (2 * quadratic);  
    426 </code></pre>
    427 
    428 <p>
    429   We calculate this radius for each light source of the scene and use it to only calculate lighting for that light source if a fragment is inside the light source's volume. Below is the updated lighting pass fragment shader that takes the calculated light volumes into account. Note that this approach is merely done for teaching purposes and not viable in a practical setting as we'll soon discuss:
    430 </p>
    431     
    432 <pre><code>
    433 struct Light {
    434     [...]
    435     float Radius;
    436 }; 
    437   
    438 void main()
    439 {
    440     [...]
    441     for(int i = 0; i &lt; NR_LIGHTS; ++i)
    442     {
    443         // calculate distance between light source and current fragment
    444         float distance = length(lights[i].Position - FragPos);
    445         if(distance &lt; lights[i].Radius)
    446         {
    447             // do expensive lighting
    448             [...]
    449         }
    450     }   
    451 }
    452 </code></pre>
    453 
    454 <p>
    455   The results are exactly the same as before, but this time each light only calculates lighting for the light sources in which volume it resides.
    456 </p>
    457     
    458 <p>
    459   You can find the final source code of the demo <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/8.2.deferred_shading_volumes/deferred_shading_volumes.cpp" target="_blank">here</a>.
    460 </p>
    461       
    462 <h3>How we really use light volumes</h3>
    463 <p>
    464   The fragment shader shown above doesn't really work in practice and only illustrates how we can <em>sort of</em> use a light's volume to reduce lighting calculations. The reality is that your GPU and GLSL are pretty bad at optimizing loops and branches. The reason for this is that shader execution on the GPU is highly parallel and most architectures have a requirement that for large collection of threads they need to run the exact same shader code for it to be efficient. This often means that a shader is run that executes <strong>all</strong> branches of an <code>if</code> statement to ensure the shader runs are the same for that group of threads, making our previous <em>radius check</em> optimization completely useless; we'd still calculate lighting for all light sources!
    465 </p>
    466       
    467 <p>
    468   The appropriate approach to using light volumes is to render actual spheres, scaled by the light volume radius. The centers of these spheres are positioned at the light source's position, and as it is scaled by the light volume radius the sphere exactly encompasses the light's visible volume. This is where the trick comes in: we use the deferred lighting shader for rendering the spheres.  As a rendered sphere produces fragment shader invocations that exactly match the pixels the light source affects, we only render the relevant pixels and skip all other pixels. The image below illustrates this:
    469 </p>
    470       
    471       <img src="/img/advanced-lighting/deferred_light_volume_rendered.png" class="clean" alt="Image of a light volume rendered with a deferred fragment shader in OpenGL"/>
    472         
    473 <p>
    474   This is done for each light source in the scene, and the resulting fragments are additively blended together. The result is then the exact same scene as before, but this time rendering only the relevant fragments per light source. This effectively reduces the computations from <code>nr_objects * nr_lights</code> to <code>nr_objects + nr_lights</code>, which makes it incredibly efficient in scenes with a large number of lights. This approach is what makes deferred rendering so suitable for rendering a large number of lights.
    475 </p>
    476         
    477 <p>
    478   There is still an issue with this approach: face culling should be enabled (otherwise we'd render a light's effect twice) and when it is enabled the user may enter a light source's volume after which the volume isn't rendered anymore (due to back-face culling), removing the light source's influence; we can solve that by only rendering the spheres' back faces. 
    479 </p>
    480         
    481  <p>
    482     Rendering light volumes does take its toll on performance, and while it is generally much faster than normal deferred shading for rendering a large number of lights, there's still more we can optimize. Two other popular (and more efficient) extensions on top of deferred shading exist called <def>deferred lighting</def> and <def>tile-based deferred shading</def>. These are even more efficient at rendering large amounts of light and also allow for relatively efficient MSAA.  
    483 </p>
    484         
    485 <h2>Deferred rendering vs forward rendering</h2>
    486 <p>
    487   By itself (without light volumes), deferred shading is a nice optimization as each pixel only runs a single fragment shader, compared to forward rendering where we'd often run the fragment shader multiple times per pixel. Deferred rendering does come with a few disadvantages though: a large memory overhead, no MSAA, and blending still has to be done with forward rendering.
    488 </p>
    489         
    490 <p>
    491   When you have a small scene and not too many lights, deferred rendering is not necessarily faster and sometimes even slower as the overhead then outweighs the benefits of deferred rendering. In more complex scenes, deferred rendering quickly becomes a significant optimization; especially with the more advanced optimization extensions. In addition, some render effects (especially post-processing effects) become cheaper on a deferred render pipeline as a lot of scene inputs are already available from the g-buffer.
    492 </p>       
    493         
    494 <p>
    495   As a final note I'd like to mention that basically all effects that can be accomplished with forward rendering can also be implemented in a deferred rendering context; this often only requires a small translation step. For instance, if we want to use normal mapping in a deferred renderer, we'd change the geometry pass shaders to output a world-space normal extracted from a normal map (using a TBN matrix) instead of the surface normal; the lighting calculations in the lighting pass don't need to change at all. And if you want parallax mapping to work, you'd want to first displace the texture coordinates in the geometry pass before sampling an object's diffuse, specular, and normal textures. Once you understand the idea behind deferred rendering, it's not too difficult to get creative.
    496 </p>
    497         
    498 <h2>Additional resources</h2>
    499 <ul>
    500     <li><a href="http://ogldev.atspace.co.uk/www/tutorial35/tutorial35.html" target="_blank">Tutorial 35: Deferred Shading - Part 1</a>: a three-part deferred shading tutorial by OGLDev.</li> 
    501     <li><a href="https://software.intel.com/sites/default/files/m/d/4/1/d/8/lauritzen_deferred_shading_siggraph_2010.pdf" target="_blank">Deferred Rendering for Current and
    502 Future Rendering Pipelines</a>: slides by Andrew Lauritzen discussing high-level tile-based deferred shading and deferred lighting.</li>
    503  </ul>
    504        
    505 
    506     </div>
    507     
    508     <div id="hover">
    509         HI
    510     </div>
    511    <!-- 728x90/320x50 sticky footer -->
    512 <div id="waldo-tag-6196"></div>
    513 
    514    <div id="disqus_thread"></div>
    515 
    516     
    517 
    518 
    519 </div> <!-- container div -->
    520 
    521 
    522 </div> <!-- super container div -->
    523 </body>
    524 </html>