Line data Source code
1 : /* Copyright (C) 2023 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "precompiled.h"
19 :
20 : #include "ShadowMap.h"
21 :
22 : #include "graphics/Camera.h"
23 : #include "graphics/LightEnv.h"
24 : #include "graphics/ShaderManager.h"
25 : #include "lib/bits.h"
26 : #include "maths/BoundingBoxAligned.h"
27 : #include "maths/Brush.h"
28 : #include "maths/Frustum.h"
29 : #include "maths/MathUtil.h"
30 : #include "maths/Matrix3D.h"
31 : #include "ps/CLogger.h"
32 : #include "ps/ConfigDB.h"
33 : #include "ps/CStrInternStatic.h"
34 : #include "ps/Profile.h"
35 : #include "ps/VideoMode.h"
36 : #include "renderer/backend/IDevice.h"
37 : #include "renderer/backend/ITexture.h"
38 : #include "renderer/DebugRenderer.h"
39 : #include "renderer/Renderer.h"
40 : #include "renderer/RenderingOptions.h"
41 : #include "renderer/SceneRenderer.h"
42 :
43 : #include <array>
44 :
45 : namespace
46 : {
47 :
48 : constexpr int MAX_CASCADE_COUNT = 4;
49 :
50 : constexpr float DEFAULT_SHADOWS_CUTOFF_DISTANCE = 300.0f;
51 : constexpr float DEFAULT_CASCADE_DISTANCE_RATIO = 1.7f;
52 :
53 : } // anonymous namespace
54 :
55 : /**
56 : * Struct ShadowMapInternals: Internal data for the ShadowMap implementation
57 : */
58 12 : struct ShadowMapInternals
59 : {
60 : std::unique_ptr<Renderer::Backend::IFramebuffer> Framebuffer;
61 : std::unique_ptr<Renderer::Backend::ITexture> Texture;
62 :
63 : // bit depth for the depth texture
64 : int DepthTextureBits;
65 : // width, height of shadow map
66 : int Width, Height;
67 : // Shadow map quality (-1 - Low, 0 - Medium, 1 - High, 2 - Very High)
68 : int QualityLevel;
69 : // used width, height of shadow map
70 : int EffectiveWidth, EffectiveHeight;
71 :
72 : // Transform world space into light space; calculated on SetupFrame
73 : CMatrix3D LightTransform;
74 :
75 : // transform light space into world space
76 : CMatrix3D InvLightTransform;
77 : CBoundingBoxAligned ShadowReceiverBound;
78 :
79 : int CascadeCount;
80 : float CascadeDistanceRatio;
81 : float ShadowsCutoffDistance;
82 : bool ShadowsCoverMap;
83 :
84 24 : struct Cascade
85 : {
86 : // transform light space into projected light space
87 : // in projected light space, the shadowbound box occupies the [-1..1] cube
88 : // calculated on BeginRender, after the final shadow bounds are known
89 : CMatrix3D LightProjection;
90 : float Distance;
91 : CBoundingBoxAligned FrustumBBAA;
92 : CBoundingBoxAligned ConvexBounds;
93 : CBoundingBoxAligned ShadowRenderBound;
94 : // Bounding box of shadowed objects in the light space.
95 : CBoundingBoxAligned ShadowCasterBound;
96 : // Transform world space into texture space of the shadow map;
97 : // calculated on BeginRender, after the final shadow bounds are known
98 : CMatrix3D TextureMatrix;
99 : // View port of the shadow texture where the cascade should be rendered.
100 : SViewPort ViewPort;
101 : };
102 : std::array<Cascade, MAX_CASCADE_COUNT> Cascades;
103 :
104 : // Camera transformed into light space
105 : CCamera LightspaceCamera;
106 :
107 : // Some drivers (at least some Intel Mesa ones) appear to handle alpha testing
108 : // incorrectly when the FBO has only a depth attachment.
109 : // When m_ShadowAlphaFix is true, we use DummyTexture to store a useless
110 : // alpha texture which is attached to the FBO as a workaround.
111 : std::unique_ptr<Renderer::Backend::ITexture> DummyTexture;
112 :
113 : // Copy of renderer's standard view camera, saved between
114 : // BeginRender and EndRender while we replace it with the shadow camera
115 : CCamera SavedViewCamera;
116 :
117 : void CalculateShadowMatrices(const int cascade);
118 : void CreateTexture();
119 : void UpdateCascadesParameters();
120 : };
121 :
122 6 : void ShadowMapInternals::UpdateCascadesParameters()
123 : {
124 6 : CascadeCount = 1;
125 6 : CFG_GET_VAL("shadowscascadecount", CascadeCount);
126 :
127 6 : if (CascadeCount < 1 || CascadeCount > MAX_CASCADE_COUNT || g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::GL_ARB)
128 0 : CascadeCount = 1;
129 :
130 6 : ShadowsCoverMap = false;
131 6 : CFG_GET_VAL("shadowscovermap", ShadowsCoverMap);
132 6 : }
133 :
134 0 : void CalculateBoundsForCascade(
135 : const CCamera& camera, const CMatrix3D& lightTransform,
136 : const float nearPlane, const float farPlane, CBoundingBoxAligned* bbaa,
137 : CBoundingBoxAligned* frustumBBAA)
138 : {
139 0 : frustumBBAA->SetEmpty();
140 :
141 : // We need to calculate a circumscribed sphere for the camera to
142 : // create a rotation stable bounding box.
143 0 : const CVector3D cameraIn = camera.m_Orientation.GetIn();
144 0 : const CVector3D cameraTranslation = camera.m_Orientation.GetTranslation();
145 0 : const CVector3D centerNear = cameraTranslation + cameraIn * nearPlane;
146 0 : const CVector3D centerDist = cameraTranslation + cameraIn * farPlane;
147 :
148 : // We can solve 3D problem in 2D space, because the frustum is
149 : // symmetric by 2 planes. Than means we can use only one corner
150 : // to find a circumscribed sphere.
151 0 : CCamera::Quad corners;
152 :
153 0 : camera.GetViewQuad(nearPlane, corners);
154 0 : for (CVector3D& corner : corners)
155 0 : corner = camera.GetOrientation().Transform(corner);
156 0 : const CVector3D cornerNear = corners[0];
157 0 : for (const CVector3D& corner : corners)
158 0 : *frustumBBAA += lightTransform.Transform(corner);
159 :
160 0 : camera.GetViewQuad(farPlane, corners);
161 0 : for (CVector3D& corner : corners)
162 0 : corner = camera.GetOrientation().Transform(corner);
163 0 : const CVector3D cornerDist = corners[0];
164 0 : for (const CVector3D& corner : corners)
165 0 : *frustumBBAA += lightTransform.Transform(corner);
166 :
167 : // We solve 2D case for the right trapezoid.
168 0 : const float firstBase = (cornerNear - centerNear).Length();
169 0 : const float secondBase = (cornerDist - centerDist).Length();
170 0 : const float height = (centerDist - centerNear).Length();
171 0 : const float distanceToCenter =
172 0 : (height * height + secondBase * secondBase - firstBase * firstBase) * 0.5f / height;
173 :
174 0 : CVector3D position = cameraTranslation + cameraIn * (nearPlane + distanceToCenter);
175 0 : const float radius = (cornerNear - position).Length();
176 :
177 : // We need to convert the bounding box to the light space.
178 0 : position = lightTransform.Rotate(position);
179 :
180 0 : const float insets = 0.2f;
181 0 : *bbaa = CBoundingBoxAligned(position, position);
182 0 : bbaa->Expand(radius);
183 0 : bbaa->Expand(insets);
184 0 : }
185 :
186 6 : ShadowMap::ShadowMap()
187 : {
188 6 : m = new ShadowMapInternals;
189 6 : m->Framebuffer = 0;
190 6 : m->Width = 0;
191 6 : m->Height = 0;
192 6 : m->QualityLevel = 0;
193 6 : m->EffectiveWidth = 0;
194 6 : m->EffectiveHeight = 0;
195 6 : m->DepthTextureBits = 0;
196 : // DepthTextureBits: 24/32 are very much faster than 16, on GeForce 4 and FX;
197 : // but they're very much slower on Radeon 9800.
198 : // In both cases, the default (no specified depth) is fast, so we just use
199 : // that by default and hope it's alright. (Otherwise, we'd probably need to
200 : // do some kind of hardware detection to work out what to use.)
201 :
202 : // Avoid using uninitialised values in AddShadowedBound if SetupFrame wasn't called first
203 6 : m->LightTransform.SetIdentity();
204 :
205 6 : m->UpdateCascadesParameters();
206 6 : }
207 :
208 12 : ShadowMap::~ShadowMap()
209 : {
210 6 : m->Framebuffer.reset();
211 6 : m->Texture.reset();
212 6 : m->DummyTexture.reset();
213 :
214 6 : delete m;
215 6 : }
216 :
217 : // Force the texture/buffer/etc to be recreated, particularly when the renderer's
218 : // size has changed
219 0 : void ShadowMap::RecreateTexture()
220 : {
221 0 : m->Framebuffer.reset();
222 0 : m->Texture.reset();
223 0 : m->DummyTexture.reset();
224 :
225 0 : m->UpdateCascadesParameters();
226 :
227 : // (Texture will be constructed in next SetupFrame)
228 0 : }
229 :
230 : // SetupFrame: camera and light direction for this frame
231 0 : void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir)
232 : {
233 0 : if (!m->Texture)
234 0 : m->CreateTexture();
235 :
236 0 : CVector3D x(0, 1, 0), eyepos;
237 :
238 0 : CVector3D z = lightdir;
239 0 : z.Normalize();
240 0 : x -= z * z.Dot(x);
241 0 : if (x.Length() < 0.001)
242 : {
243 : // this is invoked if the camera and light directions almost coincide
244 : // assumption: light direction has a significant Z component
245 0 : x = CVector3D(1.0, 0.0, 0.0);
246 0 : x -= z * z.Dot(x);
247 : }
248 0 : x.Normalize();
249 0 : CVector3D y = z.Cross(x);
250 :
251 : // X axis perpendicular to light direction, flowing along with view direction
252 0 : m->LightTransform._11 = x.X;
253 0 : m->LightTransform._12 = x.Y;
254 0 : m->LightTransform._13 = x.Z;
255 :
256 : // Y axis perpendicular to light and view direction
257 0 : m->LightTransform._21 = y.X;
258 0 : m->LightTransform._22 = y.Y;
259 0 : m->LightTransform._23 = y.Z;
260 :
261 : // Z axis is in direction of light
262 0 : m->LightTransform._31 = z.X;
263 0 : m->LightTransform._32 = z.Y;
264 0 : m->LightTransform._33 = z.Z;
265 :
266 : // eye is at the origin of the coordinate system
267 0 : m->LightTransform._14 = -x.Dot(eyepos);
268 0 : m->LightTransform._24 = -y.Dot(eyepos);
269 0 : m->LightTransform._34 = -z.Dot(eyepos);
270 :
271 0 : m->LightTransform._41 = 0.0;
272 0 : m->LightTransform._42 = 0.0;
273 0 : m->LightTransform._43 = 0.0;
274 0 : m->LightTransform._44 = 1.0;
275 :
276 0 : m->LightTransform.GetInverse(m->InvLightTransform);
277 0 : m->ShadowReceiverBound.SetEmpty();
278 :
279 0 : m->LightspaceCamera = camera;
280 0 : m->LightspaceCamera.m_Orientation = m->LightTransform * camera.m_Orientation;
281 0 : m->LightspaceCamera.UpdateFrustum();
282 :
283 0 : m->ShadowsCutoffDistance = DEFAULT_SHADOWS_CUTOFF_DISTANCE;
284 0 : m->CascadeDistanceRatio = DEFAULT_CASCADE_DISTANCE_RATIO;
285 0 : CFG_GET_VAL("shadowscutoffdistance", m->ShadowsCutoffDistance);
286 0 : CFG_GET_VAL("shadowscascadedistanceratio", m->CascadeDistanceRatio);
287 0 : m->CascadeDistanceRatio = Clamp(m->CascadeDistanceRatio, 1.1f, 16.0f);
288 :
289 0 : m->Cascades[GetCascadeCount() - 1].Distance = m->ShadowsCutoffDistance;
290 0 : for (int cascade = GetCascadeCount() - 2; cascade >= 0; --cascade)
291 0 : m->Cascades[cascade].Distance = m->Cascades[cascade + 1].Distance / m->CascadeDistanceRatio;
292 :
293 0 : if (GetCascadeCount() == 1 || m->ShadowsCoverMap)
294 : {
295 0 : m->Cascades[0].ViewPort =
296 0 : SViewPort{1, 1, m->EffectiveWidth - 2, m->EffectiveHeight - 2};
297 0 : if (m->ShadowsCoverMap)
298 0 : m->Cascades[0].Distance = camera.GetFarPlane();
299 : }
300 : else
301 : {
302 0 : for (int cascade = 0; cascade < GetCascadeCount(); ++cascade)
303 : {
304 0 : const int offsetX = (cascade & 0x1) ? m->EffectiveWidth / 2 : 0;
305 0 : const int offsetY = (cascade & 0x2) ? m->EffectiveHeight / 2 : 0;
306 0 : m->Cascades[cascade].ViewPort =
307 0 : SViewPort{offsetX + 1, offsetY + 1,
308 0 : m->EffectiveWidth / 2 - 2, m->EffectiveHeight / 2 - 2};
309 : }
310 : }
311 :
312 0 : for (int cascadeIdx = 0; cascadeIdx < GetCascadeCount(); ++cascadeIdx)
313 : {
314 0 : ShadowMapInternals::Cascade& cascade = m->Cascades[cascadeIdx];
315 :
316 0 : const float nearPlane = cascadeIdx > 0 ?
317 0 : m->Cascades[cascadeIdx - 1].Distance : camera.GetNearPlane();
318 0 : const float farPlane = cascade.Distance;
319 :
320 0 : CalculateBoundsForCascade(camera, m->LightTransform,
321 : nearPlane, farPlane, &cascade.ConvexBounds, &cascade.FrustumBBAA);
322 0 : cascade.ShadowCasterBound.SetEmpty();
323 : }
324 0 : }
325 :
326 : // AddShadowedBound: add a world-space bounding box to the bounds of shadowed
327 : // objects
328 0 : void ShadowMap::AddShadowCasterBound(const int cascade, const CBoundingBoxAligned& bounds)
329 : {
330 0 : CBoundingBoxAligned lightspacebounds;
331 :
332 0 : bounds.Transform(m->LightTransform, lightspacebounds);
333 0 : m->Cascades[cascade].ShadowCasterBound += lightspacebounds;
334 0 : }
335 :
336 0 : void ShadowMap::AddShadowReceiverBound(const CBoundingBoxAligned& bounds)
337 : {
338 0 : CBoundingBoxAligned lightspacebounds;
339 :
340 0 : bounds.Transform(m->LightTransform, lightspacebounds);
341 0 : m->ShadowReceiverBound += lightspacebounds;
342 0 : }
343 :
344 0 : CFrustum ShadowMap::GetShadowCasterCullFrustum(const int cascade)
345 : {
346 : // Get the bounds of all objects that can receive shadows
347 0 : CBoundingBoxAligned bound = m->ShadowReceiverBound;
348 :
349 : // Intersect with the camera frustum, so the shadow map doesn't have to get
350 : // stretched to cover the off-screen parts of large models
351 0 : bound.IntersectFrustumConservative(m->Cascades[cascade].FrustumBBAA.ToFrustum());
352 :
353 : // ShadowBound might have been empty to begin with, producing an empty result
354 0 : if (bound.IsEmpty())
355 : {
356 : // CFrustum can't easily represent nothingness, so approximate it with
357 : // a single point which won't match many objects
358 0 : bound += CVector3D(0.0f, 0.0f, 0.0f);
359 0 : return bound.ToFrustum();
360 : }
361 :
362 : // Extend the bounds a long way towards the light source, to encompass
363 : // all objects that might cast visible shadows.
364 : // (The exact constant was picked entirely arbitrarily.)
365 0 : bound[0].Z -= 1000.f;
366 :
367 0 : CFrustum frustum = bound.ToFrustum();
368 0 : frustum.Transform(m->InvLightTransform);
369 0 : return frustum;
370 : }
371 :
372 : // CalculateShadowMatrices: calculate required matrices for shadow map generation - the light's
373 : // projection and transformation matrices
374 0 : void ShadowMapInternals::CalculateShadowMatrices(const int cascade)
375 : {
376 0 : CBoundingBoxAligned& shadowRenderBound = Cascades[cascade].ShadowRenderBound;
377 0 : shadowRenderBound = Cascades[cascade].ConvexBounds;
378 :
379 0 : if (ShadowsCoverMap)
380 : {
381 : // Start building the shadow map to cover all objects that will receive shadows
382 0 : CBoundingBoxAligned receiverBound = ShadowReceiverBound;
383 :
384 : // Intersect with the camera frustum, so the shadow map doesn't have to get
385 : // stretched to cover the off-screen parts of large models
386 0 : receiverBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum());
387 :
388 : // Intersect with the shadow caster bounds, because there's no point
389 : // wasting space around the edges of the shadow map that we're not going
390 : // to draw into
391 0 : shadowRenderBound[0].X = std::max(receiverBound[0].X, Cascades[cascade].ShadowCasterBound[0].X);
392 0 : shadowRenderBound[0].Y = std::max(receiverBound[0].Y, Cascades[cascade].ShadowCasterBound[0].Y);
393 0 : shadowRenderBound[1].X = std::min(receiverBound[1].X, Cascades[cascade].ShadowCasterBound[1].X);
394 0 : shadowRenderBound[1].Y = std::min(receiverBound[1].Y, Cascades[cascade].ShadowCasterBound[1].Y);
395 : }
396 0 : else if (CascadeCount > 1)
397 : {
398 : // We need to offset the cascade to its place on the texture.
399 0 : const CVector3D size = (shadowRenderBound[1] - shadowRenderBound[0]) * 0.5f;
400 0 : if (!(cascade & 0x1))
401 0 : shadowRenderBound[1].X += size.X * 2.0f;
402 : else
403 0 : shadowRenderBound[0].X -= size.X * 2.0f;
404 0 : if (!(cascade & 0x2))
405 0 : shadowRenderBound[1].Y += size.Y * 2.0f;
406 : else
407 0 : shadowRenderBound[0].Y -= size.Y * 2.0f;
408 : }
409 :
410 : // Set the near and far planes to include just the shadow casters,
411 : // so we make full use of the depth texture's range. Add a bit of a
412 : // delta so we don't accidentally clip objects that are directly on
413 : // the planes.
414 0 : shadowRenderBound[0].Z = Cascades[cascade].ShadowCasterBound[0].Z - 2.f;
415 0 : shadowRenderBound[1].Z = Cascades[cascade].ShadowCasterBound[1].Z + 2.f;
416 :
417 : // Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering
418 0 : CVector3D scale = shadowRenderBound[1] - shadowRenderBound[0];
419 0 : CVector3D shift = (shadowRenderBound[1] + shadowRenderBound[0]) * -0.5;
420 :
421 0 : if (scale.X < 1.0)
422 0 : scale.X = 1.0;
423 0 : if (scale.Y < 1.0)
424 0 : scale.Y = 1.0;
425 0 : if (scale.Z < 1.0)
426 0 : scale.Z = 1.0;
427 :
428 0 : scale.X = 2.0 / scale.X;
429 0 : scale.Y = 2.0 / scale.Y;
430 0 : scale.Z = 2.0 / scale.Z;
431 :
432 : // make sure a given world position falls on a consistent shadowmap texel fractional offset
433 0 : float offsetX = fmod(shadowRenderBound[0].X - LightTransform._14, 2.0f/(scale.X*EffectiveWidth));
434 0 : float offsetY = fmod(shadowRenderBound[0].Y - LightTransform._24, 2.0f/(scale.Y*EffectiveHeight));
435 :
436 0 : CMatrix3D& lightProjection = Cascades[cascade].LightProjection;
437 0 : lightProjection.SetZero();
438 0 : lightProjection._11 = scale.X;
439 0 : lightProjection._14 = (shift.X + offsetX) * scale.X;
440 0 : lightProjection._22 = scale.Y;
441 0 : lightProjection._24 = (shift.Y + offsetY) * scale.Y;
442 0 : lightProjection._33 = scale.Z;
443 0 : lightProjection._34 = shift.Z * scale.Z;
444 0 : lightProjection._44 = 1.0;
445 :
446 : // Calculate texture matrix by creating the clip space to texture coordinate matrix
447 : // and then concatenating all matrices that have been calculated so far
448 :
449 0 : float texscalex = scale.X * 0.5f * (float)EffectiveWidth / (float)Width;
450 0 : float texscaley = scale.Y * 0.5f * (float)EffectiveHeight / (float)Height;
451 0 : float texscalez = scale.Z * 0.5f;
452 :
453 0 : CMatrix3D lightToTex;
454 0 : lightToTex.SetZero();
455 0 : lightToTex._11 = texscalex;
456 0 : lightToTex._14 = (offsetX - shadowRenderBound[0].X) * texscalex;
457 0 : lightToTex._22 = texscaley;
458 0 : lightToTex._24 = (offsetY - shadowRenderBound[0].Y) * texscaley;
459 0 : lightToTex._33 = texscalez;
460 0 : lightToTex._34 = -shadowRenderBound[0].Z * texscalez;
461 0 : lightToTex._44 = 1.0;
462 :
463 0 : if (g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::VULKAN)
464 : {
465 0 : CMatrix3D flip;
466 0 : flip.SetIdentity();
467 0 : flip._22 = -1.0f;
468 0 : flip._24 = 1.0;
469 0 : lightToTex = flip * lightToTex;
470 : }
471 :
472 0 : Cascades[cascade].TextureMatrix = lightToTex * LightTransform;
473 0 : }
474 :
475 : // Create the shadow map
476 0 : void ShadowMapInternals::CreateTexture()
477 : {
478 : // Cleanup
479 0 : Framebuffer.reset();
480 0 : Texture.reset();
481 0 : DummyTexture.reset();
482 :
483 0 : Renderer::Backend::IDevice* backendDevice = g_VideoMode.GetBackendDevice();
484 :
485 0 : CFG_GET_VAL("shadowquality", QualityLevel);
486 :
487 : // Get shadow map size as next power of two up from view width/height.
488 : int shadowMapSize;
489 0 : switch (QualityLevel)
490 : {
491 : // Low
492 0 : case -1:
493 0 : shadowMapSize = 512;
494 0 : break;
495 : // High
496 0 : case 1:
497 0 : shadowMapSize = 2048;
498 0 : break;
499 : // Ultra
500 0 : case 2:
501 0 : shadowMapSize = std::max(round_up_to_pow2(std::max(g_Renderer.GetWidth(), g_Renderer.GetHeight())), 4096);
502 0 : break;
503 : // Medium as is
504 0 : default:
505 0 : shadowMapSize = 1024;
506 0 : break;
507 : }
508 :
509 : // Clamp to the maximum texture size.
510 0 : shadowMapSize = std::min(
511 0 : shadowMapSize, static_cast<int>(backendDevice->GetCapabilities().maxTextureSize));
512 :
513 0 : Width = Height = shadowMapSize;
514 :
515 : // Since we're using a framebuffer object, the whole texture is available
516 0 : EffectiveWidth = Width;
517 0 : EffectiveHeight = Height;
518 :
519 : const char* formatName;
520 0 : Renderer::Backend::Format backendFormat = Renderer::Backend::Format::UNDEFINED;
521 : #if CONFIG2_GLES
522 : formatName = "Format::D24_UNORM";
523 : backendFormat = Renderer::Backend::Format::D24_UNORM;
524 : #else
525 0 : switch (DepthTextureBits)
526 : {
527 0 : case 16: formatName = "Format::D16_UNORM"; backendFormat = Renderer::Backend::Format::D16_UNORM; break;
528 0 : case 24: formatName = "Format::D24_UNORM"; backendFormat = Renderer::Backend::Format::D24_UNORM; break;
529 0 : case 32: formatName = "Format::D32_SFLOAT"; backendFormat = Renderer::Backend::Format::D32_SFLOAT; break;
530 0 : default:
531 0 : formatName = "Default";
532 0 : backendFormat = backendDevice->GetPreferredDepthStencilFormat(
533 : Renderer::Backend::ITexture::Usage::SAMPLED |
534 : Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
535 0 : true, false);
536 0 : break;
537 : }
538 : #endif
539 0 : ENSURE(formatName);
540 :
541 0 : LOGMESSAGE("Creating shadow texture (size %dx%d) (format = %s)",
542 : Width, Height, formatName);
543 :
544 0 : if (g_RenderingOptions.GetShadowAlphaFix())
545 : {
546 0 : DummyTexture = backendDevice->CreateTexture2D("ShadowMapDummy",
547 : Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT,
548 0 : Renderer::Backend::Format::R8G8B8A8_UNORM, Width, Height,
549 0 : Renderer::Backend::Sampler::MakeDefaultSampler(
550 : Renderer::Backend::Sampler::Filter::NEAREST,
551 0 : Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
552 : }
553 :
554 : Renderer::Backend::Sampler::Desc samplerDesc =
555 : Renderer::Backend::Sampler::MakeDefaultSampler(
556 : #if CONFIG2_GLES
557 : // GLES doesn't do depth comparisons, so treat it as a
558 : // basic unfiltered depth texture
559 : Renderer::Backend::Sampler::Filter::NEAREST,
560 : #else
561 : // Use LINEAR to trigger automatic PCF on some devices.
562 : Renderer::Backend::Sampler::Filter::LINEAR,
563 : #endif
564 0 : Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
565 : // Enable automatic depth comparisons
566 0 : samplerDesc.compareEnabled = true;
567 0 : samplerDesc.compareOp = Renderer::Backend::CompareOp::LESS_OR_EQUAL;
568 :
569 0 : Texture = backendDevice->CreateTexture2D("ShadowMapDepth",
570 : Renderer::Backend::ITexture::Usage::SAMPLED |
571 : Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
572 0 : backendFormat, Width, Height, samplerDesc);
573 :
574 0 : const bool useDummyTexture = g_RenderingOptions.GetShadowAlphaFix();
575 :
576 : // In case we used ShadowAlphaFix, we ought to clear the unused
577 : // color buffer too, else Mali 400 drivers get confused.
578 : // Might as well clear stencil too for completeness.
579 0 : Renderer::Backend::SColorAttachment colorAttachment{};
580 0 : colorAttachment.texture = DummyTexture.get();
581 0 : colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
582 0 : colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::DONT_CARE;
583 0 : colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
584 :
585 0 : Renderer::Backend::SDepthStencilAttachment depthStencilAttachment{};
586 0 : depthStencilAttachment.texture = Texture.get();
587 0 : depthStencilAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
588 0 : depthStencilAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
589 :
590 0 : Framebuffer = backendDevice->CreateFramebuffer("ShadowMapFramebuffer",
591 0 : useDummyTexture ? &colorAttachment : nullptr, &depthStencilAttachment);
592 0 : if (!Framebuffer)
593 : {
594 0 : LOGERROR("Failed to create shadows framebuffer");
595 :
596 : // Disable shadow rendering (but let the user try again if they want).
597 0 : g_RenderingOptions.SetShadows(false);
598 : }
599 0 : }
600 :
601 0 : void ShadowMap::BeginRender(
602 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
603 : {
604 0 : ENSURE(m->Framebuffer);
605 0 : deviceCommandContext->BeginFramebufferPass(m->Framebuffer.get());
606 :
607 0 : m->SavedViewCamera = g_Renderer.GetSceneRenderer().GetViewCamera();
608 0 : }
609 :
610 0 : void ShadowMap::PrepareCamera(
611 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const int cascade)
612 : {
613 0 : m->CalculateShadowMatrices(cascade);
614 :
615 0 : Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
616 0 : viewportRect.width = m->EffectiveWidth;
617 0 : viewportRect.height = m->EffectiveHeight;
618 0 : deviceCommandContext->SetViewports(1, &viewportRect);
619 :
620 0 : CCamera camera = m->SavedViewCamera;
621 0 : camera.SetProjection(m->Cascades[cascade].LightProjection);
622 0 : camera.GetOrientation() = m->InvLightTransform;
623 0 : g_Renderer.GetSceneRenderer().SetViewCamera(camera);
624 :
625 0 : const SViewPort& cascadeViewPort = m->Cascades[cascade].ViewPort;
626 : Renderer::Backend::IDeviceCommandContext::Rect scissorRect;
627 0 : scissorRect.x = cascadeViewPort.m_X;
628 0 : scissorRect.y = cascadeViewPort.m_Y;
629 0 : scissorRect.width = cascadeViewPort.m_Width;
630 0 : scissorRect.height = cascadeViewPort.m_Height;
631 0 : deviceCommandContext->SetScissors(1, &scissorRect);
632 0 : }
633 :
634 0 : void ShadowMap::EndRender(
635 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
636 : {
637 0 : deviceCommandContext->SetScissors(0, nullptr);
638 :
639 0 : deviceCommandContext->EndFramebufferPass();
640 :
641 0 : g_Renderer.GetSceneRenderer().SetViewCamera(m->SavedViewCamera);
642 0 : }
643 :
644 0 : void ShadowMap::BindTo(
645 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
646 : Renderer::Backend::IShaderProgram* shader) const
647 : {
648 0 : const int32_t shadowTexBindingSlot = shader->GetBindingSlot(str_shadowTex);
649 0 : if (shadowTexBindingSlot < 0 || !m->Texture)
650 0 : return;
651 :
652 0 : deviceCommandContext->SetTexture(shadowTexBindingSlot, m->Texture.get());
653 0 : deviceCommandContext->SetUniform(
654 0 : shader->GetBindingSlot(str_shadowScale), m->Width, m->Height, 1.0f / m->Width, 1.0f / m->Height);
655 0 : const CVector3D cameraForward = g_Renderer.GetSceneRenderer().GetCullCamera().GetOrientation().GetIn();
656 0 : deviceCommandContext->SetUniform(
657 0 : shader->GetBindingSlot(str_cameraForward), cameraForward.X, cameraForward.Y, cameraForward.Z,
658 0 : cameraForward.Dot(g_Renderer.GetSceneRenderer().GetCullCamera().GetOrientation().GetTranslation()));
659 :
660 0 : if (GetCascadeCount() == 1)
661 : {
662 0 : deviceCommandContext->SetUniform(
663 0 : shader->GetBindingSlot(str_shadowTransform),
664 0 : m->Cascades[0].TextureMatrix.AsFloatArray());
665 0 : deviceCommandContext->SetUniform(
666 0 : shader->GetBindingSlot(str_shadowDistance), m->Cascades[0].Distance);
667 : }
668 : else
669 : {
670 0 : std::vector<float> shadowDistances;
671 0 : std::vector<CMatrix3D> shadowTransforms;
672 0 : for (const ShadowMapInternals::Cascade& cascade : m->Cascades)
673 : {
674 0 : shadowDistances.emplace_back(cascade.Distance);
675 0 : shadowTransforms.emplace_back(cascade.TextureMatrix);
676 : }
677 0 : deviceCommandContext->SetUniform(
678 0 : shader->GetBindingSlot(str_shadowTransform),
679 : PS::span<const float>(
680 0 : shadowTransforms[0]._data,
681 0 : shadowTransforms[0].AsFloatArray().size() * GetCascadeCount()));
682 0 : deviceCommandContext->SetUniform(
683 0 : shader->GetBindingSlot(str_shadowDistance),
684 0 : PS::span<const float>(shadowDistances.data(), shadowDistances.size()));
685 : }
686 : }
687 :
688 : // Depth texture bits
689 0 : int ShadowMap::GetDepthTextureBits() const
690 : {
691 0 : return m->DepthTextureBits;
692 : }
693 :
694 0 : void ShadowMap::SetDepthTextureBits(int bits)
695 : {
696 0 : if (bits != m->DepthTextureBits)
697 : {
698 0 : m->Texture.reset();
699 0 : m->Width = m->Height = 0;
700 :
701 0 : m->DepthTextureBits = bits;
702 : }
703 0 : }
704 :
705 0 : void ShadowMap::RenderDebugBounds()
706 : {
707 : // Render various shadow bounds:
708 : // Yellow = bounds of objects in view frustum that receive shadows
709 : // Red = culling frustum used to find potential shadow casters
710 : // Blue = frustum used for rendering the shadow map
711 :
712 0 : const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection() * m->InvLightTransform;
713 :
714 0 : g_Renderer.GetDebugRenderer().DrawBoundingBox(
715 0 : m->ShadowReceiverBound, CColor(1.0f, 1.0f, 0.0f, 1.0f), transform, true);
716 :
717 0 : for (int cascade = 0; cascade < GetCascadeCount(); ++cascade)
718 : {
719 0 : g_Renderer.GetDebugRenderer().DrawBoundingBox(
720 0 : m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.10f), transform);
721 0 : g_Renderer.GetDebugRenderer().DrawBoundingBox(
722 0 : m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.5f), transform, true);
723 :
724 0 : const CFrustum frustum = GetShadowCasterCullFrustum(cascade);
725 : // We don't have a function to create a brush directly from a frustum, so use
726 : // the ugly approach of creating a large cube and then intersecting with the frustum
727 0 : const CBoundingBoxAligned dummy(CVector3D(-1e4, -1e4, -1e4), CVector3D(1e4, 1e4, 1e4));
728 0 : CBrush brush(dummy);
729 0 : CBrush frustumBrush;
730 0 : brush.Intersect(frustum, frustumBrush);
731 :
732 0 : g_Renderer.GetDebugRenderer().DrawBrush(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.1f));
733 0 : g_Renderer.GetDebugRenderer().DrawBrush(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.1f), true);
734 : }
735 0 : }
736 :
737 0 : int ShadowMap::GetCascadeCount() const
738 : {
739 : #if CONFIG2_GLES
740 : return 1;
741 : #else
742 0 : return m->ShadowsCoverMap ? 1 : m->CascadeCount;
743 : #endif
744 3 : }
|