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"
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"
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
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
66 // width, height of shadow map
68 // Shadow map quality (-1 - Low, 0 - Medium, 1 - High, 2 - Very High)
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
;
81 float CascadeDistanceRatio
;
82 float ShadowsCutoffDistance
;
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
;
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.
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()
126 CFG_GET_VAL("shadowscascadecount", CascadeCount
);
128 if (CascadeCount
< 1 || CascadeCount
> MAX_CASCADE_COUNT
|| Device
->GetBackend() == Renderer::Backend::Backend::GL_ARB
)
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
;
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();
214 m
->DummyTexture
.reset();
219 // Force the texture/buffer/etc to be recreated, particularly when the renderer's
221 void ShadowMap::RecreateTexture()
223 m
->Framebuffer
.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
)
238 CVector3D
x(0, 1, 0), eyepos
;
240 CVector3D z
= lightdir
;
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);
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();
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
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
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
);
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
;
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
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
;
405 shadowRenderBound
[0].X
-= size
.X
* 2.0f
;
406 if (!(cascade
& 0x2))
407 shadowRenderBound
[1].Y
+= size
.Y
* 2.0f
;
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
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;
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
)
471 lightToTex
= flip
* lightToTex
;
474 Cascades
[cascade
].TextureMatrix
= lightToTex
* LightTransform
;
477 // Create the shadow map
478 void ShadowMapInternals::CreateTexture()
483 DummyTexture
.reset();
485 CFG_GET_VAL("shadowquality", QualityLevel
);
487 // Get shadow map size as next power of two up from view width/height.
489 switch (QualityLevel
)
497 shadowMapSize
= 2048;
501 shadowMapSize
= std::max(round_up_to_pow2(std::max(g_Renderer
.GetWidth(), g_Renderer
.GetHeight())), 4096);
505 shadowMapSize
= 1024;
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
;
522 formatName
= "Format::D24_UNORM";
523 backendFormat
= Renderer::Backend::Format::D24_UNORM
;
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;
531 formatName
= "Default";
532 backendFormat
= Device
->GetPreferredDepthStencilFormat(
533 Renderer::Backend::ITexture::Usage::SAMPLED
|
534 Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT
,
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(
557 // GLES doesn't do depth comparisons, so treat it as a
558 // basic unfiltered depth texture
559 Renderer::Backend::Sampler::Filter::NEAREST
,
561 // Use LINEAR to trigger automatic PCF on some devices.
562 Renderer::Backend::Sampler::Filter::LINEAR
,
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
);
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
)
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
);
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
)
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
));
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
743 return m
->ShadowsCoverMap
? 1 : m
->CascadeCount
;