LearnOpenGL

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

CSM.html (35760B)


      1 <!DOCTYPE html>
      2 <html lang="ja"> 
      3 <head>
      4     <meta charset="utf-8"/>
      5     <title>LearnOpenGL</title>
      6     <link rel="shortcut icon" type="image/ico" href="/favicon.ico"  />
      7 	<link rel="stylesheet" href="../static/style.css" />
      8 	<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"> </script>
      9 	<script src="/static/functions.js"></script>
     10 </head>
     11 <body>
     12 	<nav>
     13 <ol>
     14 	<li id="Introduction">
     15 		<a href="https://learnopengl.com/Introduction">はじめに</a>
     16 	</li>
     17 	<li id="Getting-started">
     18 		<span class="closed">入門</span>
     19 		<ol>
     20 			<li id="Getting-started/OpenGL">
     21 				<a href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a>
     22 			</li>
     23 			<li id="Getting-started/Creating-a-window">
     24 				<a href="https://learnopengl.com/Getting-started/Creating-a-window">ウィンドウの作成</a>
     25 			</li>
     26 			<li id="Getting-started/Hello-Window">
     27 				<a href="https://learnopengl.com/Getting-started/Hello-Window">最初のウィンドウ</a>
     28 			</li>
     29 			<li id="Getting-started/Hello-Triangle">
     30 				<a href="https://learnopengl.com/Getting-started/Hello-Triangle">最初の三角形</a>
     31 			</li>
     32 			<li id="Getting-started/Shaders">
     33 				<a href="https://learnopengl.com/Getting-started/Shaders">シェーダー</a>
     34 			</li>
     35 			<li id="Getting-started/Textures">
     36 				<a href="https://learnopengl.com/Getting-started/Textures">テクスチャ</a>
     37 			</li>
     38 			<li id="Getting-started/Transformations">
     39 				<a href="https://learnopengl.com/Getting-started/Transformations">座標変換</a>
     40 			</li>
     41 			<li id="Getting-started/Coordinate-Systems">
     42 				<a href="https://learnopengl.com/Getting-started/Coordinate-Systems">座標系</a>
     43 			</li>
     44 			<li id="Getting-started/Camera">
     45 				<a href="https://learnopengl.com/Getting-started/Camera">カメラ</a>
     46 			</li>
     47 			<li id="Getting-started/Review">
     48 				<a href="https://learnopengl.com/Getting-started/Review">まとめ</a>
     49 			</li>
     50 		</ol>
     51 	</li>
     52 	<li id="Lighting">
     53 		<span class="closed">Lighting </span>
     54 		<ol>
     55 			<li id="Lighting/Colors">
     56 				<a href="https://learnopengl.com/Lighting/Colors">Colors </a>
     57 			</li>
     58 			<li id="Lighting/Basic-Lighting">
     59 				<a href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a>
     60 			</li>
     61 			<li id="Lighting/Materials">
     62 				<a href="https://learnopengl.com/Lighting/Materials">Materials </a>
     63 			</li>
     64 			<li id="Lighting/Lighting-maps">
     65 				<a href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a>
     66 			</li>
     67 			<li id="Lighting/Light-casters">
     68 				<a href="https://learnopengl.com/Lighting/Light-casters">Light casters </a>
     69 			</li>
     70 			<li id="Lighting/Multiple-lights">
     71 				<a href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a>
     72 			</li>
     73 			<li id="Lighting/Review">
     74 				<a href="https://learnopengl.com/Lighting/Review">Review </a>
     75 			</li>
     76 		</ol>
     77 	</li>
     78 	<li id="Model-Loading">
     79 		<span class="closed">Model Loading </span>
     80 		<ol>
     81 			<li id="Model-Loading/Assimp">
     82 				<a href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a>
     83 			</li>
     84 			<li id="Model-Loading/Mesh">
     85 				<a href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a>
     86 			</li>
     87 			<li id="Model-Loading/Model">
     88 				<a href="https://learnopengl.com/Model-Loading/Model">Model </a>
     89 			</li>
     90 		</ol>
     91 	</li>
     92 	<li id="Advanced-OpenGL">
     93 		<span class="closed">Advanced OpenGL </span>
     94 		<ol>
     95 			<li id="Advanced-OpenGL/Depth-testing">
     96 				<a href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a>
     97 			</li>
     98 			<li id="Advanced-OpenGL/Stencil-testing">
     99 				<a href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a>
    100 			</li>
    101 			<li id="Advanced-OpenGL/Blending">
    102 				<a href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a>
    103 			</li>
    104 			<li id="Advanced-OpenGL/Face-culling">
    105 				<a href="https://learnopengl.cm/Advanced-OpenGL/Face-culling">Face culling </a>
    106 			</li>
    107 			<li id="Advanced-OpenGL/Framebuffers">
    108 				<a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a>
    109 			</li>
    110 			<li id="Advanced-OpenGL/Cubemaps">
    111 				<a href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a>
    112 			</li>
    113 			<li id="Advanced-OpenGL/Advanced-Data">
    114 				<a href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a>
    115 			</li>
    116 			<li id="Advanced-OpenGL/Advanced-GLSL">
    117 				<a href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a>
    118 			</li>
    119 			<li id="Advanced-OpenGL/Geometry-Shader">
    120 				<a href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a>
    121 			</li>
    122 			<li id="Advanced-OpenGL/Instancing">
    123 				<a href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a>
    124 			</li>
    125 			<li id="Advanced-OpenGL/Anti-Aliasing">
    126 				<a href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a>
    127 			</li>
    128 		</ol>
    129 	</li>
    130 	<li id="Advanced-Lighting">
    131 		<span class="closed">Advanced Lighting </span>
    132 		<ol>
    133 			<li id="Advanced-Lighting/Advanced-Lighting">
    134 				<a href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a>
    135 			</li>
    136 			<li id="Advanced-Lighting/Gamma-Correction">
    137 				<a href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a>
    138 			</li>
    139 			<li id="Advanced-Lighting/Shadows">
    140 				<span class="closed">Shadows </span>
    141 				<ol>
    142 					<li id="Advanced-Lighting/Shadows/Shadow-Mapping">
    143 						<a href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a>
    144 					</li>
    145 					<li id="Advanced-Lighting/Shadows/Point-Shadows">
    146 						<a href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a>
    147 					</li>
    148 				</ol>
    149 			</li>
    150 			<li id="Advanced-Lighting/Normal-Mapping">
    151 				<a href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a>
    152 			</li>
    153 			<li id="Advanced-Lighting/Parallax-Mapping">
    154 				<a href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a>
    155 			</li>
    156 			<li id="Advanced-Lighting/HDR">
    157 				<a href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a>
    158 			</li>
    159 			<li id="Advanced-Lighting/Bloom">
    160 				<a href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a>
    161 			</li>
    162 			<li id="Advanced-Lighting/Deferred-Shading">
    163 				<a href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a>
    164 			</li>
    165 			<li id="Advanced-Lighting/SSAO">
    166 				<a href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a>
    167 			</li>
    168 		</ol>
    169 	</li>
    170 	<li id="PBR">
    171 		<span class="closed">PBR </span>
    172 		<ol>
    173 			<li id="PBR/Theory">
    174 				<a href="https://learnopengl.com/PBR/Theory">Theory </a>
    175 			</li>
    176 			<li id="PBR/Lighting">
    177 				<a href="https://learnopengl.com/PBR/Lighting">Lighting </a>
    178 			</li>
    179 			<li id="PBR/IBL">
    180 				<span class="closed">IBL </span>
    181 				<ol>
    182 					<li id="PBR/IBL/Diffuse-irradiance">
    183 						<a href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a>
    184 					</li>
    185 					<li id="PBR/IBL/Specular-IBL">
    186 						<a href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a>
    187 					</li>
    188 				</ol>
    189 			</li>
    190 		</ol>
    191 	</li>
    192 	<li id="In-Practice">
    193 		<span class="closed">In Practice </span>
    194 		<ol>
    195 			<li id="In-Practice/Debugging">
    196 				<a href="https://learnopengl.com/In-Practice/Debugging">Debugging </a>
    197 			</li>
    198 			<li id="In-Practice/Text-Rendering">
    199 				<a href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a>
    200 			</li>
    201 			<li id="In-Practice/2D-Game">
    202 				<span class="closed">2D Game </span>
    203 				<ol>
    204 					<li id="In-Practice/2D-Game/Breakout">
    205 						<a href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a>
    206 					</li>
    207 					<li id="In-Practice/2D-Game/Setting-up">
    208 						<a href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a>
    209 					</li>
    210 					<li id="In-Practice/2D-Game/Rendering-Sprites">
    211 						<a href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a>
    212 					</li>
    213 					<li id="In-Practice/2D-Game/Levels">
    214 						<a href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a>
    215 					</li>
    216 					<li id="In-Practice/2D-Game/Collisions">
    217 						<span class="closed">Collisions </span>
    218 						<ol>
    219 							<li id="In-Practice/2D-Game/Collisions/Ball">
    220 								<a href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a>
    221 							</li>
    222 							<li id="In-Practice/2D-Game/Collisions/Collision-detection">
    223 								<a href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a>
    224 							</li>
    225 							<li id="In-Practice/2D-Game/Collisions/Collision-resolution">
    226 								<a href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a>
    227 							</li>
    228 						</ol>
    229 					</li>
    230 					<li id="In-Practice/2D-Game/Particles">
    231 						<a href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a>
    232 					</li>
    233 					<li id="In-Practice/2D-Game/Postprocessing">
    234 						<a href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a>
    235 					</li>
    236 					<li id="In-Practice/2D-Game/Powerups">
    237 						<a href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a>
    238 					</li>
    239 					<li id="In-Practice/2D-Game/Audio">
    240 						<a href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a>
    241 					</li>
    242 					<li id="In-Practice/2D-Game/Render-text">
    243 						<a href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a>
    244 					</li>
    245 					<li id="In-Practice/2D-Game/Final-thoughts">
    246 						<a href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a>
    247 					</li>
    248 				</ol>
    249 			</li>
    250 		</ol>
    251 	</li>
    252 	<li id="Guest-Articles">
    253 		<span class="closed">Guest Articles </span>
    254 		<ol>
    255 			<li id="Guest-Articles/How-to-publish">
    256 				<a href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a>
    257 			</li>
    258 			<li id="Guest-Articles/2020">
    259 				<span class="closed">2020 </span>
    260 				<ol>
    261 					<li id="Guest-Articles/2020/OIT">
    262 						<span class="closed">OIT </span>
    263 						<ol>
    264 							<li id="Guest-Articles/2020/OIT/Introduction">
    265 								<a href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a>
    266 							</li>
    267 							<li id="Guest-Articles/2020/OIT/Weighted-Blended">
    268 								<a href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a>
    269 							</li>
    270 						</ol>
    271 					</li>
    272 					<li id="Guest-Articles/2020/Skeletal-Animation">
    273 						<a href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a>
    274 					</li>
    275 				</ol>
    276 			</li>
    277 			<li id="Guest-Articles/2021">
    278 				<span class="closed">2021 </span>
    279 				<ol>
    280 					<li id="Guest-Articles/2021/CSM">
    281 						<a href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a>
    282 					</li>
    283 					<li id="Guest-Articles/2021/Scene">
    284 						<span class="closed">Scene </span>
    285 						<ol>
    286 							<li id="Guest-Articles/2021/Scene/Scene-Graph">
    287 								<a href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a>
    288 							</li>
    289 							<li id="Guest-Articles/2021/Scene/Frustum-Culling">
    290 								<a href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a>
    291 							</li>
    292 						</ol>
    293 					</li>
    294 					<li id="Guest-Articles/2021/Tessellation">
    295 						<span class="closed">Tessellation </span>
    296 						<ol>
    297 							<li id="Guest-Articles/2021/Tessellation/Height-map">
    298 								<a href="https://learnopengl.com/Guest-Articles/2021/Tessellation/Height-map">Height map </a>
    299 							</li>
    300 						</ol>
    301 					</li>
    302 				</ol>
    303 			</li>
    304 		</ol>
    305 	</li>
    306 	<li id="Code-repository">
    307 		<a href="https://learnopengl.com/Code-repository">Code repository </a>
    308 	</li>
    309 	<li id="Translations">
    310 		<a href="https://learnopengl.com/Translations">Translations </a>
    311 	</li>
    312 	<li id="About">
    313 		<a href="https://learnopengl.com/About">About </a>
    314 	</li>
    315 </ol>
    316 	</nav>
    317 	<main>
    318     <div id="content">
    319     <h1 id="content-title">CSM</h1>
    320 <h1 id="content-url" style='display:none;'>Guest-Articles/2021/CSM</h1>
    321 <p>
    322 		<a href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping" target="_blank">Shadow mapping</a> as described on LearnOpenGL is a powerful, and relatively simple technique. However, if implemented as-is from the above referred tutorial, the avid OpenGL student will notice a few shortcomings.
    323 	</p>
    324 	<ul>
    325 		<li>The shadow map is always created around the origin, and not on the area the camera is actually looking at. It would be best of course if we could shadow map the whole scene, with sufficient resolution, but on current hardware this is not feasible. In reality we want the shadow maps to be created on objects that are in view, saving our precious GPU memory for things that matter. </li>
    326 		<li>The shadow map orthographic projection matrix is not properly fitted onto the view frustum. To achieve the best possible resolution for our shadow maps, the ortho matrix needs to be as tightly fit to the camera frustum as possible, because again: if it’s larger that means that less detail is spent on the objects that are actually visible.</li>
    327 		<li>The shadow maps (even with advanced PCF functions) are blurry if we want the shadow rendering distance to be large, as we would in a game with a first-person camera. We can increase the resolution of the shadow maps to mitigate this, but GPU memory is a resource we should be conservative of.</li>
    328 	</ul>
    329 	<p>
    330 		Cascaded shadow mapping is a direct answer to the third point, however while implementing it we will solve the first two points, too. The core insight in cascaded shadow mapping is, that we don’t need the same amount of shadow detail for things that are far from us. We want crisp shadows for stuff that’s near to the near plane, and we are absolutely fine with blurriness for objects that are hundreds of units away: it’s not going to be noticeable at all. How can we achieve this? The answer is beautiful in its simplicity: just render different shadow maps for things that are close and for those that are far away, and sample from them according to the depth of the fragment in the fragment shader. The high-level algorithm is as follows:
    331 	</p>
    332 	<ul>
    333 		<li>Divide our ordinary view frustum into n subfrusta, where the far plane of the <code>i</code>-th frustum is the near plane of the <code>(i+1)</code>-th frustum</li>
    334 		<li>Compute the tightly fitting ortho matrix for each frustum</li>
    335 		<li>For each frustum render a shadow map as if seen from our directional light</li>
    336 		<li>Send all shadow maps to our fragment shader</li>
    337 		<li>Render the scene, and according to the fragment’s depth value sample from the correct shadow map</li>
    338 	</ul>
    339 	<p>
    340 		Sounds simple right? Well, it is, but as it often is when it comes to our friend OpenGL: the devil is in the details.
    341 	</p>
    342 	<img src="/img/guest/2021/CSM/cs_go.png" width="800px">
    343 	<p>
    344 		In the above image we can see the edges of shadow cascades in Counter-Strike: Global Offensive. The image was captured on low graphics settings.
    345 	</p>
    346       
    347 	<h2>World coordinates of the view frustum</h2>
    348 	<p>
    349 		Before getting our hands dirty with shadows, we need to tackle a more abstract problem: making our projection matrix fit nicely onto a generic frustum. To be able to do this, we need to know the world space coordinates of the frustum in question. While this might sound daunting at first, we already have all the tools necessary in our repertoire.  If we think back on the excellent <a href="https://learnopengl.com/Getting-started/Coordinate-Systems" target="_blank"> coordinate systems</a> tutorial, the beheaded pyramid of the frustum only looks that way in world coordinates, and our view and projection matrices do the job of transforming this unusual shape into the NDC cube. And we know the coordinates of the corners of the NDC cube: the coordinates are in the range <code>[-1,1]</code>  on the three axes. Because matrix multiplication is a reversible process, we can apply the inverse of the view and projection matrices on the corner points of the NDC cube to get the frustum corners in world space.
    350 	</p>
    351 	<math>
    352 		$$v_{NDC} = M_{proj} M_{view} v_{world}$$
    353 		$$v_{world} = (M_{proj} M_{view})^{-1} v_{NDC}$$
    354 	</math>
    355 
    356 <pre><code>
    357 std::vector&lt;glm::vec4&gt; getFrustumCornersWorldSpace(const glm::mat4& proj, const glm::mat4& view)
    358 {
    359     const auto inv = glm::inverse(proj * view);
    360     
    361     std::vector&lt;glm::vec4> frustumCorners;
    362     for (unsigned int x = 0; x &lt; 2; ++x)
    363     {
    364         for (unsigned int y = 0; y &lt; 2; ++y)
    365         {
    366             for (unsigned int z = 0; z &lt; 2; ++z)
    367             {
    368                 const glm::vec4 pt = 
    369                     inv * glm::vec4(
    370                         2.0f * x - 1.0f,
    371                         2.0f * y - 1.0f,
    372                         2.0f * z - 1.0f,
    373                         1.0f);
    374                 frustumCorners.push_back(pt / pt.w);
    375             }
    376         }
    377     }
    378     
    379     return frustumCorners;
    380 }
    381 </code></pre>
    382   
    383 	<p>
    384 		The projection matrix described here is a perspective matrix, using the camera’s aspect ratio and fov, and using the near and far plane of the current frustum being analyzed. The view matrix is the view matrix of our camera.
    385 	</p>
    386       
    387 <pre><code>
    388 const auto proj = <function id='58'>glm::perspective</function>(
    389     <function id='63'>glm::radians</function>(camera.Zoom),
    390     (float)SCR_WIDTH / (float)SCR_HEIGHT,
    391     nearPlane,
    392     farPlane
    393 );
    394 </code></pre>
    395       
    396 	<img src="/img/guest/2021/CSM/frustum_fitting.png">
    397 	<br>
    398       
    399 	<h2>The light view-projection matrix</h2>
    400 	<p>
    401 		This matrix - as in ordinary shadow mapping – is the product of the view and projection matrix that transforms the scene as if it were seen by the light. Calculating the view matrix is simple, we know the direction our light is coming from, and we know a point in world space that it definitely is looking at: the center of the frustum. The position of the frustum center can be obtained by averaging the coordinates of the frustum corners. (This is so because averaging the coordinates of the near and far plane gives us the center of those rectangles, and taking the midpoint of these two points gives us the center of the frustum.)
    402 	</p>
    403 	<pre><code>
    404 glm::vec3 center = glm::vec3(0, 0, 0);
    405 for (const auto& v : corners)
    406 {
    407     center += glm::vec3(v);
    408 }
    409 center /= corners.size();
    410     
    411 const auto lightView = <function id='62'>glm::lookAt</function>(
    412     center + lightDir,
    413     center,
    414     glm::vec3(0.0f, 1.0f, 0.0f)
    415 );
    416     </code></pre>
    417 	<p>
    418 		The projection matrix is bit more complex. Because the light in question is a directional light, the matrix needs to be an orthographic projection matrix, this much is clear. To understand how we determine the left, right, top etc. parameters of the matrix imagine the scene you are drawing from the perspective of the light. From this viewpoint the shadow map we are trying to render is going to be an axis aligned rectangle, and this rectangle – as we established before – needs to fit on the frustum tightly. So we need to obtain the coordinates of the frustum in this space, and take the maximum and minimum of the coordinates along the coordinate axes. While this might sound daunting at first, this perspective is exactly what our light view matrix transformation gives us. We need to transform the frustum corner points in the light view space, and find the maximum and minimum coordinates.
    419 	</p>
    420       
    421 	<pre><code>
    422 float minX = std::numeric_limits&lt;float>::max();
    423 float maxX = std::numeric_limits&lt;float>::min();
    424 float minY = std::numeric_limits&lt;float>::max();
    425 float maxY = std::numeric_limits&lt;float>::min();
    426 float minZ = std::numeric_limits&lt;float>::max();
    427 float maxZ = std::numeric_limits&lt;float>::min();
    428 for (const auto& v : corners)
    429 {
    430     const auto trf = lightView * v;
    431     minX = std::min(minX, trf.x);
    432     maxX = std::max(maxX, trf.x);
    433     minY = std::min(minY, trf.y);
    434     maxY = std::max(maxY, trf.y);
    435     minZ = std::min(minZ, trf.z);
    436     maxZ = std::max(maxZ, trf.z);
    437 }
    438 	</code></pre>
    439       
    440 	<p>
    441 		We are going to create our projection matrix from the product of two matrices. First, we are going to create an ortho projection matrix, with the left, right, top, bottom parameters set to <code>-1</code> or <code>1</code>, and the z values set to <var>minZ</var> and <var>maxZ</var>. This creates a 3D rectangle sitting on the origin with width and height of <code>2</code>, and depth of (<var>maxZ</var> – <var>minZ</var>). In the code we increase the amount of space covered by <var>minZ</var> and <var>maxZ</var> by multiplying or dividing them with a <var>zMult</var>. This is because we want to include geometry which is behind or in front of our frustum in camera space. Think about it: not only geometry which is in the frustum can cast shadows on a surface in the frustum!
    442 	</p>
    443       
    444 <pre><code>
    445 // Tune this parameter according to the scene
    446 constexpr float zMult = 10.0f;
    447 if (minZ &lt; 0)
    448 {
    449     minZ *= zMult;
    450 }
    451 else
    452 {
    453     minZ /= zMult;
    454 }
    455 if (maxZ &lt; 0)
    456 {
    457     maxZ /= zMult;
    458 }
    459 else
    460 {
    461     maxZ *= zMult;
    462 }
    463    
    464 const glm::mat4 lpMatrix = <function id='59'>glm::ortho</function>(-1.0f, 1.0f, -1.0f, 1.0f, minZ, maxZ);
    465 </code></pre>
    466 	<p>
    467 		The other part of our projection matrix is going to be a crop matrix, which moves the rectangle onto the frustum in light view space. Starting from a unit matrix, we need to set the x and y scaling components, and the x and y offset components of the matrix. 
    468 	</p>
    469       
    470 	<math>
    471 		$$S_x = {2 \over M_x - m_x}$$
    472 		$$S_y = {2 \over M_y - m_y}$$
    473 		$$O_x = -0.5(M_x + m_x)S_x$$
    474 		$$O_y = -0.5(M_y + m_y)S_y$$
    475 		$$C = \begin{bmatrix}S_x & 0 & 0 & O_x \\0 & S_y & 0 & O_y \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1\end{bmatrix}$$
    476 	</math>
    477 	
    478 	<p>
    479 		Let’s look at S<sub>x</sub>. It needs to shrink down the frustum bounding rectangle to a unit size, this is achieved by dividing by the width of the rectangle (M<sub>x</sub> – m<sub>x</sub>). However, we need to scale by <code>2</code>, because our 3D rectangle sitting on the origin has a width of <code>2</code>. S<sub>y</sub> is analogous to this.
    480 	</p>
    481 	<img src="/img/guest/2021/CSM/frustum_cropping.png" width="800px">
    482 	<p>
    483 		For the x offset, we need to multiply by the negative halfway point between M<sub>x</sub> and m<sub>x</sub>, and scale by S<sub>x</sub>. And the y offset is analogous.
    484 	</p>
    485 	
    486 <pre><code>
    487 const float scaleX = 2.0f / (maxX - minX);
    488 const float scaleY = 2.0f / (maxY - minY);
    489 const float offsetX = -0.5f * (minX + maxX) * scaleX;
    490 const float offsetY = -0.5f * (minY + maxY) * scaleY;
    491     
    492 glm::mat4 cropMatrix(1.0f);
    493 cropMatrix[0][0] = scaleX;
    494 cropMatrix[1][1] = scaleY;
    495 cropMatrix[3][0] = offsetX;
    496 cropMatrix[3][1] = offsetY;
    497     
    498 return cropMatrix * lpMatrix * lightView;
    499 </code></pre>
    500 	
    501 	<p>
    502 		Multiplying the view, projection and crop matrices together, we get the view-projection matrix of the light for the given frustum. We need to do this procedure for every frustum in our cascade.
    503 	</p>
    504       
    505 	<h2>2D array textures</h2>
    506 	<p>
    507 		While we let our stomachs digest what we learned about frustum fitting we should figure out how to store our shadow maps. In principle there is no limit on how many cascades we can do, so hardcoding an arbitrary value doesn’t seem like a wise idea. Also, it quickly becomes tiresome typing out and binding sampler2Ds for our shaders. OpenGL has a good solution to this problem in the form of <def>2D array textures</def>. This object is an array of textures, which have the same dimensions. To use them in shaders declare them like this:
    508 	</p>
    509       
    510 <pre><code>
    511 uniform sampler2DArray shadowMap;
    512 </code></pre>
    513       
    514 	<p>
    515 		To sample from them we can use the regular texture function with a vec3 parameter for texture coordinates, the third dimension specifying which layer to sample from, starting from <code>0</code>.
    516 	</p>
    517       
    518 <pre><code>
    519 texture(depthMap, vec3(TexCoords, currentLayer))
    520 </code></pre>
    521       
    522 	<p>
    523 		Creating our array texture is slightly different than creating a regular old texture2D. Instead of <function id='52'>glTexImage2D</function> we use glTexImage3D to allocate memory, and when binding the texture to the framebuffer we use glFramebufferTexture instead of <function id='81'>glFramebufferTexture2D</function>. The parameters of these functions are somewhat self-explanatory if we know the old versions. When calling the OpenGL functions, we need to pass <var>GL_TEXTURE_2D_ARRAY</var> instead of <var>GL_TEXTURE_2D</var> as the target, to tell OpenGL what kind of texture we are dealing with.
    524 	</p>
    525 	
    526 <pre><code>
    527 <function id='76'>glGenFramebuffers</function>(1, &lightFBO);
    528     
    529 <function id='50'>glGenTextures</function>(1, &lightDepthMaps);
    530 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D_ARRAY, lightDepthMaps);
    531 glTexImage3D(
    532     GL_TEXTURE_2D_ARRAY,
    533     0,
    534     GL_DEPTH_COMPONENT32F,
    535     depthMapResolution,
    536     depthMapResolution,
    537     int(shadowCascadeLevels.size()) + 1,
    538     0,
    539     GL_DEPTH_COMPONENT,
    540     GL_FLOAT,
    541     nullptr);
    542     
    543 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    544 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    545 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
    546 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
    547     
    548 constexpr float bordercolor[] = { 1.0f, 1.0f, 1.0f, 1.0f };
    549 <function id='15'>glTexParameter</function>fv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, bordercolor);
    550     
    551 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, lightFBO);
    552 glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, lightDepthMaps, 0);
    553 glDrawBuffer(GL_NONE);
    554 glReadBuffer(GL_NONE);
    555     
    556 int status = <function id='79'>glCheckFramebufferStatus</function>(GL_FRAMEBUFFER);
    557 if (status != GL_FRAMEBUFFER_COMPLETE)
    558 {
    559     std::cout &lt;&lt; "ERROR::FRAMEBUFFER:: Framebuffer is not complete!";
    560     throw 0;
    561 }
    562     
    563 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0);
    564 </code></pre>
    565       
    566 	<p>
    567 		Take care when binding this texture to a sampler. Again: we need to use <var>GL_TEXTURE_2D_ARRAY</var> as the target parameter.
    568 	</p>
    569       
    570 <pre><code>
    571 <function id='49'>glActiveTexture</function>(GL_TEXTURE1);
    572 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D_ARRAY, lightDepthMaps);
    573 </code></pre>
    574 	
    575 	<p>
    576 		So far so good, now we know the semantics of using a texture array. It all seems straightforward, but OpenGL has one more curveball to throw at us: we can’t render into this texture the ordinary way, we need to do something called <def>layered rendering</def>. This process is very similar to what we did with <a href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows" target="_blank">cubemaps and pointlights</a> , we coax the <a href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader" target="_blank">geometry shader</a> into generating multiple layers of geometry for us at the same time. If we recall our depthmap shader is very simple: transform the vertices to light space in the vertex stage, and let the hardware do the rest with an empty fragment shader. In our new depthmap shader we are going to only do the world space transformation in the vertex shader.
    577 	</p>
    578       
    579 <pre><code>
    580 #version 460 core
    581 layout (location = 0) in vec3 aPos;
    582     
    583 uniform mat4 model;
    584     
    585 void main()
    586 {
    587     gl_Position = model * vec4(aPos, 1.0);
    588 }
    589 </code></pre>
    590   
    591 	<p>
    592 	The newly inserted geometry shader will look something like this:
    593 	</p>
    594       
    595 <pre><code>
    596 #version 460 core
    597     
    598 layout(triangles, invocations = 5) in;
    599 layout(triangle_strip, max_vertices = 3) out;
    600     
    601 layout (std140, binding = 0) uniform LightSpaceMatrices
    602 {
    603     mat4 lightSpaceMatrices[16];
    604 };
    605     
    606 void main()
    607 {          
    608     for (int i = 0; i &lt; 3; ++i)
    609     {
    610         gl_Position = 
    611             lightSpaceMatrices[gl_InvocationID] * gl_in[i].gl_Position;
    612         gl_Layer = gl_InvocationID;
    613         EmitVertex();
    614     }
    615     EndPrimitive();
    616 }  
    617 </code></pre>
    618       
    619 	<p>
    620 		The input declaration has a new member, specifying the <def>invocation count</def>. This number means, that this shader will be instanced, these instances running in parallel, and we can refer the current instance by the inbuilt variable <var>gl_InvocationID</var>. We will use this number in the shader code to reference which layer of the array texture we are rendering to, and which shadow matrix we are going to use to do the light space transform. We are iterating over all triangles, and setting <var>gl_Layer</var> and <var>gl_Position</var> accordingly. 
    621 	</p>
    622       
    623 	<note>
    624 		I strongly suggest modifying your Shader class in your engine to enable the possibility of inserting variables into the shader code before shader compilation, so that you can make the <i>invocations</i> parameter dynamic. This way if you modify the number of cascades in the C++ code you dont have to modify the shader itself, removing one cog from the complex machine that is your engine. I didn't include this in the tutorial for the sake of simplicity.
    625 	</note>
    626       
    627 	<p>
    628 		The fragment shader remains the same empty, passthrough shader.
    629 	</p>
    630       
    631 <pre><code>
    632 #version 460 core
    633     
    634 void main()
    635 {             
    636 }
    637 </code></pre>
    638       
    639 	<p>
    640 		Our draw call invoking the shader looks something like this:
    641 	</p>
    642       
    643 <pre><code>
    644 simpleDepthShader.use();
    645     
    646 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, lightFBO);
    647 glFramebufferTexture(GL_FRAMEBUFFER, GL_TEXTURE_2D_ARRAY, lightDepthMaps, 0);
    648 <function id='22'>glViewport</function>(0, 0, depthMapResolution, depthMapResolution);
    649 <function id='10'>glClear</function>(GL_DEPTH_BUFFER_BIT);
    650 <function id='74'>glCullFace</function>(GL_FRONT);  // peter panning
    651 renderScene(simpleDepthShader);
    652 <function id='74'>glCullFace</function>(GL_BACK);
    653 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0);
    654 </code></pre>
    655       
    656 	<img src="/img/guest/2021/CSM/cascades.png" width="800px">
    657 
    658 	<h2>Scene rendering</h2>
    659 	<p>
    660 		Now the only thing remaining is doing the actual shadow rendering. In our ordinary phong/deferred fragment shader where we calculate whether the current fragment is occluded or not, we need to insert some logic to decide which light space matrix to use, and which texture to sample from.
    661 	</p>
    662       
    663 <pre><code>
    664 // select cascade layer
    665 vec4 fragPosViewSpace = view * vec4(fragPosWorldSpace, 1.0);
    666 float depthValue = abs(fragPosViewSpace.z);
    667     
    668 int layer = -1;
    669 for (int i = 0; i &lt; cascadeCount; ++i)
    670 {
    671     if (depthValue &lt; cascadePlaneDistances[i])
    672     {
    673         layer = i;
    674         break;
    675     }
    676 }
    677 if (layer == -1)
    678 {
    679     layer = cascadeCount;
    680 }
    681     
    682 vec4 fragPosLightSpace = lightSpaceMatrices[layer] * vec4(fragPosWorldSpace, 1.0);
    683 </code></pre>
    684       
    685 	<p>
    686 		If you remember to prevent shadow acne we applied a depth bias to our image. We need to do the same here, but keep in mind that we are dealing with multiple shadow maps, and on each of them the pixels cover a widely different amount of space, and a unit increase in pixel value means  different depth increase in all of them. Because of this we need to apply a different bias depending on which shadow map we sample from. In my experience scaling the bias inversely proportionally with the far plane works nicely.
    687 	</p>
    688       
    689 <pre><code>
    690 // perform perspective divide
    691 vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
    692 // transform to [0,1] range
    693 projCoords = projCoords * 0.5 + 0.5;
    694     
    695 // get depth of current fragment from light's perspective
    696 float currentDepth = projCoords.z;
    697 if (currentDepth  &gt; 1.0)
    698 {
    699     return 0.0;
    700 }
    701 // calculate bias (based on depth map resolution and slope)
    702 vec3 normal = normalize(fs_in.Normal);
    703 float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
    704 if (layer == cascadeCount)
    705 {
    706     bias *= 1 / (farPlane * 0.5f);
    707 }
    708 else
    709 {
    710     bias *= 1 / (cascadePlaneDistances[layer] * 0.5f);
    711 }
    712 </code></pre>
    713       
    714 	<note>
    715 		Please note that there are different strategies for applying bias when dealing with shadow maps. I will link to a few sources detailing these in the citations section.
    716 	</note>
    717       
    718 	<p>
    719 		The rest of the function is the same as before, the only difference is that we are sampling from a 2D array texture, hence we need to add a third parameter to the <fun>texture</fun> and the <fun>textureSize</fun> functions.
    720 	</p>
    721       
    722 <pre><code>
    723 // PCF
    724 float shadow = 0.0;
    725 vec2 texelSize = 1.0 / vec2(textureSize(shadowMap, 0));
    726 for(int x = -1; x &lt;= 1; ++x)
    727 {
    728     for(int y = -1; y &lt;= 1; ++y)
    729     {
    730         float pcfDepth = texture(
    731                     shadowMap,
    732                     vec3(projCoords.xy + vec2(x, y) * texelSize,
    733                     layer)
    734                     ).r; 
    735         shadow += (currentDepth - bias) > pcfDepth ? 1.0 : 0.0;        
    736     }    
    737 }
    738 shadow /= 9.0;
    739     
    740 // keep the shadow at 0.0 when outside the far_plane region of the light's frustum.
    741 if(projCoords.z &gt; 1.0)
    742 {
    743     shadow = 0.0;
    744 }
    745     	
    746 return shadow;
    747 </code></pre>
    748       
    749 	<p>
    750 		And that's it! If we did everything correctly we should see that the renderer switches between shadow maps based on the distance. Try setting some unreasonable cascade plane distances (for example only one, which is a few units from the camera) to see if the code really does work. You should see a noticable degradation in shadow quality between the two sides of the plane. If you see moire artifacts on the screen try changing around bias parameters a bit.
    751 	</p>
    752       
    753       <img src="/img/guest/2021/CSM/demoscene.png" width="800px">
    754         
    755         <p>
    756           You can find the full source code for the cascaded shadow mapping demo <a href="/code_viewer_gh.php?code=src/8.guest/2021/2.csm/shadow_mapping.cpp" target="_blank">here</a>.
    757         </p>
    758 
    759 	<h2>Closing thoughts</h2>
    760 	<p>
    761 		In the sample project provided you can toggle depthmap visualization by pressing <kbd>F</kbd>. When in depthmap visualization mode you can press the <kbd>+</kbd> key to swap between the different layers.
    762 	</p>
    763 	<p>
    764 		When browsing through the code you might wonder why is the UBO array length <code>16</code>. This is just an arbitrary choice, to me it seemed unlikely that anyone would use more than <code>16</code> shadow cascades, so this seemed like a nice number to allocate.
    765 	</p>
    766       
    767 	<h2>Additional Resources</h2>
    768 	<ul>
    769 		<li><a href="https://developer.download.nvidia.com/SDK/10.5/opengl/src/cascaded_shadow_maps/doc/cascaded_shadow_maps.pdf" target="_blank">NVIDIA paper on the subject:</a> incomprehensible in my opinion but has to be mentioned</li>
    770 		<li><a href="https://www.gamedev.net/forums/topic/672664-fitting-directional-light-in-view-frustum/?page=1" target="_blank">A series of incredibly helpful and useful forum posts</a></li>
    771 		<li><a href="https://ogldev.org/www/tutorial49/tutorial49.html" target="_blank">Another interesting tutorial from OGLDev</a></li>
    772 		<li><a href="https://docs.microsoft.com/en-us/windows/win32/dxtecharts/cascaded-shadow-maps" target="_blank">An article from Microsoft:</a> nice pictures illustrating some issues with CSM</li>
    773 		<li><a href="https://digitalrune.github.io/DigitalRune-Documentation/html/3f4d959e-9c98-4a97-8d85-7a73c26145d7.htm" target="_blank">An article about shadow bias</a></li>
    774 		<li><a href="http://c0de517e.blogspot.com/2011/05/shadowmap-bias-notes.html" target="_blank">Some informative drawings about shadow bias strategies</a></li>
    775 	</ul>
    776 	<br>
    777       
    778 <author>
    779   <strong>Article by: </strong>Márton Árbócz<br/>
    780   <!--<strong>Contact: </strong><a href="mailto:eklavyagames@gmail.com" target="_blank">e-mail</a>-->
    781 </author>       
    782 
    783     </div>
    784     
    785 	</main>
    786 </body>
    787 </html>