Point-Shadows.html (28801B)
1 <h1 id="content-title">Point Shadows</h1> 2 <h1 id="content-url" style='display:none;'>Advanced-Lighting/Shadows/Point-Shadows</h1> 3 <p> 4 In the last chapter we learned to create dynamic shadows with shadow mapping. It works great, but it's mostly suited for directional (or spot) lights as the shadows are generated only in the direction of the light source. It is therefore also known as <def>directional shadow mapping</def> as the depth (or shadow) map is generated from only the direction the light is looking at. 5 </p> 6 7 <p> 8 What this chapter will focus on is the generation of dynamic shadows in all surrounding directions. The technique we're using is perfect for point lights as a real point light would cast shadows in all directions. This technique is known as point (light) shadows or more formerly as <def>omnidirectional shadow maps</def>. 9 </p> 10 11 <note> 12 This chapter builds upon the previous <a href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping" target="_blank">shadow mapping</a> chapter so unless you're familiar with traditional shadow mapping it is advised to read the shadow mapping chapter first. 13 </note> 14 15 <p> 16 The technique is mostly similar to directional shadow mapping: we generate a depth map from the light's perspective(s), sample the depth map based on the current fragment position, and compare each fragment with the stored depth value to see whether it is in shadow. The main difference between directional shadow mapping and omnidirectional shadow mapping is the depth map we use. 17 </p> 18 19 <p> 20 The depth map we need requires rendering a scene from all surrounding directions of a point light and as such a normal 2D depth map won't work; what if we were to use a <a href="https://learnopengl.com/Advanced-OpenGL/Cubemaps" target="_blank">cubemap</a> instead? Because a cubemap can store full environment data with only 6 faces, it is possible to render the entire scene to each of the faces of a cubemap and sample these as the point light's surrounding depth values. 21 </p> 22 23 <img src="/img/advanced-lighting/point_shadows_diagram.png" class="clean" alt="Image of how omnidrectional shadow mapping or point shadows work"/> 24 25 <p> 26 The generated depth cubemap is then passed to the lighting fragment shader that samples the cubemap with a direction vector to obtain the closest depth (from the light's perspective) at that fragment. Most of the complicated stuff we've already discussed in the shadow mapping chapter. What makes this technique a bit more difficult is the depth cubemap generation. 27 </p> 28 29 <h2>Generating the depth cubemap</h2> 30 <p> 31 To create a cubemap of a light's surrounding depth values we have to render the scene 6 times: once for each face. One (quite obvious) way to do this, is render the scene 6 times with 6 different view matrices, each time attaching a different cubemap face to the framebuffer object. This would look something like this: 32 </p> 33 34 <pre><code> 35 for(unsigned int i = 0; i < 6; i++) 36 { 37 GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X + i; 38 <function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, face, depthCubemap, 0); 39 BindViewMatrix(lightViewMatrices[i]); 40 RenderScene(); 41 } 42 </code></pre> 43 44 <p> 45 This can be quite expensive though as a lot of render calls are necessary for this single depth map. In this chapter we're going to use an alternative (more organized) approach using a little trick in the geometry shader that allows us to build the depth cubemap with just a single render pass. 46 </p> 47 48 <p> 49 First, we'll need to create a cubemap: 50 </p> 51 52 <pre><code> 53 unsigned int depthCubemap; 54 <function id='50'>glGenTextures</function>(1, &depthCubemap); 55 </code></pre> 56 57 <p> 58 And assign each of the single cubemap faces a 2D depth-valued texture image: 59 </p> 60 61 <pre><code> 62 const unsigned int SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024; 63 <function id='48'>glBindTexture</function>(GL_TEXTURE_CUBE_MAP, depthCubemap); 64 for (unsigned int i = 0; i < 6; ++i) 65 <function id='52'>glTexImage2D</function>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_DEPTH_COMPONENT, 66 SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); 67 </code></pre> 68 69 <p> 70 And don't forget to set the texture parameters: 71 </p> 72 73 <pre><code> 74 <function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 75 <function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 76 <function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 77 <function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 78 <function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); 79 </code></pre> 80 81 <p> 82 Normally we'd attach a single face of a cubemap texture to the framebuffer object and render the scene 6 times, each time switching the depth buffer target of the framebuffer to a different cubemap face. Since we're going to use a geometry shader, that allows us to render to all faces in a single pass, we can directly attach the cubemap as a framebuffer's depth attachment with <fun>glFramebufferTexture</fun>: 83 </p> 84 85 <pre class="cpp"><code> 86 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, depthMapFBO); 87 glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthCubemap, 0); 88 glDrawBuffer(GL_NONE); 89 glReadBuffer(GL_NONE); 90 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); 91 </code></pre> 92 93 <p> 94 Again, note the call to <fun>glDrawBuffer</fun> and <fun>glReadBuffer</fun>: we only care about depth values when generating a depth cubemap so we have to explicitly tell OpenGL this framebuffer object does not render to a color buffer. 95 </p> 96 97 <p> 98 With omnidirectional shadow maps we have two render passes: first, we generate the depth cubemap and second, we use the depth cubemap in the normal render pass to add shadows to the scene. This process looks a bit like this: 99 </p> 100 101 <pre><code> 102 // 1. first render to depth cubemap 103 <function id='22'>glViewport</function>(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT); 104 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, depthMapFBO); 105 <function id='10'>glClear</function>(GL_DEPTH_BUFFER_BIT); 106 ConfigureShaderAndMatrices(); 107 RenderScene(); 108 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); 109 // 2. then render scene as normal with shadow mapping (using depth cubemap) 110 <function id='22'>glViewport</function>(0, 0, SCR_WIDTH, SCR_HEIGHT); 111 <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 112 ConfigureShaderAndMatrices(); 113 <function id='48'>glBindTexture</function>(GL_TEXTURE_CUBE_MAP, depthCubemap); 114 RenderScene(); 115 </code></pre> 116 117 <p> 118 The process is exactly the same as with default shadow mapping, although this time we render to and use a cubemap depth texture compared to a 2D depth texture. 119 </p> 120 121 <h3>Light space transform</h3> 122 <p> 123 With the framebuffer and cubemap set, we need some way to transform all the scene's geometry to the relevant light spaces in all 6 directions of the light. Just like the <a href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping" target="_blank">shadow mapping</a> chapter we're going to need a light space transformation matrix \(T\), but this time one for each face. 124 </p> 125 126 <p> 127 Each light space transformation matrix contains both a projection and a view matrix. For the projection matrix we're going to use a perspective projection matrix; the light source represents a point in space so perspective projection makes most sense. Each light space transformation matrix uses the same projection matrix: 128 </p> 129 130 <pre><code> 131 float aspect = (float)SHADOW_WIDTH/(float)SHADOW_HEIGHT; 132 float near = 1.0f; 133 float far = 25.0f; 134 glm::mat4 shadowProj = <function id='58'>glm::perspective</function>(<function id='63'>glm::radians</function>(90.0f), aspect, near, far); 135 </code></pre> 136 137 <p> 138 Important to note here is the field of view parameter of <fun><function id='58'>glm::perspective</function></fun> that we set to 90 degrees. By setting this to 90 degrees we make sure the viewing field is exactly large enough to fill a single face of the cubemap such that all faces align correctly to each other at the edges. 139 </p> 140 141 <p> 142 As the projection matrix does not change per direction we can re-use it for each of the 6 transformation matrices. We do need a different view matrix per direction. With <fun><function id='62'>glm::lookAt</function></fun> we create 6 view directions, each looking at one face direction of the cubemap in the order: right, left, top, bottom, near and far. 143 </p> 144 145 <pre><code> 146 std::vector<glm::mat4> shadowTransforms; 147 shadowTransforms.push_back(shadowProj * 148 <function id='62'>glm::lookAt</function>(lightPos, lightPos + glm::vec3( 1.0, 0.0, 0.0), glm::vec3(0.0,-1.0, 0.0)); 149 shadowTransforms.push_back(shadowProj * 150 <function id='62'>glm::lookAt</function>(lightPos, lightPos + glm::vec3(-1.0, 0.0, 0.0), glm::vec3(0.0,-1.0, 0.0)); 151 shadowTransforms.push_back(shadowProj * 152 <function id='62'>glm::lookAt</function>(lightPos, lightPos + glm::vec3( 0.0, 1.0, 0.0), glm::vec3(0.0, 0.0, 1.0)); 153 shadowTransforms.push_back(shadowProj * 154 <function id='62'>glm::lookAt</function>(lightPos, lightPos + glm::vec3( 0.0,-1.0, 0.0), glm::vec3(0.0, 0.0,-1.0)); 155 shadowTransforms.push_back(shadowProj * 156 <function id='62'>glm::lookAt</function>(lightPos, lightPos + glm::vec3( 0.0, 0.0, 1.0), glm::vec3(0.0,-1.0, 0.0)); 157 shadowTransforms.push_back(shadowProj * 158 <function id='62'>glm::lookAt</function>(lightPos, lightPos + glm::vec3( 0.0, 0.0,-1.0), glm::vec3(0.0,-1.0, 0.0)); 159 </code></pre> 160 161 <p> 162 Here we create 6 view matrices and multiply them with the projection matrix to get a total of 6 different light space transformation matrices. The <code>target</code> parameter of <fun><function id='62'>glm::lookAt</function></fun> each looks into the direction of a single cubemap face. 163 </p> 164 165 <p> 166 These transformation matrices are sent to the shaders that render the depth into the cubemap. 167 </p> 168 169 <h3>Depth shaders</h3> 170 <p> 171 To render depth values to a depth cubemap we're going to need a total of three shaders: a vertex and fragment shader, and a <a href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader" target="_blank">geometry shader</a> in between. 172 </p> 173 174 <p> 175 The geometry shader will be the shader responsible for transforming all world-space vertices to the 6 different light spaces. Therefore, the vertex shader simply transforms vertices to world-space and directs them to the geometry shader: 176 </p> 177 178 <pre><code> 179 #version 330 core 180 layout (location = 0) in vec3 aPos; 181 182 uniform mat4 model; 183 184 void main() 185 { 186 gl_Position = model * vec4(aPos, 1.0); 187 } 188 </code></pre> 189 190 <p> 191 The geometry shader will take as input 3 triangle vertices and a uniform array of light space transformation matrices. The geometry shader is responsible for transforming the vertices to the light spaces; this is also where it gets interesting. 192 </p> 193 194 <p> 195 The geometry shader has a built-in variable called <var>gl_Layer</var> that specifies which cubemap face to emit a primitive to. When left alone, the geometry shader just sends its primitives further down the pipeline as usual, but when we update this variable we can control to which cubemap face we render to for each primitive. This of course only works when we have a cubemap texture attached to the active framebuffer. 196 </p> 197 198 <pre><code> 199 #version 330 core 200 layout (triangles) in; 201 layout (triangle_strip, max_vertices=18) out; 202 203 uniform mat4 shadowMatrices[6]; 204 205 out vec4 FragPos; // FragPos from GS (output per emitvertex) 206 207 void main() 208 { 209 for(int face = 0; face < 6; ++face) 210 { 211 gl_Layer = face; // built-in variable that specifies to which face we render. 212 for(int i = 0; i < 3; ++i) // for each triangle vertex 213 { 214 FragPos = gl_in[i].gl_Position; 215 gl_Position = shadowMatrices[face] * FragPos; 216 EmitVertex(); 217 } 218 EndPrimitive(); 219 } 220 } 221 </code></pre> 222 223 <p> 224 This geometry shader is relatively straightforward. We take as input a triangle, and output a total of 6 triangles (6 * 3 equals 18 vertices). In the <fun>main</fun> function we iterate over 6 cubemap faces where we specify each face as the output face by storing the face integer into <var>gl_Layer</var>. We then generate the output triangles by transforming each world-space input vertex to the relevant light space by multiplying <var>FragPos</var> with the face's light-space transformation matrix. Note that we also sent the resulting <var>FragPos</var> variable to the fragment shader that we'll need to calculate a depth value. 225 </p> 226 227 <p> 228 In the last chapter we used an empty fragment shader and let OpenGL figure out the depth values of the depth map. This time we're going to calculate our own (linear) depth as the linear distance between each closest fragment position and the light source's position. Calculating our own depth values makes the later shadow calculations a bit more intuitive. 229 </p> 230 231 <pre><code> 232 #version 330 core 233 in vec4 FragPos; 234 235 uniform vec3 lightPos; 236 uniform float far_plane; 237 238 void main() 239 { 240 // get distance between fragment and light source 241 float lightDistance = length(FragPos.xyz - lightPos); 242 243 // map to [0;1] range by dividing by far_plane 244 lightDistance = lightDistance / far_plane; 245 246 // write this as modified depth 247 gl_FragDepth = lightDistance; 248 } 249 </code></pre> 250 251 <p> 252 The fragment shader takes as input the <var>FragPos</var> from the geometry shader, the light's position vector, and the frustum's far plane value. Here we take the distance between the fragment and the light source, map it to the [<code>0</code>,<code>1</code>] range and write it as the fragment's depth value. 253 </p> 254 255 <p> 256 Rendering the scene with these shaders and the cubemap-attached framebuffer object active should give you a completely filled depth cubemap for the second pass's shadow calculations. 257 </p> 258 259 <h2>Omnidirectional shadow maps</h2> 260 <p> 261 With everything set up it is time to render the actual omnidirectional shadows. The procedure is similar to the directional shadow mapping chapter, although this time we bind a cubemap texture instead of a 2D texture and also pass the light projection's far plane variable to the shaders. 262 </p> 263 264 <pre><code> 265 <function id='22'>glViewport</function>(0, 0, SCR_WIDTH, SCR_HEIGHT); 266 <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 267 shader.use(); 268 // ... send uniforms to shader (including light's far_plane value) 269 <function id='49'>glActiveTexture</function>(GL_TEXTURE0); 270 <function id='48'>glBindTexture</function>(GL_TEXTURE_CUBE_MAP, depthCubemap); 271 // ... bind other textures 272 RenderScene(); 273 </code></pre> 274 275 <p> 276 Here the <fun>renderScene</fun> function renders a few cubes in a large cube room scattered around a light source at the center of the scene. 277 </p> 278 279 <p> 280 The vertex and fragment shader are mostly similar to the original shadow mapping shaders: the difference being that the fragment shader no longer requires a fragment position in light space as we can now sample the depth values with a direction vector. 281 </p> 282 283 <p> 284 Because of this, the vertex shader doesn't needs to transform its position vectors to light space so we can remove the <var>FragPosLightSpace</var> variable: 285 </p> 286 287 <pre><code> 288 #version 330 core 289 layout (location = 0) in vec3 aPos; 290 layout (location = 1) in vec3 aNormal; 291 layout (location = 2) in vec2 aTexCoords; 292 293 out vec2 TexCoords; 294 295 out VS_OUT { 296 vec3 FragPos; 297 vec3 Normal; 298 vec2 TexCoords; 299 } vs_out; 300 301 uniform mat4 projection; 302 uniform mat4 view; 303 uniform mat4 model; 304 305 void main() 306 { 307 vs_out.FragPos = vec3(model * vec4(aPos, 1.0)); 308 vs_out.Normal = transpose(inverse(mat3(model))) * aNormal; 309 vs_out.TexCoords = aTexCoords; 310 gl_Position = projection * view * model * vec4(aPos, 1.0); 311 } 312 </code></pre> 313 314 <p> 315 The fragment shader's Blinn-Phong lighting code is exactly the same as we had before with a shadow multiplication at the end: 316 </p> 317 318 <pre><code> 319 #version 330 core 320 out vec4 FragColor; 321 322 in VS_OUT { 323 vec3 FragPos; 324 vec3 Normal; 325 vec2 TexCoords; 326 } fs_in; 327 328 uniform sampler2D diffuseTexture; 329 uniform samplerCube depthMap; 330 331 uniform vec3 lightPos; 332 uniform vec3 viewPos; 333 334 uniform float far_plane; 335 336 float ShadowCalculation(vec3 fragPos) 337 { 338 [...] 339 } 340 341 void main() 342 { 343 vec3 color = texture(diffuseTexture, fs_in.TexCoords).rgb; 344 vec3 normal = normalize(fs_in.Normal); 345 vec3 lightColor = vec3(0.3); 346 // ambient 347 vec3 ambient = 0.3 * color; 348 // diffuse 349 vec3 lightDir = normalize(lightPos - fs_in.FragPos); 350 float diff = max(dot(lightDir, normal), 0.0); 351 vec3 diffuse = diff * lightColor; 352 // specular 353 vec3 viewDir = normalize(viewPos - fs_in.FragPos); 354 vec3 reflectDir = reflect(-lightDir, normal); 355 float spec = 0.0; 356 vec3 halfwayDir = normalize(lightDir + viewDir); 357 spec = pow(max(dot(normal, halfwayDir), 0.0), 64.0); 358 vec3 specular = spec * lightColor; 359 // calculate shadow 360 float shadow = ShadowCalculation(fs_in.FragPos); 361 vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color; 362 363 FragColor = vec4(lighting, 1.0); 364 } 365 </code></pre> 366 367 <p> 368 There are a few subtle differences: the lighting code is the same, but we now have a <code>samplerCube</code> uniform and the <fun>ShadowCalculation</fun> function takes the current fragment's position as its argument instead of the fragment position in light space. We now also include the light frustum's <var>far_plane</var> value that we'll later need. 369 </p> 370 371 <p> 372 The biggest difference is in the content of the <fun>ShadowCalculation</fun> function that now samples depth values from a cubemap instead of a 2D texture. Let's discuss its content step by step. 373 </p> 374 375 <p> 376 The first thing we have to do is retrieve the depth of the cubemap. You may remember from the cubemap section of this chapter that we stored the depth as the linear distance between the fragment and the light position; we're taking a similar approach here: 377 </p> 378 379 <pre><code> 380 float ShadowCalculation(vec3 fragPos) 381 { 382 vec3 fragToLight = fragPos - lightPos; 383 float closestDepth = texture(depthMap, fragToLight).r; 384 } 385 </code></pre> 386 387 <p> 388 Here we take the difference vector between the fragment's position and the light's position and use that vector as a direction vector to sample the cubemap. The direction vector doesn't need to be a unit vector to sample from a cubemap so there's no need to normalize it. The resulting <var>closestDepth</var> value is the normalized depth value between the light source and its closest visible fragment. 389 </p> 390 391 <p> 392 The <var>closestDepth</var> value is currently in the range [<code>0</code>,<code>1</code>] so we first transform it back to [<code>0</code>,<code>far_plane</code>] by multiplying it with <var>far_plane</var>. 393 </p> 394 395 <pre><code> 396 closestDepth *= far_plane; 397 </code></pre> 398 399 <p> 400 Next we retrieve the depth value between the current fragment and the light source, which we can easily obtain by taking the length of <var>fragToLight</var> due to how we calculated depth values in the cubemap: 401 </p> 402 403 <pre><code> 404 float currentDepth = length(fragToLight); 405 </code></pre> 406 407 <p> 408 This returns a depth value in the same (or larger) range as <var>closestDepth</var>. 409 </p> 410 411 <p> 412 Now we can compare both depth values to see which is closer than the other and determine whether the current fragment is in shadow. We also include a shadow bias so we don't get shadow acne as discussed in the <a href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping" target="_blank">previous</a> chapter. 413 </p> 414 415 <pre><code> 416 float bias = 0.05; 417 float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0; 418 </code></pre> 419 420 <p> 421 The complete <fun>ShadowCalculation</fun> then becomes: 422 </p> 423 424 <pre><code> 425 float ShadowCalculation(vec3 fragPos) 426 { 427 // get vector between fragment position and light position 428 vec3 fragToLight = fragPos - lightPos; 429 // use the light to fragment vector to sample from the depth map 430 float closestDepth = texture(depthMap, fragToLight).r; 431 // it is currently in linear range between [0,1]. Re-transform back to original value 432 closestDepth *= far_plane; 433 // now get current linear depth as the length between the fragment and light position 434 float currentDepth = length(fragToLight); 435 // now test for shadows 436 float bias = 0.05; 437 float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0; 438 439 return shadow; 440 } 441 </code></pre> 442 443 <p> 444 With these shaders we already get pretty good shadows and this time in all surrounding directions from a point light. With a point light positioned at the center of a simple scene it'll look a bit like this: 445 </p> 446 447 <img src="/img/advanced-lighting/point_shadows.png" class="clean" alt="Omnidirectional point shadow maps in OpenGL"/> 448 449 <p> 450 You can find the source code of this demo <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/3.2.1.point_shadows/point_shadows.cpp" target="_blank">here</a>. 451 </p> 452 453 <h3>Visualizing cubemap depth buffer</h3> 454 <p> 455 If you're somewhat like me you probably didn't get this right on the first try so it makes sense to do some debugging, with one of the obvious checks being validating whether the depth map was built correctly. A simple trick to visualize the depth buffer is to take the <var>closestDepth</var> variable in the <fun>ShadowCalculation</fun> function and display that variable as: 456 </p> 457 458 <pre><code> 459 FragColor = vec4(vec3(closestDepth / far_plane), 1.0); 460 </code></pre> 461 462 <p> 463 The result is a grayed out scene where each color represents the linear depth values of the scene: 464 </p> 465 466 <img src="/img/advanced-lighting/point_shadows_depth_cubemap.png" class="clean" alt="Visualized depth cube map of omnidrectional shadow maps"/> 467 468 <p> 469 You can also see the to-be shadowed regions on the outside wall. If it looks somewhat similar, you know the depth cubemap was properly generated. 470 </p> 471 472 <h2>PCF</h2> 473 <p> 474 Since omnidirectional shadow maps are based on the same principles of traditional shadow mapping it also has the same resolution dependent artifacts. If you zoom in close enough you can again see jagged edges. <def>Percentage-closer filtering</def> or PCF allows us to smooth out these jagged edges by filtering multiple samples around the fragment position and average the results. 475 </p> 476 477 <p> 478 If we take the same simple PCF filter of the previous chapter and add a third dimension we get: 479 </p> 480 481 <pre><code> 482 float shadow = 0.0; 483 float bias = 0.05; 484 float samples = 4.0; 485 float offset = 0.1; 486 for(float x = -offset; x < offset; x += offset / (samples * 0.5)) 487 { 488 for(float y = -offset; y < offset; y += offset / (samples * 0.5)) 489 { 490 for(float z = -offset; z < offset; z += offset / (samples * 0.5)) 491 { 492 float closestDepth = texture(depthMap, fragToLight + vec3(x, y, z)).r; 493 closestDepth *= far_plane; // undo mapping [0;1] 494 if(currentDepth - bias > closestDepth) 495 shadow += 1.0; 496 } 497 } 498 } 499 shadow /= (samples * samples * samples); 500 </code></pre> 501 502 <p> 503 The code isn't that different from the traditional shadow mapping code. We calculate and add texture offsets dynamically for each axis based on a fixed number of samples. For each sample we repeat the original shadow process on the offsetted sample direction and average the results at the end. 504 </p> 505 506 <p> 507 The shadows now look more soft and smooth and give more plausible results. 508 </p> 509 510 <img src="/img/advanced-lighting/point_shadows_soft.png" class="clean" alt="Soft shades with omnidirectional shadow mapping in OpenGL using PCF"/> 511 512 <p> 513 However, with <var>samples</var> set to <code>4.0</code> we take a total of <code>64</code> samples each fragment which is a lot! 514 </p> 515 516 <p> 517 As most of these samples are redundant in that they sample close to the original direction vector it may make more sense to only sample in perpendicular directions of the sample direction vector. However as there is no (easy) way to figure out which sub-directions are redundant this becomes difficult. One trick we can use is to take an array of offset directions that are all roughly separable e.g. each of them points in completely different directions. This will significantly reduce the number of sub-directions that are close together. Below we have such an array of a maximum of <code>20</code> offset directions: 518 </p> 519 520 <pre><code> 521 vec3 sampleOffsetDirections[20] = vec3[] 522 ( 523 vec3( 1, 1, 1), vec3( 1, -1, 1), vec3(-1, -1, 1), vec3(-1, 1, 1), 524 vec3( 1, 1, -1), vec3( 1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1), 525 vec3( 1, 1, 0), vec3( 1, -1, 0), vec3(-1, -1, 0), vec3(-1, 1, 0), 526 vec3( 1, 0, 1), vec3(-1, 0, 1), vec3( 1, 0, -1), vec3(-1, 0, -1), 527 vec3( 0, 1, 1), vec3( 0, -1, 1), vec3( 0, -1, -1), vec3( 0, 1, -1) 528 ); 529 </code></pre> 530 531 <p> 532 From this we can adapt the PCF algorithm to take a fixed amount of samples from <var>sampleOffsetDirections</var> and use these to sample the cubemap. The advantage here is that we need a lot less samples to get visually similar results. 533 </p> 534 535 <pre><code> 536 float shadow = 0.0; 537 float bias = 0.15; 538 int samples = 20; 539 float viewDistance = length(viewPos - fragPos); 540 float diskRadius = 0.05; 541 for(int i = 0; i < samples; ++i) 542 { 543 float closestDepth = texture(depthMap, fragToLight + sampleOffsetDirections[i] * diskRadius).r; 544 closestDepth *= far_plane; // undo mapping [0;1] 545 if(currentDepth - bias > closestDepth) 546 shadow += 1.0; 547 } 548 shadow /= float(samples); 549 </code></pre> 550 551 <p> 552 Here we add multiple offsets, scaled by some <var>diskRadius</var>, around the original <var>fragToLight</var> direction vector to sample from the cubemap. 553 </p> 554 555 <p> 556 Another interesting trick we can apply here is that we can change <var>diskRadius</var> based on the distance of the viewer to the fragment, making the shadows softer when far away and sharper when close by. 557 </p> 558 559 <pre><code> 560 float diskRadius = (1.0 + (viewDistance / far_plane)) / 25.0; 561 </code></pre> 562 563 <p> 564 The results of the updated PCF algorithm gives just as good, if not better, results of soft shadows: 565 </p> 566 567 <img src="/img/advanced-lighting/point_shadows_soft_better.png" class="clean" alt="Soft shades with omnidirectional shadow mapping in OpenGL using PCF, more efficient"/> 568 569 <p> 570 Of course, the <var>bias</var> we add to each sample is highly based on context and will always require tweaking based on the scene you're working with. Play around with all the values and see how they affect the scene. 571 </p> 572 573 <p> 574 You can find the final code here: <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/3.2.2.point_shadows_soft/point_shadows_soft.cpp" target="_blank">here</a>. 575 </p> 576 577 <p> 578 I should mention that using geometry shaders to generate a depth map isn't necessarily faster than rendering the scene 6 times for each face. Using a geometry shader like this has its own performance penalties that may outweigh the performance gain of using one in the first place. This of course depends on the type of environment, the specific video card drivers, and plenty of other factors. So if you really care about pushing the most out of your system, make sure to profile both methods and select the more efficient one for your scene. 579 </p> 580 581 <h2>Additional resources</h2> 582 <ul> 583 <li><a href="http://www.sunandblackcat.com/tipFullView.php?l=eng&topicid=36" target="_blank">Shadow Mapping for point light sources in OpenGL</a>: omnidirectional shadow mapping tutorial by sunandblackcat.</li> 584 <li><a href="http://ogldev.atspace.co.uk/www/tutorial43/tutorial43.html" target="_blank">Multipass Shadow Mapping With Point Lights</a>: omnidirectional shadow mapping tutorial by ogldev.</li> 585 <li><a href="http://www.cg.tuwien.ac.at/~husky/RTR/OmnidirShadows-whyCaps.pdf" target="_blank">Omni-directional Shadows</a>: a nice set of slides about omnidirectional shadow mapping by Peter Houska.</li> 586 </ul> 587 588 </div> 589 590 <div id="hover"> 591 HI 592 </div> 593 <!-- 728x90/320x50 sticky footer --> 594 <div id="waldo-tag-6196"></div> 595 596 <div id="disqus_thread"></div> 597 598 599 600 601 </div> <!-- container div --> 602 603 604 </div> <!-- super container div --> 605 </body> 606 </html>