Merge 'remotes/trunk'
[0ad.git] / source / renderer / ShadowMap.cpp
blob1e5cb4d06bfe441d1bc7eab32fc85432b9099003
1 /* Copyright (C) 2023 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "ShadowMap.h"
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 "renderer/backend/IDevice.h"
36 #include "renderer/backend/ITexture.h"
37 #include "renderer/DebugRenderer.h"
38 #include "renderer/Renderer.h"
39 #include "renderer/RenderingOptions.h"
40 #include "renderer/SceneRenderer.h"
42 #include <array>
44 namespace
47 constexpr int MAX_CASCADE_COUNT = 4;
49 constexpr float DEFAULT_SHADOWS_CUTOFF_DISTANCE = 300.0f;
50 constexpr float DEFAULT_CASCADE_DISTANCE_RATIO = 1.7f;
52 } // anonymous namespace
54 /**
55 * Struct ShadowMapInternals: Internal data for the ShadowMap implementation
57 struct ShadowMapInternals
59 Renderer::Backend::IDevice* Device = nullptr;
61 std::unique_ptr<Renderer::Backend::IFramebuffer> Framebuffer;
62 std::unique_ptr<Renderer::Backend::ITexture> Texture;
64 // bit depth for the depth texture
65 int DepthTextureBits;
66 // width, height of shadow map
67 int Width, Height;
68 // Shadow map quality (-1 - Low, 0 - Medium, 1 - High, 2 - Very High)
69 int QualityLevel;
70 // used width, height of shadow map
71 int EffectiveWidth, EffectiveHeight;
73 // Transform world space into light space; calculated on SetupFrame
74 CMatrix3D LightTransform;
76 // transform light space into world space
77 CMatrix3D InvLightTransform;
78 CBoundingBoxAligned ShadowReceiverBound;
80 int CascadeCount;
81 float CascadeDistanceRatio;
82 float ShadowsCutoffDistance;
83 bool ShadowsCoverMap;
85 struct Cascade
87 // transform light space into projected light space
88 // in projected light space, the shadowbound box occupies the [-1..1] cube
89 // calculated on BeginRender, after the final shadow bounds are known
90 CMatrix3D LightProjection;
91 float Distance;
92 CBoundingBoxAligned FrustumBBAA;
93 CBoundingBoxAligned ConvexBounds;
94 CBoundingBoxAligned ShadowRenderBound;
95 // Bounding box of shadowed objects in the light space.
96 CBoundingBoxAligned ShadowCasterBound;
97 // Transform world space into texture space of the shadow map;
98 // calculated on BeginRender, after the final shadow bounds are known
99 CMatrix3D TextureMatrix;
100 // View port of the shadow texture where the cascade should be rendered.
101 SViewPort ViewPort;
103 std::array<Cascade, MAX_CASCADE_COUNT> Cascades;
105 // Camera transformed into light space
106 CCamera LightspaceCamera;
108 // Some drivers (at least some Intel Mesa ones) appear to handle alpha testing
109 // incorrectly when the FBO has only a depth attachment.
110 // When m_ShadowAlphaFix is true, we use DummyTexture to store a useless
111 // alpha texture which is attached to the FBO as a workaround.
112 std::unique_ptr<Renderer::Backend::ITexture> DummyTexture;
114 // Copy of renderer's standard view camera, saved between
115 // BeginRender and EndRender while we replace it with the shadow camera
116 CCamera SavedViewCamera;
118 void CalculateShadowMatrices(const int cascade);
119 void CreateTexture();
120 void UpdateCascadesParameters();
123 void ShadowMapInternals::UpdateCascadesParameters()
125 CascadeCount = 1;
126 CFG_GET_VAL("shadowscascadecount", CascadeCount);
128 if (CascadeCount < 1 || CascadeCount > MAX_CASCADE_COUNT || Device->GetBackend() == Renderer::Backend::Backend::GL_ARB)
129 CascadeCount = 1;
131 ShadowsCoverMap = false;
132 CFG_GET_VAL("shadowscovermap", ShadowsCoverMap);
135 void CalculateBoundsForCascade(
136 const CCamera& camera, const CMatrix3D& lightTransform,
137 const float nearPlane, const float farPlane, CBoundingBoxAligned* bbaa,
138 CBoundingBoxAligned* frustumBBAA)
140 frustumBBAA->SetEmpty();
142 // We need to calculate a circumscribed sphere for the camera to
143 // create a rotation stable bounding box.
144 const CVector3D cameraIn = camera.m_Orientation.GetIn();
145 const CVector3D cameraTranslation = camera.m_Orientation.GetTranslation();
146 const CVector3D centerNear = cameraTranslation + cameraIn * nearPlane;
147 const CVector3D centerDist = cameraTranslation + cameraIn * farPlane;
149 // We can solve 3D problem in 2D space, because the frustum is
150 // symmetric by 2 planes. Than means we can use only one corner
151 // to find a circumscribed sphere.
152 CCamera::Quad corners;
154 camera.GetViewQuad(nearPlane, corners);
155 for (CVector3D& corner : corners)
156 corner = camera.GetOrientation().Transform(corner);
157 const CVector3D cornerNear = corners[0];
158 for (const CVector3D& corner : corners)
159 *frustumBBAA += lightTransform.Transform(corner);
161 camera.GetViewQuad(farPlane, corners);
162 for (CVector3D& corner : corners)
163 corner = camera.GetOrientation().Transform(corner);
164 const CVector3D cornerDist = corners[0];
165 for (const CVector3D& corner : corners)
166 *frustumBBAA += lightTransform.Transform(corner);
168 // We solve 2D case for the right trapezoid.
169 const float firstBase = (cornerNear - centerNear).Length();
170 const float secondBase = (cornerDist - centerDist).Length();
171 const float height = (centerDist - centerNear).Length();
172 const float distanceToCenter =
173 (height * height + secondBase * secondBase - firstBase * firstBase) * 0.5f / height;
175 CVector3D position = cameraTranslation + cameraIn * (nearPlane + distanceToCenter);
176 const float radius = (cornerNear - position).Length();
178 // We need to convert the bounding box to the light space.
179 position = lightTransform.Rotate(position);
181 const float insets = 0.2f;
182 *bbaa = CBoundingBoxAligned(position, position);
183 bbaa->Expand(radius);
184 bbaa->Expand(insets);
187 ShadowMap::ShadowMap(Renderer::Backend::IDevice* device)
189 m = new ShadowMapInternals;
190 m->Device = device;
191 m->Framebuffer = 0;
192 m->Width = 0;
193 m->Height = 0;
194 m->QualityLevel = 0;
195 m->EffectiveWidth = 0;
196 m->EffectiveHeight = 0;
197 m->DepthTextureBits = 0;
198 // DepthTextureBits: 24/32 are very much faster than 16, on GeForce 4 and FX;
199 // but they're very much slower on Radeon 9800.
200 // In both cases, the default (no specified depth) is fast, so we just use
201 // that by default and hope it's alright. (Otherwise, we'd probably need to
202 // do some kind of hardware detection to work out what to use.)
204 // Avoid using uninitialised values in AddShadowedBound if SetupFrame wasn't called first
205 m->LightTransform.SetIdentity();
207 m->UpdateCascadesParameters();
210 ShadowMap::~ShadowMap()
212 m->Framebuffer.reset();
213 m->Texture.reset();
214 m->DummyTexture.reset();
216 delete m;
219 // Force the texture/buffer/etc to be recreated, particularly when the renderer's
220 // size has changed
221 void ShadowMap::RecreateTexture()
223 m->Framebuffer.reset();
224 m->Texture.reset();
225 m->DummyTexture.reset();
227 m->UpdateCascadesParameters();
229 // (Texture will be constructed in next SetupFrame)
232 // SetupFrame: camera and light direction for this frame
233 void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir)
235 if (!m->Texture)
236 m->CreateTexture();
238 CVector3D x(0, 1, 0), eyepos;
240 CVector3D z = lightdir;
241 z.Normalize();
242 x -= z * z.Dot(x);
243 if (x.Length() < 0.001)
245 // this is invoked if the camera and light directions almost coincide
246 // assumption: light direction has a significant Z component
247 x = CVector3D(1.0, 0.0, 0.0);
248 x -= z * z.Dot(x);
250 x.Normalize();
251 CVector3D y = z.Cross(x);
253 // X axis perpendicular to light direction, flowing along with view direction
254 m->LightTransform._11 = x.X;
255 m->LightTransform._12 = x.Y;
256 m->LightTransform._13 = x.Z;
258 // Y axis perpendicular to light and view direction
259 m->LightTransform._21 = y.X;
260 m->LightTransform._22 = y.Y;
261 m->LightTransform._23 = y.Z;
263 // Z axis is in direction of light
264 m->LightTransform._31 = z.X;
265 m->LightTransform._32 = z.Y;
266 m->LightTransform._33 = z.Z;
268 // eye is at the origin of the coordinate system
269 m->LightTransform._14 = -x.Dot(eyepos);
270 m->LightTransform._24 = -y.Dot(eyepos);
271 m->LightTransform._34 = -z.Dot(eyepos);
273 m->LightTransform._41 = 0.0;
274 m->LightTransform._42 = 0.0;
275 m->LightTransform._43 = 0.0;
276 m->LightTransform._44 = 1.0;
278 m->LightTransform.GetInverse(m->InvLightTransform);
279 m->ShadowReceiverBound.SetEmpty();
281 m->LightspaceCamera = camera;
282 m->LightspaceCamera.m_Orientation = m->LightTransform * camera.m_Orientation;
283 m->LightspaceCamera.UpdateFrustum();
285 m->ShadowsCutoffDistance = DEFAULT_SHADOWS_CUTOFF_DISTANCE;
286 m->CascadeDistanceRatio = DEFAULT_CASCADE_DISTANCE_RATIO;
287 CFG_GET_VAL("shadowscutoffdistance", m->ShadowsCutoffDistance);
288 CFG_GET_VAL("shadowscascadedistanceratio", m->CascadeDistanceRatio);
289 m->CascadeDistanceRatio = Clamp(m->CascadeDistanceRatio, 1.1f, 16.0f);
291 m->Cascades[GetCascadeCount() - 1].Distance = m->ShadowsCutoffDistance;
292 for (int cascade = GetCascadeCount() - 2; cascade >= 0; --cascade)
293 m->Cascades[cascade].Distance = m->Cascades[cascade + 1].Distance / m->CascadeDistanceRatio;
295 if (GetCascadeCount() == 1 || m->ShadowsCoverMap)
297 m->Cascades[0].ViewPort =
298 SViewPort{1, 1, m->EffectiveWidth - 2, m->EffectiveHeight - 2};
299 if (m->ShadowsCoverMap)
300 m->Cascades[0].Distance = camera.GetFarPlane();
302 else
304 for (int cascade = 0; cascade < GetCascadeCount(); ++cascade)
306 const int offsetX = (cascade & 0x1) ? m->EffectiveWidth / 2 : 0;
307 const int offsetY = (cascade & 0x2) ? m->EffectiveHeight / 2 : 0;
308 m->Cascades[cascade].ViewPort =
309 SViewPort{offsetX + 1, offsetY + 1,
310 m->EffectiveWidth / 2 - 2, m->EffectiveHeight / 2 - 2};
314 for (int cascadeIdx = 0; cascadeIdx < GetCascadeCount(); ++cascadeIdx)
316 ShadowMapInternals::Cascade& cascade = m->Cascades[cascadeIdx];
318 const float nearPlane = cascadeIdx > 0 ?
319 m->Cascades[cascadeIdx - 1].Distance : camera.GetNearPlane();
320 const float farPlane = cascade.Distance;
322 CalculateBoundsForCascade(camera, m->LightTransform,
323 nearPlane, farPlane, &cascade.ConvexBounds, &cascade.FrustumBBAA);
324 cascade.ShadowCasterBound.SetEmpty();
328 // AddShadowedBound: add a world-space bounding box to the bounds of shadowed
329 // objects
330 void ShadowMap::AddShadowCasterBound(const int cascade, const CBoundingBoxAligned& bounds)
332 CBoundingBoxAligned lightspacebounds;
334 bounds.Transform(m->LightTransform, lightspacebounds);
335 m->Cascades[cascade].ShadowCasterBound += lightspacebounds;
338 void ShadowMap::AddShadowReceiverBound(const CBoundingBoxAligned& bounds)
340 CBoundingBoxAligned lightspacebounds;
342 bounds.Transform(m->LightTransform, lightspacebounds);
343 m->ShadowReceiverBound += lightspacebounds;
346 CFrustum ShadowMap::GetShadowCasterCullFrustum(const int cascade)
348 // Get the bounds of all objects that can receive shadows
349 CBoundingBoxAligned bound = m->ShadowReceiverBound;
351 // Intersect with the camera frustum, so the shadow map doesn't have to get
352 // stretched to cover the off-screen parts of large models
353 bound.IntersectFrustumConservative(m->Cascades[cascade].FrustumBBAA.ToFrustum());
355 // ShadowBound might have been empty to begin with, producing an empty result
356 if (bound.IsEmpty())
358 // CFrustum can't easily represent nothingness, so approximate it with
359 // a single point which won't match many objects
360 bound += CVector3D(0.0f, 0.0f, 0.0f);
361 return bound.ToFrustum();
364 // Extend the bounds a long way towards the light source, to encompass
365 // all objects that might cast visible shadows.
366 // (The exact constant was picked entirely arbitrarily.)
367 bound[0].Z -= 1000.f;
369 CFrustum frustum = bound.ToFrustum();
370 frustum.Transform(m->InvLightTransform);
371 return frustum;
374 // CalculateShadowMatrices: calculate required matrices for shadow map generation - the light's
375 // projection and transformation matrices
376 void ShadowMapInternals::CalculateShadowMatrices(const int cascade)
378 CBoundingBoxAligned& shadowRenderBound = Cascades[cascade].ShadowRenderBound;
379 shadowRenderBound = Cascades[cascade].ConvexBounds;
381 if (ShadowsCoverMap)
383 // Start building the shadow map to cover all objects that will receive shadows
384 CBoundingBoxAligned receiverBound = ShadowReceiverBound;
386 // Intersect with the camera frustum, so the shadow map doesn't have to get
387 // stretched to cover the off-screen parts of large models
388 receiverBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum());
390 // Intersect with the shadow caster bounds, because there's no point
391 // wasting space around the edges of the shadow map that we're not going
392 // to draw into
393 shadowRenderBound[0].X = std::max(receiverBound[0].X, Cascades[cascade].ShadowCasterBound[0].X);
394 shadowRenderBound[0].Y = std::max(receiverBound[0].Y, Cascades[cascade].ShadowCasterBound[0].Y);
395 shadowRenderBound[1].X = std::min(receiverBound[1].X, Cascades[cascade].ShadowCasterBound[1].X);
396 shadowRenderBound[1].Y = std::min(receiverBound[1].Y, Cascades[cascade].ShadowCasterBound[1].Y);
398 else if (CascadeCount > 1)
400 // We need to offset the cascade to its place on the texture.
401 const CVector3D size = (shadowRenderBound[1] - shadowRenderBound[0]) * 0.5f;
402 if (!(cascade & 0x1))
403 shadowRenderBound[1].X += size.X * 2.0f;
404 else
405 shadowRenderBound[0].X -= size.X * 2.0f;
406 if (!(cascade & 0x2))
407 shadowRenderBound[1].Y += size.Y * 2.0f;
408 else
409 shadowRenderBound[0].Y -= size.Y * 2.0f;
412 // Set the near and far planes to include just the shadow casters,
413 // so we make full use of the depth texture's range. Add a bit of a
414 // delta so we don't accidentally clip objects that are directly on
415 // the planes.
416 shadowRenderBound[0].Z = Cascades[cascade].ShadowCasterBound[0].Z - 2.f;
417 shadowRenderBound[1].Z = Cascades[cascade].ShadowCasterBound[1].Z + 2.f;
419 // Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering
420 CVector3D scale = shadowRenderBound[1] - shadowRenderBound[0];
421 CVector3D shift = (shadowRenderBound[1] + shadowRenderBound[0]) * -0.5;
423 if (scale.X < 1.0)
424 scale.X = 1.0;
425 if (scale.Y < 1.0)
426 scale.Y = 1.0;
427 if (scale.Z < 1.0)
428 scale.Z = 1.0;
430 scale.X = 2.0 / scale.X;
431 scale.Y = 2.0 / scale.Y;
432 scale.Z = 2.0 / scale.Z;
434 // make sure a given world position falls on a consistent shadowmap texel fractional offset
435 float offsetX = fmod(shadowRenderBound[0].X - LightTransform._14, 2.0f/(scale.X*EffectiveWidth));
436 float offsetY = fmod(shadowRenderBound[0].Y - LightTransform._24, 2.0f/(scale.Y*EffectiveHeight));
438 CMatrix3D& lightProjection = Cascades[cascade].LightProjection;
439 lightProjection.SetZero();
440 lightProjection._11 = scale.X;
441 lightProjection._14 = (shift.X + offsetX) * scale.X;
442 lightProjection._22 = scale.Y;
443 lightProjection._24 = (shift.Y + offsetY) * scale.Y;
444 lightProjection._33 = scale.Z;
445 lightProjection._34 = shift.Z * scale.Z;
446 lightProjection._44 = 1.0;
448 // Calculate texture matrix by creating the clip space to texture coordinate matrix
449 // and then concatenating all matrices that have been calculated so far
451 float texscalex = scale.X * 0.5f * (float)EffectiveWidth / (float)Width;
452 float texscaley = scale.Y * 0.5f * (float)EffectiveHeight / (float)Height;
453 float texscalez = scale.Z * 0.5f;
455 CMatrix3D lightToTex;
456 lightToTex.SetZero();
457 lightToTex._11 = texscalex;
458 lightToTex._14 = (offsetX - shadowRenderBound[0].X) * texscalex;
459 lightToTex._22 = texscaley;
460 lightToTex._24 = (offsetY - shadowRenderBound[0].Y) * texscaley;
461 lightToTex._33 = texscalez;
462 lightToTex._34 = -shadowRenderBound[0].Z * texscalez;
463 lightToTex._44 = 1.0;
465 if (Device->GetBackend() == Renderer::Backend::Backend::VULKAN)
467 CMatrix3D flip;
468 flip.SetIdentity();
469 flip._22 = -1.0f;
470 flip._24 = 1.0;
471 lightToTex = flip * lightToTex;
474 Cascades[cascade].TextureMatrix = lightToTex * LightTransform;
477 // Create the shadow map
478 void ShadowMapInternals::CreateTexture()
480 // Cleanup
481 Framebuffer.reset();
482 Texture.reset();
483 DummyTexture.reset();
485 CFG_GET_VAL("shadowquality", QualityLevel);
487 // Get shadow map size as next power of two up from view width/height.
488 int shadowMapSize;
489 switch (QualityLevel)
491 // Low
492 case -1:
493 shadowMapSize = 512;
494 break;
495 // High
496 case 1:
497 shadowMapSize = 2048;
498 break;
499 // Ultra
500 case 2:
501 shadowMapSize = std::max(round_up_to_pow2(std::max(g_Renderer.GetWidth(), g_Renderer.GetHeight())), 4096);
502 break;
503 // Medium as is
504 default:
505 shadowMapSize = 1024;
506 break;
509 // Clamp to the maximum texture size.
510 shadowMapSize = std::min(
511 shadowMapSize, static_cast<int>(Device->GetCapabilities().maxTextureSize));
513 Width = Height = shadowMapSize;
515 // Since we're using a framebuffer object, the whole texture is available
516 EffectiveWidth = Width;
517 EffectiveHeight = Height;
519 const char* formatName;
520 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 switch (DepthTextureBits)
527 case 16: formatName = "Format::D16_UNORM"; backendFormat = Renderer::Backend::Format::D16_UNORM; break;
528 case 24: formatName = "Format::D24_UNORM"; backendFormat = Renderer::Backend::Format::D24_UNORM; break;
529 case 32: formatName = "Format::D32_SFLOAT"; backendFormat = Renderer::Backend::Format::D32_SFLOAT; break;
530 default:
531 formatName = "Default";
532 backendFormat = Device->GetPreferredDepthStencilFormat(
533 Renderer::Backend::ITexture::Usage::SAMPLED |
534 Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
535 true, false);
536 break;
538 #endif
539 ENSURE(formatName);
541 LOGMESSAGE("Creating shadow texture (size %dx%d) (format = %s)",
542 Width, Height, formatName);
544 if (g_RenderingOptions.GetShadowAlphaFix())
546 DummyTexture = Device->CreateTexture2D("ShadowMapDummy",
547 Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT,
548 Renderer::Backend::Format::R8G8B8A8_UNORM, Width, Height,
549 Renderer::Backend::Sampler::MakeDefaultSampler(
550 Renderer::Backend::Sampler::Filter::NEAREST,
551 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
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 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
565 // Enable automatic depth comparisons
566 samplerDesc.compareEnabled = true;
567 samplerDesc.compareOp = Renderer::Backend::CompareOp::LESS_OR_EQUAL;
569 Texture = Device->CreateTexture2D("ShadowMapDepth",
570 Renderer::Backend::ITexture::Usage::SAMPLED |
571 Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
572 backendFormat, Width, Height, samplerDesc);
574 const bool useDummyTexture = g_RenderingOptions.GetShadowAlphaFix();
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 Renderer::Backend::SColorAttachment colorAttachment{};
580 colorAttachment.texture = DummyTexture.get();
581 colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
582 colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::DONT_CARE;
583 colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
585 Renderer::Backend::SDepthStencilAttachment depthStencilAttachment{};
586 depthStencilAttachment.texture = Texture.get();
587 depthStencilAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
588 depthStencilAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
590 Framebuffer = Device->CreateFramebuffer("ShadowMapFramebuffer",
591 useDummyTexture ? &colorAttachment : nullptr, &depthStencilAttachment);
592 if (!Framebuffer)
594 LOGERROR("Failed to create shadows framebuffer");
596 // Disable shadow rendering (but let the user try again if they want).
597 g_RenderingOptions.SetShadows(false);
601 void ShadowMap::BeginRender(
602 Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
604 ENSURE(m->Framebuffer);
605 deviceCommandContext->BeginFramebufferPass(m->Framebuffer.get());
607 m->SavedViewCamera = g_Renderer.GetSceneRenderer().GetViewCamera();
610 void ShadowMap::PrepareCamera(
611 Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const int cascade)
613 m->CalculateShadowMatrices(cascade);
615 Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
616 viewportRect.width = m->EffectiveWidth;
617 viewportRect.height = m->EffectiveHeight;
618 deviceCommandContext->SetViewports(1, &viewportRect);
620 CCamera camera = m->SavedViewCamera;
621 camera.SetProjection(m->Cascades[cascade].LightProjection);
622 camera.GetOrientation() = m->InvLightTransform;
623 camera.UpdateFrustum();
624 g_Renderer.GetSceneRenderer().SetViewCamera(camera);
626 const SViewPort& cascadeViewPort = m->Cascades[cascade].ViewPort;
627 Renderer::Backend::IDeviceCommandContext::Rect scissorRect;
628 scissorRect.x = cascadeViewPort.m_X;
629 scissorRect.y = cascadeViewPort.m_Y;
630 scissorRect.width = cascadeViewPort.m_Width;
631 scissorRect.height = cascadeViewPort.m_Height;
632 deviceCommandContext->SetScissors(1, &scissorRect);
635 void ShadowMap::EndRender(
636 Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
638 deviceCommandContext->SetScissors(0, nullptr);
640 deviceCommandContext->EndFramebufferPass();
642 g_Renderer.GetSceneRenderer().SetViewCamera(m->SavedViewCamera);
645 void ShadowMap::BindTo(
646 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
647 Renderer::Backend::IShaderProgram* shader) const
649 const int32_t shadowTexBindingSlot = shader->GetBindingSlot(str_shadowTex);
650 if (shadowTexBindingSlot < 0 || !m->Texture)
651 return;
653 deviceCommandContext->SetTexture(shadowTexBindingSlot, m->Texture.get());
654 deviceCommandContext->SetUniform(
655 shader->GetBindingSlot(str_shadowScale), m->Width, m->Height, 1.0f / m->Width, 1.0f / m->Height);
656 const CVector3D cameraForward = g_Renderer.GetSceneRenderer().GetCullCamera().GetOrientation().GetIn();
657 deviceCommandContext->SetUniform(
658 shader->GetBindingSlot(str_cameraForward), cameraForward.X, cameraForward.Y, cameraForward.Z,
659 cameraForward.Dot(g_Renderer.GetSceneRenderer().GetCullCamera().GetOrientation().GetTranslation()));
661 if (GetCascadeCount() == 1)
663 deviceCommandContext->SetUniform(
664 shader->GetBindingSlot(str_shadowTransform),
665 m->Cascades[0].TextureMatrix.AsFloatArray());
666 deviceCommandContext->SetUniform(
667 shader->GetBindingSlot(str_shadowDistance), m->Cascades[0].Distance);
669 else
671 std::vector<float> shadowDistances;
672 std::vector<CMatrix3D> shadowTransforms;
673 for (const ShadowMapInternals::Cascade& cascade : m->Cascades)
675 shadowDistances.emplace_back(cascade.Distance);
676 shadowTransforms.emplace_back(cascade.TextureMatrix);
678 deviceCommandContext->SetUniform(
679 shader->GetBindingSlot(str_shadowTransform),
680 PS::span<const float>(
681 shadowTransforms[0]._data,
682 shadowTransforms[0].AsFloatArray().size() * GetCascadeCount()));
683 deviceCommandContext->SetUniform(
684 shader->GetBindingSlot(str_shadowDistance),
685 PS::span<const float>(shadowDistances.data(), shadowDistances.size()));
689 // Depth texture bits
690 int ShadowMap::GetDepthTextureBits() const
692 return m->DepthTextureBits;
695 void ShadowMap::SetDepthTextureBits(int bits)
697 if (bits != m->DepthTextureBits)
699 m->Texture.reset();
700 m->Width = m->Height = 0;
702 m->DepthTextureBits = bits;
706 void ShadowMap::RenderDebugBounds()
708 // Render various shadow bounds:
709 // Yellow = bounds of objects in view frustum that receive shadows
710 // Red = culling frustum used to find potential shadow casters
711 // Blue = frustum used for rendering the shadow map
713 const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection() * m->InvLightTransform;
715 g_Renderer.GetDebugRenderer().DrawBoundingBox(
716 m->ShadowReceiverBound, CColor(1.0f, 1.0f, 0.0f, 1.0f), transform, true);
718 for (int cascade = 0; cascade < GetCascadeCount(); ++cascade)
720 g_Renderer.GetDebugRenderer().DrawBoundingBox(
721 m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.10f), transform);
722 g_Renderer.GetDebugRenderer().DrawBoundingBox(
723 m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.5f), transform, true);
725 const CFrustum frustum = GetShadowCasterCullFrustum(cascade);
726 // We don't have a function to create a brush directly from a frustum, so use
727 // the ugly approach of creating a large cube and then intersecting with the frustum
728 const CBoundingBoxAligned dummy(CVector3D(-1e4, -1e4, -1e4), CVector3D(1e4, 1e4, 1e4));
729 CBrush brush(dummy);
730 CBrush frustumBrush;
731 brush.Intersect(frustum, frustumBrush);
733 g_Renderer.GetDebugRenderer().DrawBrush(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.1f));
734 g_Renderer.GetDebugRenderer().DrawBrush(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.1f), true);
738 int ShadowMap::GetCascadeCount() const
740 #if CONFIG2_GLES
741 return 1;
742 #else
743 return m->ShadowsCoverMap ? 1 : m->CascadeCount;
744 #endif