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 < 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 < 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 < NR_LIGHTS; ++i) 442 { 443 // calculate distance between light source and current fragment 444 float distance = length(lights[i].Position - FragPos); 445 if(distance < 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>