1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "BsShadowRendering.h"
4#include "BsRendererView.h"
5#include "BsRendererScene.h"
6#include "Renderer/BsLight.h"
7#include "Renderer/BsRendererUtility.h"
8#include "Material/BsGpuParamsSet.h"
9#include "Mesh/BsMesh.h"
10#include "Renderer/BsCamera.h"
11#include "Utility/BsBitwise.h"
12#include "RenderAPI/BsVertexDataDesc.h"
13#include "Renderer/BsRenderer.h"
14#include "BsRendererRenderable.h"
15
16namespace bs { namespace ct
17{
18 ShadowParamsDef gShadowParamsDef;
19
20 void ShadowDepthNormalMat::bind(const SPtr<GpuParamBlockBuffer>& shadowParams)
21 {
22 mParams->setParamBlockBuffer("ShadowParams", shadowParams);
23
24 RenderAPI::instance().setGraphicsPipeline(mGfxPipeline);
25 RenderAPI::instance().setStencilRef(mStencilRef);
26 }
27
28 void ShadowDepthNormalMat::setPerObjectBuffer(const SPtr<GpuParamBlockBuffer>& perObjectParams)
29 {
30 mParams->setParamBlockBuffer("PerObject", perObjectParams);
31
32 RenderAPI::instance().setGpuParams(mParams);
33 }
34
35 ShadowDepthNormalMat* ShadowDepthNormalMat::getVariation(bool skinned, bool morph)
36 {
37 if(skinned)
38 {
39 if(morph)
40 return get(getVariation<true, true>());
41
42 return get(getVariation<true, false>());
43 }
44 else
45 {
46 if(morph)
47 return get(getVariation<false, true>());
48
49 return get(getVariation<false, false>());
50 }
51 }
52
53 ShadowDepthNormalNoPSMat::ShadowDepthNormalNoPSMat()
54 { }
55
56 void ShadowDepthNormalNoPSMat::bind(const SPtr<GpuParamBlockBuffer>& shadowParams)
57 {
58 mParams->setParamBlockBuffer("ShadowParams", shadowParams);
59
60 RenderAPI::instance().setGraphicsPipeline(mGfxPipeline);
61 RenderAPI::instance().setStencilRef(mStencilRef);
62 }
63
64 void ShadowDepthNormalNoPSMat::setPerObjectBuffer(const SPtr<GpuParamBlockBuffer>& perObjectParams)
65 {
66 mParams->setParamBlockBuffer("PerObject", perObjectParams);
67
68 RenderAPI::instance().setGpuParams(mParams);
69 }
70
71 ShadowDepthNormalNoPSMat* ShadowDepthNormalNoPSMat::getVariation(bool skinned, bool morph)
72 {
73 if(skinned)
74 {
75 if(morph)
76 return get(getVariation<true, true>());
77
78 return get(getVariation<true, false>());
79 }
80 else
81 {
82 if(morph)
83 return get(getVariation<false, true>());
84
85 return get(getVariation<false, false>());
86 }
87 }
88
89 ShadowDepthDirectionalMat::ShadowDepthDirectionalMat()
90 { }
91
92 void ShadowDepthDirectionalMat::bind(const SPtr<GpuParamBlockBuffer>& shadowParams)
93 {
94 mParams->setParamBlockBuffer("ShadowParams", shadowParams);
95
96 RenderAPI::instance().setGraphicsPipeline(mGfxPipeline);
97 RenderAPI::instance().setStencilRef(mStencilRef);
98 }
99
100 void ShadowDepthDirectionalMat::setPerObjectBuffer(const SPtr<GpuParamBlockBuffer>& perObjectParams)
101 {
102 mParams->setParamBlockBuffer("PerObject", perObjectParams);
103 RenderAPI::instance().setGpuParams(mParams);
104 }
105
106 ShadowDepthDirectionalMat* ShadowDepthDirectionalMat::getVariation(bool skinned, bool morph)
107 {
108 if(skinned)
109 {
110 if(morph)
111 return get(getVariation<true, true>());
112
113 return get(getVariation<true, false>());
114 }
115 else
116 {
117 if(morph)
118 return get(getVariation<false, true>());
119
120 return get(getVariation<false, false>());
121 }
122 }
123
124 ShadowCubeMatricesDef gShadowCubeMatricesDef;
125 ShadowCubeMasksDef gShadowCubeMasksDef;
126
127 ShadowDepthCubeMat::ShadowDepthCubeMat()
128 { }
129
130 void ShadowDepthCubeMat::bind(const SPtr<GpuParamBlockBuffer>& shadowParams,
131 const SPtr<GpuParamBlockBuffer>& shadowCubeMatrices)
132 {
133 mParams->setParamBlockBuffer("ShadowParams", shadowParams);
134 mParams->setParamBlockBuffer("ShadowCubeMatrices", shadowCubeMatrices);
135
136 RenderAPI::instance().setGraphicsPipeline(mGfxPipeline);
137 RenderAPI::instance().setStencilRef(mStencilRef);
138 }
139
140 void ShadowDepthCubeMat::setPerObjectBuffer(const SPtr<GpuParamBlockBuffer>& perObjectParams,
141 const SPtr<GpuParamBlockBuffer>& shadowCubeMasks)
142 {
143 mParams->setParamBlockBuffer("PerObject", perObjectParams);
144 mParams->setParamBlockBuffer("ShadowCubeMasks", shadowCubeMasks);
145
146 RenderAPI::instance().setGpuParams(mParams);
147 }
148
149 ShadowDepthCubeMat* ShadowDepthCubeMat::getVariation(bool skinned, bool morph)
150 {
151 if(skinned)
152 {
153 if(morph)
154 return get(getVariation<true, true>());
155
156 return get(getVariation<true, false>());
157 }
158 else
159 {
160 if(morph)
161 return get(getVariation<false, true>());
162
163 return get(getVariation<false, false>());
164 }
165 }
166
167 ShadowProjectParamsDef gShadowProjectParamsDef;
168 ShadowProjectVertParamsDef gShadowProjectVertParamsDef;
169
170 ShadowProjectStencilMat::ShadowProjectStencilMat()
171 {
172 mVertParams = gShadowProjectVertParamsDef.createBuffer();
173 if(mParams->hasParamBlock(GPT_VERTEX_PROGRAM, "VertParams"))
174 mParams->setParamBlockBuffer(GPT_VERTEX_PROGRAM, "VertParams", mVertParams);
175 }
176
177 void ShadowProjectStencilMat::bind(const SPtr<GpuParamBlockBuffer>& perCamera)
178 {
179 Vector4 lightPosAndScale(0, 0, 0, 1);
180 gShadowProjectVertParamsDef.gPositionAndScale.set(mVertParams, lightPosAndScale);
181
182 mParams->setParamBlockBuffer("PerCamera", perCamera);
183
184 RendererMaterial::bind();
185 }
186
187 ShadowProjectStencilMat* ShadowProjectStencilMat::getVariation(bool directional, bool useZFailStencil)
188 {
189 if(directional)
190 return get(getVariation<true, true>());
191 else
192 {
193 if (useZFailStencil)
194 return get(getVariation<false, true>());
195 else
196 return get(getVariation<false, false>());
197 }
198 }
199
200 ShadowProjectMat::ShadowProjectMat()
201 : mGBufferParams(GPT_FRAGMENT_PROGRAM, mParams)
202 {
203 mParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gShadowTex", mShadowMapParam);
204 if(mParams->hasSamplerState(GPT_FRAGMENT_PROGRAM, "gShadowSampler"))
205 mParams->getSamplerStateParam(GPT_FRAGMENT_PROGRAM, "gShadowSampler", mShadowSamplerParam);
206 else
207 mParams->getSamplerStateParam(GPT_FRAGMENT_PROGRAM, "gShadowTex", mShadowSamplerParam);
208
209 SAMPLER_STATE_DESC desc;
210 desc.minFilter = FO_POINT;
211 desc.magFilter = FO_POINT;
212 desc.mipFilter = FO_POINT;
213 desc.addressMode.u = TAM_CLAMP;
214 desc.addressMode.v = TAM_CLAMP;
215 desc.addressMode.w = TAM_CLAMP;
216
217 mSamplerState = SamplerState::create(desc);
218
219 mVertParams = gShadowProjectVertParamsDef.createBuffer();
220 if(mParams->hasParamBlock(GPT_VERTEX_PROGRAM, "VertParams"))
221 mParams->setParamBlockBuffer(GPT_VERTEX_PROGRAM, "VertParams", mVertParams);
222 }
223
224 void ShadowProjectMat::bind(const ShadowProjectParams& params)
225 {
226 Vector4 lightPosAndScale(Vector3(0.0f, 0.0f, 0.0f), 1.0f);
227 gShadowProjectVertParamsDef.gPositionAndScale.set(mVertParams, lightPosAndScale);
228
229 mGBufferParams.bind(params.gbuffer);
230
231 mShadowMapParam.set(params.shadowMap);
232 mShadowSamplerParam.set(mSamplerState);
233
234 mParams->setParamBlockBuffer("Params", params.shadowParams);
235 mParams->setParamBlockBuffer("PerCamera", params.perCamera);
236
237 RendererMaterial::bind();
238 }
239
240 ShadowProjectMat* ShadowProjectMat::getVariation(UINT32 quality, bool directional, bool MSAA)
241 {
242#define BIND_MAT(QUALITY) \
243 { \
244 if(directional) \
245 if (MSAA) \
246 return get(getVariation<QUALITY, true, true>()); \
247 else \
248 return get(getVariation<QUALITY, true, false>()); \
249 else \
250 if (MSAA) \
251 return get(getVariation<QUALITY, false, true>()); \
252 else \
253 return get(getVariation<QUALITY, false, false>()); \
254 }
255
256 if(quality <= 1)
257 BIND_MAT(1)
258 else if(quality == 2)
259 BIND_MAT(2)
260 else if(quality == 3)
261 BIND_MAT(3)
262 else // 4 or higher
263 BIND_MAT(4)
264
265#undef BIND_MAT
266 }
267
268 ShadowProjectOmniParamsDef gShadowProjectOmniParamsDef;
269
270 ShadowProjectOmniMat::ShadowProjectOmniMat()
271 : mGBufferParams(GPT_FRAGMENT_PROGRAM, mParams)
272 {
273 mParams->getTextureParam(GPT_FRAGMENT_PROGRAM, "gShadowCubeTex", mShadowMapParam);
274
275 if(mParams->hasSamplerState(GPT_FRAGMENT_PROGRAM, "gShadowCubeSampler"))
276 mParams->getSamplerStateParam(GPT_FRAGMENT_PROGRAM, "gShadowCubeSampler", mShadowSamplerParam);
277 else
278 mParams->getSamplerStateParam(GPT_FRAGMENT_PROGRAM, "gShadowCubeTex", mShadowSamplerParam);
279
280 SAMPLER_STATE_DESC desc;
281 desc.minFilter = FO_LINEAR;
282 desc.magFilter = FO_LINEAR;
283 desc.mipFilter = FO_POINT;
284 desc.addressMode.u = TAM_CLAMP;
285 desc.addressMode.v = TAM_CLAMP;
286 desc.addressMode.w = TAM_CLAMP;
287 desc.comparisonFunc = CMPF_GREATER_EQUAL;
288
289 mSamplerState = SamplerState::create(desc);
290
291 mVertParams = gShadowProjectVertParamsDef.createBuffer();
292 if(mParams->hasParamBlock(GPT_VERTEX_PROGRAM, "VertParams"))
293 mParams->setParamBlockBuffer(GPT_VERTEX_PROGRAM, "VertParams", mVertParams);
294 }
295
296 void ShadowProjectOmniMat::bind(const ShadowProjectParams& params)
297 {
298 Vector4 lightPosAndScale(params.light.getTransform().getPosition(), params.light.getAttenuationRadius());
299 gShadowProjectVertParamsDef.gPositionAndScale.set(mVertParams, lightPosAndScale);
300
301 mGBufferParams.bind(params.gbuffer);
302
303 mShadowMapParam.set(params.shadowMap);
304 mShadowSamplerParam.set(mSamplerState);
305
306 mParams->setParamBlockBuffer("Params", params.shadowParams);
307 mParams->setParamBlockBuffer("PerCamera", params.perCamera);
308
309 RendererMaterial::bind();
310 }
311
312 ShadowProjectOmniMat* ShadowProjectOmniMat::getVariation(UINT32 quality, bool inside, bool MSAA)
313 {
314#define BIND_MAT(QUALITY) \
315 { \
316 if(inside) \
317 if (MSAA) \
318 return get(getVariation<QUALITY, true, true>()); \
319 else \
320 return get(getVariation<QUALITY, true, false>()); \
321 else \
322 if (MSAA) \
323 return get(getVariation<QUALITY, false, true>()); \
324 else \
325 return get(getVariation<QUALITY, false, false>()); \
326 }
327
328 if(quality <= 1)
329 BIND_MAT(1)
330 else if(quality == 2)
331 BIND_MAT(2)
332 else if(quality == 3)
333 BIND_MAT(3)
334 else // 4 or higher
335 BIND_MAT(4)
336
337#undef BIND_MAT
338 }
339
340 void ShadowInfo::updateNormArea(UINT32 atlasSize)
341 {
342 normArea.x = area.x / (float)atlasSize;
343 normArea.y = area.y / (float)atlasSize;
344 normArea.width = area.width / (float)atlasSize;
345 normArea.height = area.height / (float)atlasSize;
346 }
347
348 ShadowMapAtlas::ShadowMapAtlas(UINT32 size)
349 : mLayout(0, 0, size, size, true), mLastUsedCounter(0)
350 {
351 mAtlas = GpuResourcePool::instance().get(
352 POOLED_RENDER_TEXTURE_DESC::create2D(SHADOW_MAP_FORMAT, size, size, TU_DEPTHSTENCIL));
353 }
354
355 bool ShadowMapAtlas::addMap(UINT32 size, Rect2I& area, UINT32 border)
356 {
357 UINT32 sizeWithBorder = size + border * 2;
358
359 UINT32 x, y;
360 if (!mLayout.addElement(sizeWithBorder, sizeWithBorder, x, y))
361 return false;
362
363 area.width = area.height = size;
364 area.x = x + border;
365 area.y = y + border;
366
367 mLastUsedCounter = 0;
368 return true;
369 }
370
371 void ShadowMapAtlas::clear()
372 {
373 mLayout.clear();
374 mLastUsedCounter++;
375 }
376
377 bool ShadowMapAtlas::isEmpty() const
378 {
379 return mLayout.isEmpty();
380 }
381
382 SPtr<Texture> ShadowMapAtlas::getTexture() const
383 {
384 return mAtlas->texture;
385 }
386
387 SPtr<RenderTexture> ShadowMapAtlas::getTarget() const
388 {
389 return mAtlas->renderTexture;
390 }
391
392 ShadowMapBase::ShadowMapBase(UINT32 size)
393 : mSize(size), mIsUsed(false), mLastUsedCounter (0)
394 { }
395
396 SPtr<Texture> ShadowMapBase::getTexture() const
397 {
398 return mShadowMap->texture;
399 }
400
401 ShadowCubemap::ShadowCubemap(UINT32 size)
402 :ShadowMapBase(size)
403 {
404 mShadowMap = GpuResourcePool::instance().get(
405 POOLED_RENDER_TEXTURE_DESC::createCube(SHADOW_MAP_FORMAT, size, size, TU_DEPTHSTENCIL));
406 }
407
408 SPtr<RenderTexture> ShadowCubemap::getTarget() const
409 {
410 return mShadowMap->renderTexture;
411 }
412
413 ShadowCascadedMap::ShadowCascadedMap(UINT32 size, UINT32 numCascades)
414 :ShadowMapBase(size), mNumCascades(numCascades), mTargets(numCascades), mShadowInfos(numCascades)
415 {
416 mShadowMap = GpuResourcePool::instance().get(POOLED_RENDER_TEXTURE_DESC::create2D(SHADOW_MAP_FORMAT, size, size,
417 TU_DEPTHSTENCIL, 0, false, numCascades));
418
419 RENDER_TEXTURE_DESC rtDesc;
420 rtDesc.depthStencilSurface.texture = mShadowMap->texture;
421 rtDesc.depthStencilSurface.numFaces = 1;
422
423 for (UINT32 i = 0; i < mNumCascades; ++i)
424 {
425 rtDesc.depthStencilSurface.face = i;
426 mTargets[i] = RenderTexture::create(rtDesc);
427 }
428 }
429
430 SPtr<RenderTexture> ShadowCascadedMap::getTarget(UINT32 cascadeIdx) const
431 {
432 return mTargets[cascadeIdx];
433 }
434
435 /**
436 * Provides a common way for all types of shadow depth rendering to render the relevant objects into the depth map.
437 * Iterates over all relevant objects in the scene, binds the relevant materials and renders the objects into the depth
438 * map.
439 */
440 class ShadowRenderQueue
441 {
442 public:
443 struct Command
444 {
445 Command()
446 { }
447
448 Command(RenderableElement* element)
449 :element(element), isElement(true)
450 { }
451
452 union
453 {
454 RenderableElement* element;
455 RendererRenderable* renderable;
456 };
457
458
459 bool isElement : 1;
460 UINT32 mask : 6;
461 };
462
463 template<class Options>
464 static void execute(RendererScene& scene, const FrameInfo& frameInfo, const Options& opt)
465 {
466 static_assert((UINT32)RenderableAnimType::Count == 4, "RenderableAnimType is expected to have four sequential entries.");
467
468 const SceneInfo& sceneInfo = scene.getSceneInfo();
469
470 bs_frame_mark();
471 {
472 FrameVector<Command> commands[4];
473
474 // Make a list of relevant renderables and prepare them for rendering
475 for (UINT32 i = 0; i < sceneInfo.renderables.size(); i++)
476 {
477 const Sphere& bounds = sceneInfo.renderableCullInfos[i].bounds.getSphere();
478 if (!opt.intersects(bounds))
479 continue;
480
481 scene.prepareRenderable(i, frameInfo);
482
483 Command renderableCommand;
484 renderableCommand.mask = 0;
485
486 RendererRenderable* renderable = sceneInfo.renderables[i];
487 renderableCommand.isElement = false;
488 renderableCommand.renderable = renderable;
489
490 opt.prepare(renderableCommand, bounds);
491
492 bool renderableBound[4];
493 bs_zero_out(renderableBound);
494
495 for (auto& element : renderable->elements)
496 {
497 UINT32 arrayIdx = (int)element.animType;
498
499 if (!renderableBound[arrayIdx])
500 {
501 commands[arrayIdx].push_back(renderableCommand);
502 renderableBound[arrayIdx] = true;
503 }
504
505 commands[arrayIdx].push_back(Command(&element));
506 }
507 }
508
509 static const ShaderVariation* VAR_LOOKUP[4];
510 VAR_LOOKUP[0] = &getVertexInputVariation<false, false>();
511 VAR_LOOKUP[1] = &getVertexInputVariation<true, false>();
512 VAR_LOOKUP[2] = &getVertexInputVariation<false, true>();
513 VAR_LOOKUP[3] = &getVertexInputVariation<true, true>();
514
515 for (UINT32 i = 0; i < (UINT32)RenderableAnimType::Count; i++)
516 {
517 opt.bindMaterial(*VAR_LOOKUP[i]);
518
519 for (auto& command : commands[i])
520 {
521 if (command.isElement)
522 {
523 const RenderableElement& element = *command.element;
524
525 if (element.morphVertexDeclaration == nullptr)
526 gRendererUtility().draw(element.mesh, element.subMesh);
527 else
528 gRendererUtility().drawMorph(element.mesh, element.subMesh, element.morphShapeBuffer,
529 element.morphVertexDeclaration);
530 }
531 else
532 opt.bindRenderable(command);
533 }
534 }
535 }
536 bs_frame_clear();
537 }
538 };
539
540 /** Specialization used for ShadowRenderQueue when rendering cube (omnidirectional) shadow maps (all faces at once). */
541 struct ShadowRenderQueueCubeOptions
542 {
543 ShadowRenderQueueCubeOptions(
544 const ConvexVolume (&frustums)[6],
545 const ConvexVolume& boundingVolume,
546 const SPtr<GpuParamBlockBuffer>& shadowParamsBuffer,
547 const SPtr<GpuParamBlockBuffer>& shadowCubeMatricesBuffer,
548 const SPtr<GpuParamBlockBuffer>& shadowCubeMasksBuffer)
549 : frustums(frustums), boundingVolume(boundingVolume), shadowParamsBuffer(shadowParamsBuffer)
550 , shadowCubeMatricesBuffer(shadowCubeMatricesBuffer), shadowCubeMasksBuffer(shadowCubeMasksBuffer)
551 { }
552
553 bool intersects(const Sphere& bounds) const
554 {
555 return boundingVolume.intersects(bounds);
556 }
557
558 void prepare(ShadowRenderQueue::Command& command, const Sphere& bounds) const
559 {
560 for (UINT32 j = 0; j < 6; j++)
561 command.mask |= (frustums[j].intersects(bounds) ? 1 : 0) << j;
562 }
563
564 void bindMaterial(const ShaderVariation& variation) const
565 {
566 material = ShadowDepthCubeMat::get(variation);
567 material->bind(shadowParamsBuffer, shadowCubeMatricesBuffer);
568 }
569
570 void bindRenderable(ShadowRenderQueue::Command& command) const
571 {
572 RendererRenderable* renderable = command.renderable;
573
574 for (UINT32 j = 0; j < 6; j++)
575 gShadowCubeMasksDef.gFaceMasks.set(shadowCubeMasksBuffer, (command.mask & (1 << j)), j);
576
577 material->setPerObjectBuffer(renderable->perObjectParamBuffer, shadowCubeMasksBuffer);
578 }
579
580 const ConvexVolume (&frustums)[6];
581 const ConvexVolume& boundingVolume;
582 const SPtr<GpuParamBlockBuffer>& shadowParamsBuffer;
583 const SPtr<GpuParamBlockBuffer>& shadowCubeMatricesBuffer;
584 const SPtr<GpuParamBlockBuffer>& shadowCubeMasksBuffer;
585
586 mutable ShadowDepthCubeMat* material = nullptr;
587 };
588
589 /** Specialization used for ShadowRenderQueue when rendering cube (omnidirectional) shadow maps (one face at a time). */
590 struct ShadowRenderQueueCubeSingleOptions
591 {
592 ShadowRenderQueueCubeSingleOptions(
593 const ConvexVolume& boundingVolume,
594 const SPtr<GpuParamBlockBuffer>& shadowParamsBuffer)
595 : boundingVolume(boundingVolume), shadowParamsBuffer(shadowParamsBuffer)
596 { }
597
598 bool intersects(const Sphere& bounds) const
599 {
600 return boundingVolume.intersects(bounds);
601 }
602
603 void prepare(ShadowRenderQueue::Command& command, const Sphere& bounds) const
604 {
605 }
606
607 void bindMaterial(const ShaderVariation& variation) const
608 {
609 material = ShadowDepthNormalNoPSMat::get(variation);
610 material->bind(shadowParamsBuffer);
611 }
612
613 void bindRenderable(ShadowRenderQueue::Command& command) const
614 {
615 RendererRenderable* renderable = command.renderable;
616
617 material->setPerObjectBuffer(renderable->perObjectParamBuffer);
618 }
619
620 const ConvexVolume& boundingVolume;
621 const SPtr<GpuParamBlockBuffer>& shadowParamsBuffer;
622
623 mutable ShadowDepthNormalNoPSMat* material = nullptr;
624 };
625
626 /** Specialization used for ShadowRenderQueue when rendering spot light shadow maps. */
627 struct ShadowRenderQueueSpotOptions
628 {
629 ShadowRenderQueueSpotOptions(
630 const ConvexVolume& boundingVolume,
631 const SPtr<GpuParamBlockBuffer>& shadowParamsBuffer)
632 : boundingVolume(boundingVolume), shadowParamsBuffer(shadowParamsBuffer)
633 { }
634
635 bool intersects(const Sphere& bounds) const
636 {
637 return boundingVolume.intersects(bounds);
638 }
639
640 void prepare(ShadowRenderQueue::Command& command, const Sphere& bounds) const
641 {
642 }
643
644 void bindMaterial(const ShaderVariation& variation) const
645 {
646 material = ShadowDepthNormalMat::get(variation);
647 material->bind(shadowParamsBuffer);
648 }
649
650 void bindRenderable(ShadowRenderQueue::Command& command) const
651 {
652 RendererRenderable* renderable = command.renderable;
653
654 material->setPerObjectBuffer(renderable->perObjectParamBuffer);
655 }
656
657 const ConvexVolume& boundingVolume;
658 const SPtr<GpuParamBlockBuffer>& shadowParamsBuffer;
659
660 mutable ShadowDepthNormalMat* material = nullptr;
661 };
662
663 /** Specialization used for ShadowRenderQueue when rendering directional light shadow maps. */
664 struct ShadowRenderQueueDirOptions
665 {
666 ShadowRenderQueueDirOptions(
667 const ConvexVolume& boundingVolume,
668 const SPtr<GpuParamBlockBuffer>& shadowParamsBuffer)
669 : boundingVolume(boundingVolume), shadowParamsBuffer(shadowParamsBuffer)
670 { }
671
672 bool intersects(const Sphere& bounds) const
673 {
674 return boundingVolume.intersects(bounds);
675 }
676
677 void prepare(ShadowRenderQueue::Command& command, const Sphere& bounds) const
678 {
679 }
680
681 void bindMaterial(const ShaderVariation& variation) const
682 {
683 material = ShadowDepthDirectionalMat::get(variation);
684 material->bind(shadowParamsBuffer);
685 }
686
687 void bindRenderable(ShadowRenderQueue::Command& command) const
688 {
689 RendererRenderable* renderable = command.renderable;
690
691 material->setPerObjectBuffer(renderable->perObjectParamBuffer);
692 }
693
694 const ConvexVolume& boundingVolume;
695 const SPtr<GpuParamBlockBuffer>& shadowParamsBuffer;
696
697 mutable ShadowDepthDirectionalMat* material = nullptr;
698 };
699
700 const UINT32 ShadowRendering::MAX_ATLAS_SIZE = 4096;
701 const UINT32 ShadowRendering::MAX_UNUSED_FRAMES = 60;
702 const UINT32 ShadowRendering::MIN_SHADOW_MAP_SIZE = 32;
703 const UINT32 ShadowRendering::SHADOW_MAP_FADE_SIZE = 64;
704 const UINT32 ShadowRendering::SHADOW_MAP_BORDER = 4;
705 const float ShadowRendering::CASCADE_FRACTION_FADE = 0.1f;
706
707 ShadowRendering::ShadowRendering(UINT32 shadowMapSize)
708 : mShadowMapSize(shadowMapSize)
709 {
710 SPtr<VertexDataDesc> vertexDesc = VertexDataDesc::create();
711 vertexDesc->addVertElem(VET_FLOAT3, VES_POSITION);
712
713 mPositionOnlyVD = VertexDeclaration::create(vertexDesc);
714
715 // Create plane index and vertex buffers
716 {
717 VERTEX_BUFFER_DESC vbDesc;
718 vbDesc.numVerts = 8;
719 vbDesc.usage = GBU_DYNAMIC;
720 vbDesc.vertexSize = mPositionOnlyVD->getProperties().getVertexSize(0);
721
722 mPlaneVB = VertexBuffer::create(vbDesc);
723
724 INDEX_BUFFER_DESC ibDesc;
725 ibDesc.indexType = IT_32BIT;
726 ibDesc.numIndices = 12;
727
728 mPlaneIB = IndexBuffer::create(ibDesc);
729
730 UINT32 indices[] =
731 {
732 // Far plane, back facing
733 4, 7, 6,
734 4, 6, 5,
735
736 // Near plane, front facing
737 0, 1, 2,
738 0, 2, 3
739 };
740
741 mPlaneIB->writeData(0, sizeof(indices), indices);
742 }
743
744 // Create frustum index and vertex buffers
745 {
746 VERTEX_BUFFER_DESC vbDesc;
747 vbDesc.numVerts = 8;
748 vbDesc.usage = GBU_DYNAMIC;
749 vbDesc.vertexSize = mPositionOnlyVD->getProperties().getVertexSize(0);
750
751 mFrustumVB = VertexBuffer::create(vbDesc);
752
753 INDEX_BUFFER_DESC ibDesc;
754 ibDesc.indexType = IT_32BIT;
755 ibDesc.numIndices = 36;
756
757 mFrustumIB = IndexBuffer::create(ibDesc);
758 mFrustumIB->writeData(0, sizeof(AABox::CUBE_INDICES), AABox::CUBE_INDICES);
759 }
760 }
761
762 void ShadowRendering::setShadowMapSize(UINT32 size)
763 {
764 if (mShadowMapSize == size)
765 return;
766
767 mCascadedShadowMaps.clear();
768 mDynamicShadowMaps.clear();
769 mShadowCubemaps.clear();
770
771 mShadowMapSize = size;
772 }
773
774 void ShadowRendering::renderShadowMaps(RendererScene& scene, const RendererViewGroup& viewGroup,
775 const FrameInfo& frameInfo)
776 {
777 // Note: Currently all shadows are dynamic and are rebuilt every frame. I should later added support for static
778 // shadow maps which can be used for immovable lights. Such a light can then maintain a set of shadow maps,
779 // one of which is static and only effects the static geometry, while the rest are per-object shadow maps used
780 // for dynamic objects. Then only a small subset of geometry needs to be redrawn, instead of everything.
781
782 // Note: Add support for per-object shadows and a way to force a renderable to use per-object shadows. This can be
783 // used for adding high quality shadows on specific objects (e.g. important characters during cinematics).
784
785 const SceneInfo& sceneInfo = scene.getSceneInfo();
786 const VisibilityInfo& visibility = viewGroup.getVisibilityInfo();
787
788 // Clear all transient data from last frame
789 mShadowInfos.clear();
790
791 mSpotLightShadows.resize(sceneInfo.spotLights.size());
792 mRadialLightShadows.resize(sceneInfo.radialLights.size());
793 mDirectionalLightShadows.resize(sceneInfo.directionalLights.size());
794
795 mSpotLightShadowOptions.clear();
796 mRadialLightShadowOptions.clear();
797
798 // Clear all dynamic light atlases
799 for (auto& entry : mCascadedShadowMaps)
800 entry.clear();
801
802 for (auto& entry : mDynamicShadowMaps)
803 entry.clear();
804
805 for (auto& entry : mShadowCubemaps)
806 entry.clear();
807
808 // Determine shadow map sizes and sort them
809 UINT32 shadowInfoCount = 0;
810 for (UINT32 i = 0; i < (UINT32)sceneInfo.spotLights.size(); ++i)
811 {
812 const RendererLight& light = sceneInfo.spotLights[i];
813 mSpotLightShadows[i].startIdx = shadowInfoCount;
814 mSpotLightShadows[i].numShadows = 0;
815
816 // Note: I'm using visibility across all views, while I could be using visibility for every view individually,
817 // if I kept that information somewhere
818 if (!light.internal->getCastsShadow() || !visibility.spotLights[i])
819 continue;
820
821 ShadowMapOptions options;
822 options.lightIdx = i;
823
824 float maxFadePercent;
825 calcShadowMapProperties(light, viewGroup, SHADOW_MAP_BORDER, options.mapSize, options.fadePercents, maxFadePercent);
826
827 // Don't render shadow maps that will end up nearly completely faded out
828 if (maxFadePercent < 0.005f)
829 continue;
830
831 mSpotLightShadowOptions.push_back(options);
832 shadowInfoCount++; // For now, always a single fully dynamic shadow for a single light, but that may change
833 }
834
835 for (UINT32 i = 0; i < (UINT32)sceneInfo.radialLights.size(); ++i)
836 {
837 const RendererLight& light = sceneInfo.radialLights[i];
838 mRadialLightShadows[i].startIdx = shadowInfoCount;
839 mRadialLightShadows[i].numShadows = 0;
840
841 // Note: I'm using visibility across all views, while I could be using visibility for every view individually,
842 // if I kept that information somewhere
843 if (!light.internal->getCastsShadow() || !visibility.radialLights[i])
844 continue;
845
846 ShadowMapOptions options;
847 options.lightIdx = i;
848
849 float maxFadePercent;
850 calcShadowMapProperties(light, viewGroup, 0, options.mapSize, options.fadePercents, maxFadePercent);
851
852 // Don't render shadow maps that will end up nearly completely faded out
853 if (maxFadePercent < 0.005f)
854 continue;
855
856 mRadialLightShadowOptions.push_back(options);
857
858 shadowInfoCount++; // For now, always a single fully dynamic shadow for a single light, but that may change
859 }
860
861 // Sort spot lights by size so they fit neatly in the texture atlas
862 std::sort(mSpotLightShadowOptions.begin(), mSpotLightShadowOptions.end(),
863 [](const ShadowMapOptions& a, const ShadowMapOptions& b) { return a.mapSize > b.mapSize; } );
864
865 // Reserve space for shadow infos
866 mShadowInfos.resize(shadowInfoCount);
867
868 // Deallocate unused textures (must be done before rendering shadows, in order to ensure indices don't change)
869 for(auto iter = mDynamicShadowMaps.begin(); iter != mDynamicShadowMaps.end(); ++iter)
870 {
871 if(iter->getLastUsedCounter() >= MAX_UNUSED_FRAMES)
872 {
873 // These are always populated in order, so we can assume all following atlases are also empty
874 mDynamicShadowMaps.erase(iter, mDynamicShadowMaps.end());
875 break;
876 }
877 }
878
879 for(auto iter = mCascadedShadowMaps.begin(); iter != mCascadedShadowMaps.end();)
880 {
881 if (iter->getLastUsedCounter() >= MAX_UNUSED_FRAMES)
882 iter = mCascadedShadowMaps.erase(iter);
883 else
884 ++iter;
885 }
886
887 for(auto iter = mShadowCubemaps.begin(); iter != mShadowCubemaps.end();)
888 {
889 if (iter->getLastUsedCounter() >= MAX_UNUSED_FRAMES)
890 iter = mShadowCubemaps.erase(iter);
891 else
892 ++iter;
893 }
894
895 // Render shadow maps
896 for (UINT32 i = 0; i < (UINT32)sceneInfo.directionalLights.size(); ++i)
897 {
898 const RendererLight& light = sceneInfo.directionalLights[i];
899
900 if (!light.internal->getCastsShadow())
901 return;
902
903 UINT32 numViews = viewGroup.getNumViews();
904 mDirectionalLightShadows[i].viewShadows.resize(numViews);
905
906 for (UINT32 j = 0; j < numViews; ++j)
907 renderCascadedShadowMaps(*viewGroup.getView(j), i, scene, frameInfo);
908 }
909
910 for(auto& entry : mSpotLightShadowOptions)
911 {
912 UINT32 lightIdx = entry.lightIdx;
913 renderSpotShadowMap(sceneInfo.spotLights[lightIdx], entry, scene, frameInfo);
914 }
915
916 for (auto& entry : mRadialLightShadowOptions)
917 {
918 UINT32 lightIdx = entry.lightIdx;
919 renderRadialShadowMap(sceneInfo.radialLights[lightIdx], entry, scene, frameInfo);
920 }
921 }
922
923 /**
924 * Generates a frustum from the provided view-projection matrix.
925 *
926 * @param[in] invVP Inverse of the view-projection matrix to use for generating the frustum.
927 * @param[out] worldFrustum Generated frustum planes, in world space.
928 * @return Individual vertices of the frustum corners, in world space. Ordered using the
929 * AABox::CornerEnum.
930 */
931 std::array<Vector3, 8> getFrustum(const Matrix4& invVP, ConvexVolume& worldFrustum)
932 {
933 std::array<Vector3, 8> output;
934
935 const RenderAPICapabilities& caps = gCaps();
936
937 float flipY = 1.0f;
938 if (caps.conventions.ndcYAxis == Conventions::Axis::Down)
939 flipY = -1.0f;
940
941 AABox frustumCube(
942 Vector3(-1, -1 * flipY, caps.minDepth),
943 Vector3(1, 1 * flipY, caps.maxDepth)
944 );
945
946 for(size_t i = 0; i < output.size(); i++)
947 {
948 Vector3 corner = frustumCube.getCorner((AABox::Corner)i);
949 output[i] = invVP.multiply(corner);
950 }
951
952 Vector<Plane> planes(6);
953 planes[FRUSTUM_PLANE_NEAR] = Plane(output[AABox::NEAR_LEFT_BOTTOM], output[AABox::NEAR_RIGHT_BOTTOM], output[AABox::NEAR_RIGHT_TOP]);
954 planes[FRUSTUM_PLANE_FAR] = Plane(output[AABox::FAR_LEFT_BOTTOM], output[AABox::FAR_LEFT_TOP], output[AABox::FAR_RIGHT_TOP]);
955 planes[FRUSTUM_PLANE_LEFT] = Plane(output[AABox::NEAR_LEFT_BOTTOM], output[AABox::NEAR_LEFT_TOP], output[AABox::FAR_LEFT_TOP]);
956 planes[FRUSTUM_PLANE_RIGHT] = Plane(output[AABox::FAR_RIGHT_TOP], output[AABox::NEAR_RIGHT_TOP], output[AABox::NEAR_RIGHT_BOTTOM]);
957 planes[FRUSTUM_PLANE_TOP] = Plane(output[AABox::NEAR_LEFT_TOP], output[AABox::NEAR_RIGHT_TOP], output[AABox::FAR_RIGHT_TOP]);
958 planes[FRUSTUM_PLANE_BOTTOM] = Plane(output[AABox::NEAR_LEFT_BOTTOM], output[AABox::FAR_LEFT_BOTTOM], output[AABox::FAR_RIGHT_BOTTOM]);
959
960 worldFrustum = ConvexVolume(planes);
961 return output;
962 }
963
964 /**
965 * Converts a point in mixed space (clip_x, clip_y, view_z, view_w) to UV coordinates on a shadow map (x, y),
966 * and normalized linear depth from the shadow caster's perspective (z).
967 */
968 Matrix4 createMixedToShadowUVMatrix(const Matrix4& viewP, const Matrix4& viewInvVP, const Rect2& shadowMapArea,
969 float depthScale, float depthOffset, const Matrix4& shadowViewProj)
970 {
971 // Projects a point from (clip_x, clip_y, view_z, view_w) into clip space
972 Matrix4 mixedToShadow = Matrix4::IDENTITY;
973 mixedToShadow[2][2] = viewP[2][2];
974 mixedToShadow[2][3] = viewP[2][3];
975 mixedToShadow[3][2] = viewP[3][2];
976 mixedToShadow[3][3] = 0.0f;
977
978 // Projects a point in clip space back to homogeneus world space
979 mixedToShadow = viewInvVP * mixedToShadow;
980
981 // Projects a point in world space to shadow clip space
982 mixedToShadow = shadowViewProj * mixedToShadow;
983
984 // Convert shadow clip space coordinates to UV coordinates relative to the shadow map rectangle, and normalize
985 // depth
986 const Conventions& rapiConventions = gCaps().conventions;
987
988 float flipY = -1.0f;
989 // Either of these flips the Y axis, but if they're both true they cancel out
990 if ((rapiConventions.uvYAxis == Conventions::Axis::Up) ^ (rapiConventions.ndcYAxis == Conventions::Axis::Down))
991 flipY = -flipY;
992
993 Matrix4 shadowMapTfrm
994 (
995 shadowMapArea.width * 0.5f, 0, 0, shadowMapArea.x + 0.5f * shadowMapArea.width,
996 0, flipY * shadowMapArea.height * 0.5f, 0, shadowMapArea.y + 0.5f * shadowMapArea.height,
997 0, 0, depthScale, depthOffset,
998 0, 0, 0, 1
999 );
1000
1001 return shadowMapTfrm * mixedToShadow;
1002 }
1003
1004 void ShadowRendering::renderShadowOcclusion(const RendererView& view, const RendererLight& rendererLight,
1005 GBufferTextures gbuffer) const
1006 {
1007 UINT32 shadowQuality = view.getRenderSettings().shadowSettings.shadowFilteringQuality;
1008
1009 const Light* light = rendererLight.internal;
1010 UINT32 lightIdx = light->getRendererId();
1011
1012 auto viewProps = view.getProperties();
1013
1014 const Matrix4& viewP = viewProps.projTransform;
1015 Matrix4 viewInvVP = viewProps.viewProjTransform.inverse();
1016
1017 SPtr<GpuParamBlockBuffer> perViewBuffer = view.getPerViewBuffer();
1018
1019 ProfileGPUBlock sampleBlock("Render shadow occlusion");
1020
1021 const RenderAPICapabilities& caps = gCaps();
1022 // TODO - Calculate and set a scissor rectangle for the light
1023
1024 SPtr<GpuParamBlockBuffer> shadowParamBuffer = gShadowProjectParamsDef.createBuffer();
1025 SPtr<GpuParamBlockBuffer> shadowOmniParamBuffer = gShadowProjectOmniParamsDef.createBuffer();
1026
1027 UINT32 viewIdx = view.getViewIdx();
1028 Vector<const ShadowInfo*> shadowInfos;
1029
1030 if(light->getType() == LightType::Radial)
1031 {
1032 const LightShadows& shadows = mRadialLightShadows[lightIdx];
1033
1034 for(UINT32 i = 0; i < shadows.numShadows; ++i)
1035 {
1036 UINT32 shadowIdx = shadows.startIdx + i;
1037 const ShadowInfo& shadowInfo = mShadowInfos[shadowIdx];
1038
1039 if (shadowInfo.fadePerView[viewIdx] < 0.005f)
1040 continue;
1041
1042 for(UINT32 j = 0; j < 6; j++)
1043 gShadowProjectOmniParamsDef.gFaceVPMatrices.set(shadowOmniParamBuffer, shadowInfo.shadowVPTransforms[j], j);
1044
1045 gShadowProjectOmniParamsDef.gDepthBias.set(shadowOmniParamBuffer, shadowInfo.depthBias);
1046 gShadowProjectOmniParamsDef.gFadePercent.set(shadowOmniParamBuffer, shadowInfo.fadePerView[viewIdx]);
1047 gShadowProjectOmniParamsDef.gInvResolution.set(shadowOmniParamBuffer, 1.0f / shadowInfo.area.width);
1048
1049 const Transform& tfrm = light->getTransform();
1050 Vector4 lightPosAndRadius(tfrm.getPosition(), light->getAttenuationRadius());
1051 gShadowProjectOmniParamsDef.gLightPosAndRadius.set(shadowOmniParamBuffer, lightPosAndRadius);
1052
1053 // Reduce shadow quality based on shadow map resolution for spot lights
1054 UINT32 effectiveShadowQuality = getShadowQuality(shadowQuality, shadowInfo.area.width, 2);
1055
1056 // Check if viewer is inside the light bounds
1057 //// Expand the light bounds slightly to handle the case when the near plane is intersecting the light volume
1058 float lightRadius = light->getAttenuationRadius() + viewProps.nearPlane * 3.0f;
1059 bool viewerInsideVolume = (tfrm.getPosition() - viewProps.viewOrigin).length() < lightRadius;
1060
1061 SPtr<Texture> shadowMap = mShadowCubemaps[shadowInfo.textureIdx].getTexture();
1062 ShadowProjectParams shadowParams(*light, shadowMap, shadowOmniParamBuffer, perViewBuffer, gbuffer);
1063
1064 ShadowProjectOmniMat* mat = ShadowProjectOmniMat::getVariation(effectiveShadowQuality, viewerInsideVolume,
1065 viewProps.target.numSamples > 1);
1066 mat->bind(shadowParams);
1067
1068 gRendererUtility().draw(gRendererUtility().getSphereStencil());
1069 }
1070 }
1071 else // Directional & spot
1072 {
1073 shadowInfos.clear();
1074
1075 bool isCSM = light->getType() == LightType::Directional;
1076 if(!isCSM)
1077 {
1078 const LightShadows& shadows = mSpotLightShadows[lightIdx];
1079 for (UINT32 i = 0; i < shadows.numShadows; ++i)
1080 {
1081 UINT32 shadowIdx = shadows.startIdx + i;
1082 const ShadowInfo& shadowInfo = mShadowInfos[shadowIdx];
1083
1084 if (shadowInfo.fadePerView[viewIdx] < 0.005f)
1085 continue;
1086
1087 shadowInfos.push_back(&shadowInfo);
1088 }
1089 }
1090 else // Directional
1091 {
1092 const LightShadows& shadows = mDirectionalLightShadows[lightIdx].viewShadows[viewIdx];
1093 if (shadows.numShadows > 0)
1094 {
1095 UINT32 mapIdx = shadows.startIdx;
1096 const ShadowCascadedMap& cascadedMap = mCascadedShadowMaps[mapIdx];
1097
1098 // Render cascades in far to near order.
1099 // Note: If rendering other non-cascade maps they should be rendered after cascades.
1100 for (INT32 i = cascadedMap.getNumCascades() - 1; i >= 0; i--)
1101 shadowInfos.push_back(&cascadedMap.getShadowInfo(i));
1102 }
1103 }
1104
1105 for(auto& shadowInfo : shadowInfos)
1106 {
1107 float depthScale, depthOffset;
1108
1109 // Depth range scale is already baked into the ortho projection matrix, so avoid doing it here
1110 if (isCSM)
1111 {
1112 // Need to map from API-specific clip space depth to [0, 1] range
1113 depthScale = 1.0f / (caps.maxDepth - caps.minDepth);
1114 depthOffset = -caps.minDepth * depthScale;
1115 }
1116 else
1117 {
1118 depthScale = 1.0f / shadowInfo->depthRange;
1119 depthOffset = 0.0f;
1120 }
1121
1122 SPtr<Texture> shadowMap;
1123 UINT32 shadowMapFace = 0;
1124 if(!isCSM)
1125 shadowMap = mDynamicShadowMaps[shadowInfo->textureIdx].getTexture();
1126 else
1127 {
1128 shadowMap = mCascadedShadowMaps[shadowInfo->textureIdx].getTexture();
1129 shadowMapFace = shadowInfo->cascadeIdx;
1130 }
1131
1132 Matrix4 mixedToShadowUV = createMixedToShadowUVMatrix(viewP, viewInvVP, shadowInfo->normArea,
1133 depthScale, depthOffset, shadowInfo->shadowVPTransform);
1134
1135 auto shadowMapProps = shadowMap->getProperties();
1136
1137 Vector2 shadowMapSize((float)shadowMapProps.getWidth(), (float)shadowMapProps.getHeight());
1138 float transitionScale = getFadeTransition(*light, shadowInfo->subjectBounds.getRadius(),
1139 shadowInfo->depthRange, shadowInfo->area.width);
1140
1141 gShadowProjectParamsDef.gFadePlaneDepth.set(shadowParamBuffer, shadowInfo->depthFade);
1142 gShadowProjectParamsDef.gMixedToShadowSpace.set(shadowParamBuffer, mixedToShadowUV);
1143 gShadowProjectParamsDef.gShadowMapSize.set(shadowParamBuffer, shadowMapSize);
1144 gShadowProjectParamsDef.gShadowMapSizeInv.set(shadowParamBuffer, 1.0f / shadowMapSize);
1145 gShadowProjectParamsDef.gSoftTransitionScale.set(shadowParamBuffer, transitionScale);
1146
1147 if(isCSM)
1148 gShadowProjectParamsDef.gFadePercent.set(shadowParamBuffer, 1.0f);
1149 else
1150 gShadowProjectParamsDef.gFadePercent.set(shadowParamBuffer, shadowInfo->fadePerView[viewIdx]);
1151
1152 if(shadowInfo->fadeRange == 0.0f)
1153 gShadowProjectParamsDef.gInvFadePlaneRange.set(shadowParamBuffer, 0.0f);
1154 else
1155 gShadowProjectParamsDef.gInvFadePlaneRange.set(shadowParamBuffer, 1.0f / shadowInfo->fadeRange);
1156
1157 // Generate a stencil buffer to avoid evaluating pixels without any receiver geometry in the shadow area
1158 std::array<Vector3, 8> frustumVertices;
1159 UINT32 effectiveShadowQuality = shadowQuality;
1160 if(!isCSM)
1161 {
1162 ConvexVolume shadowFrustum;
1163 frustumVertices = getFrustum(shadowInfo->shadowVPTransform.inverse(), shadowFrustum);
1164
1165 // Check if viewer is inside the frustum. Frustum is slightly expanded so that if the near plane is
1166 // intersecting the shadow frustum, it is counted as inside. This needs to be conservative as the code
1167 // for handling viewer outside the frustum will not properly render intersections with the near plane.
1168 bool viewerInsideFrustum = shadowFrustum.contains(viewProps.viewOrigin, viewProps.nearPlane * 3.0f);
1169
1170 ShadowProjectStencilMat* mat = ShadowProjectStencilMat::getVariation(false, viewerInsideFrustum);
1171 mat->bind(perViewBuffer);
1172 drawFrustum(frustumVertices);
1173
1174 // Reduce shadow quality based on shadow map resolution for spot lights
1175 effectiveShadowQuality = getShadowQuality(shadowQuality, shadowInfo->area.width, 2);
1176 }
1177 else
1178 {
1179 // Need to generate near and far planes to clip the geometry within the current CSM slice.
1180 // Note: If the render API supports built-in depth bound tests that could be used instead.
1181
1182 Vector3 near = viewProps.projTransform.multiply(Vector3(0, 0, -shadowInfo->depthNear));
1183 Vector3 far = viewProps.projTransform.multiply(Vector3(0, 0, -shadowInfo->depthFar));
1184
1185 ShadowProjectStencilMat* mat = ShadowProjectStencilMat::getVariation(true, true);
1186 mat->bind(perViewBuffer);
1187
1188 drawNearFarPlanes(near.z, far.z, shadowInfo->cascadeIdx != 0);
1189 }
1190
1191 gShadowProjectParamsDef.gFace.set(shadowParamBuffer, (float)shadowMapFace);
1192 ShadowProjectParams shadowParams(*light, shadowMap, shadowParamBuffer, perViewBuffer, gbuffer);
1193
1194 ShadowProjectMat* mat = ShadowProjectMat::getVariation(effectiveShadowQuality, isCSM,
1195 viewProps.target.numSamples > 1);
1196 mat->bind(shadowParams);
1197
1198 if (!isCSM)
1199 drawFrustum(frustumVertices);
1200 else
1201 gRendererUtility().drawScreenQuad();
1202 }
1203 }
1204 }
1205
1206 void ShadowRendering::renderCascadedShadowMaps(const RendererView& view, UINT32 lightIdx, RendererScene& scene,
1207 const FrameInfo& frameInfo)
1208 {
1209 UINT32 viewIdx = view.getViewIdx();
1210 LightShadows& lightShadows = mDirectionalLightShadows[lightIdx].viewShadows[viewIdx];
1211
1212 if (!view.getRenderSettings().enableShadows)
1213 {
1214 lightShadows.startIdx = -1;
1215 lightShadows.numShadows = 0;
1216 return;
1217 }
1218
1219 // Note: Currently I'm using spherical bounds for the cascaded frustum which might result in non-optimal usage
1220 // of the shadow map. A different approach would be to generate a bounding box and then both adjust the aspect
1221 // ratio (and therefore dimensions) of the shadow map, as well as rotate the camera so the visible area best fits
1222 // in the map. It remains to be seen if this is viable.
1223 // - Note2: Actually both of these will likely have serious negative impact on shadow stability.
1224 const SceneInfo& sceneInfo = scene.getSceneInfo();
1225
1226 const RendererLight& rendererLight = sceneInfo.directionalLights[lightIdx];
1227 Light* light = rendererLight.internal;
1228
1229 RenderAPI& rapi = RenderAPI::instance();
1230
1231 const Transform& tfrm = light->getTransform();
1232 Vector3 lightDir = -tfrm.getRotation().zAxis();
1233 SPtr<GpuParamBlockBuffer> shadowParamsBuffer = gShadowParamsDef.createBuffer();
1234
1235 ShadowInfo shadowInfo;
1236 shadowInfo.lightIdx = lightIdx;
1237 shadowInfo.textureIdx = -1;
1238
1239 UINT32 mapSize = std::min(mShadowMapSize, MAX_ATLAS_SIZE);
1240 shadowInfo.area = Rect2I(0, 0, mapSize, mapSize);
1241 shadowInfo.updateNormArea(mapSize);
1242
1243 UINT32 numCascades = view.getRenderSettings().shadowSettings.numCascades;
1244 for (UINT32 i = 0; i < (UINT32)mCascadedShadowMaps.size(); i++)
1245 {
1246 ShadowCascadedMap& shadowMap = mCascadedShadowMaps[i];
1247
1248 if (!shadowMap.isUsed() && shadowMap.getSize() == mapSize && shadowMap.getNumCascades() == numCascades)
1249 {
1250 shadowInfo.textureIdx = i;
1251 shadowMap.markAsUsed();
1252
1253 break;
1254 }
1255 }
1256
1257 if (shadowInfo.textureIdx == (UINT32)-1)
1258 {
1259 shadowInfo.textureIdx = (UINT32)mCascadedShadowMaps.size();
1260 mCascadedShadowMaps.push_back(ShadowCascadedMap(mapSize, numCascades));
1261
1262 ShadowCascadedMap& shadowMap = mCascadedShadowMaps.back();
1263 shadowMap.markAsUsed();
1264 }
1265
1266 ShadowCascadedMap& shadowMap = mCascadedShadowMaps[shadowInfo.textureIdx];
1267
1268 Quaternion lightRotation(BsIdentity);
1269 lightRotation.lookRotation(lightDir, Vector3::UNIT_Y);
1270
1271 ProfileGPUBlock profileSample("Project directional light shadow");
1272
1273 for (UINT32 i = 0; i < numCascades; ++i)
1274 {
1275 Sphere frustumBounds;
1276 ConvexVolume cascadeCullVolume = getCSMSplitFrustum(view, lightDir, i, numCascades, frustumBounds);
1277
1278 // Make sure the size of the projected area is in multiples of shadow map pixel size (for stability)
1279 float worldUnitsPerTexel = frustumBounds.getRadius() * 2.0f / shadowMap.getSize();
1280
1281 float orthoSize = floor(frustumBounds.getRadius() * 2.0f / worldUnitsPerTexel) * worldUnitsPerTexel * 0.5f;
1282 worldUnitsPerTexel = orthoSize * 2.0f / shadowMap.getSize();
1283
1284 // Snap caster origin to the shadow map pixel grid, to ensure shadow map stability
1285 Vector3 casterOrigin = frustumBounds.getCenter();
1286 Matrix4 shadowView = Matrix4::view(Vector3::ZERO, lightRotation);
1287 Vector3 shadowSpaceOrigin = shadowView.multiplyAffine(casterOrigin);
1288
1289 Vector2 snapOffset(fmod(shadowSpaceOrigin.x, worldUnitsPerTexel), fmod(shadowSpaceOrigin.y, worldUnitsPerTexel));
1290 shadowSpaceOrigin.x -= snapOffset.x;
1291 shadowSpaceOrigin.y -= snapOffset.y;
1292
1293 Matrix4 shadowViewInv = shadowView.inverseAffine();
1294 casterOrigin = shadowViewInv.multiplyAffine(shadowSpaceOrigin);
1295
1296 // Move the light so it is centered at the subject frustum, with depth range covering the frustum bounds
1297 shadowInfo.depthRange = frustumBounds.getRadius() * 2.0f;
1298
1299 Vector3 offsetLightPos = casterOrigin - lightDir * frustumBounds.getRadius();
1300 Matrix4 offsetViewMat = Matrix4::view(offsetLightPos, lightRotation);
1301
1302 Matrix4 proj = Matrix4::projectionOrthographic(-orthoSize, orthoSize, orthoSize, -orthoSize, 0.0f,
1303 shadowInfo.depthRange);
1304
1305 RenderAPI::instance().convertProjectionMatrix(proj, proj);
1306
1307 shadowInfo.cascadeIdx = i;
1308 shadowInfo.shadowVPTransform = proj * offsetViewMat;
1309
1310 // Determine split range
1311 float splitNear = getCSMSplitDistance(view, i, numCascades);
1312 float splitFar = getCSMSplitDistance(view, i + 1, numCascades);
1313
1314 shadowInfo.depthNear = splitNear;
1315 shadowInfo.depthFade = splitFar;
1316 shadowInfo.subjectBounds = frustumBounds;
1317
1318 if ((UINT32)(i + 1) < numCascades)
1319 shadowInfo.fadeRange = CASCADE_FRACTION_FADE * (shadowInfo.depthFade - shadowInfo.depthNear);
1320 else
1321 shadowInfo.fadeRange = 0.0f;
1322
1323 shadowInfo.depthFar = shadowInfo.depthFade + shadowInfo.fadeRange;
1324 shadowInfo.depthBias = getDepthBias(*light, frustumBounds.getRadius(), shadowInfo.depthRange, mapSize);
1325
1326 gShadowParamsDef.gDepthBias.set(shadowParamsBuffer, shadowInfo.depthBias);
1327 gShadowParamsDef.gInvDepthRange.set(shadowParamsBuffer, 1.0f / shadowInfo.depthRange);
1328 gShadowParamsDef.gMatViewProj.set(shadowParamsBuffer, shadowInfo.shadowVPTransform);
1329 gShadowParamsDef.gNDCZToDeviceZ.set(shadowParamsBuffer, RendererView::getNDCZToDeviceZ());
1330
1331 rapi.setRenderTarget(shadowMap.getTarget(i));
1332 rapi.clearRenderTarget(FBT_DEPTH);
1333
1334 ShadowDepthDirectionalMat* depthDirMat = ShadowDepthDirectionalMat::get();
1335 depthDirMat->bind(shadowParamsBuffer);
1336
1337 // Render all renderables into the shadow map
1338 ShadowRenderQueueDirOptions dirOptions(
1339 cascadeCullVolume,
1340 shadowParamsBuffer);
1341
1342 ShadowRenderQueue::execute(scene, frameInfo, dirOptions);
1343
1344 shadowMap.setShadowInfo(i, shadowInfo);
1345 }
1346
1347 lightShadows.startIdx = shadowInfo.textureIdx;
1348 lightShadows.numShadows = 1;
1349 }
1350
1351 void ShadowRendering::renderSpotShadowMap(const RendererLight& rendererLight, const ShadowMapOptions& options,
1352 RendererScene& scene, const FrameInfo& frameInfo)
1353 {
1354 Light* light = rendererLight.internal;
1355
1356 SPtr<GpuParamBlockBuffer> shadowParamsBuffer = gShadowParamsDef.createBuffer();
1357
1358 ShadowInfo mapInfo;
1359 mapInfo.fadePerView = options.fadePercents;
1360 mapInfo.lightIdx = options.lightIdx;
1361 mapInfo.cascadeIdx = -1;
1362
1363 bool foundSpace = false;
1364 for (UINT32 i = 0; i < (UINT32)mDynamicShadowMaps.size(); i++)
1365 {
1366 ShadowMapAtlas& atlas = mDynamicShadowMaps[i];
1367
1368 if (atlas.addMap(options.mapSize, mapInfo.area, SHADOW_MAP_BORDER))
1369 {
1370 mapInfo.textureIdx = i;
1371
1372 foundSpace = true;
1373 break;
1374 }
1375 }
1376
1377 if (!foundSpace)
1378 {
1379 mapInfo.textureIdx = (UINT32)mDynamicShadowMaps.size();
1380 mDynamicShadowMaps.push_back(ShadowMapAtlas(MAX_ATLAS_SIZE));
1381
1382 ShadowMapAtlas& atlas = mDynamicShadowMaps.back();
1383 atlas.addMap(options.mapSize, mapInfo.area, SHADOW_MAP_BORDER);
1384 }
1385
1386 mapInfo.updateNormArea(MAX_ATLAS_SIZE);
1387 ShadowMapAtlas& atlas = mDynamicShadowMaps[mapInfo.textureIdx];
1388
1389 ProfileGPUBlock profileSample("Project spot light shadows");
1390
1391 RenderAPI& rapi = RenderAPI::instance();
1392 rapi.setRenderTarget(atlas.getTarget());
1393 rapi.setViewport(mapInfo.normArea);
1394 rapi.clearViewport(FBT_DEPTH);
1395
1396 mapInfo.depthNear = 0.05f;
1397 mapInfo.depthFar = light->getAttenuationRadius();
1398 mapInfo.depthFade = mapInfo.depthFar;
1399 mapInfo.fadeRange = 0.0f;
1400 mapInfo.depthRange = mapInfo.depthFar - mapInfo.depthNear;
1401 mapInfo.depthBias = getDepthBias(*light, light->getBounds().getRadius(), mapInfo.depthRange, options.mapSize);
1402 mapInfo.subjectBounds = light->getBounds();
1403
1404 Quaternion lightRotation = light->getTransform().getRotation();
1405
1406 Matrix4 view = Matrix4::view(rendererLight.getShiftedLightPosition(), lightRotation);
1407 Matrix4 proj = Matrix4::projectionPerspective(light->getSpotAngle(), 1.0f, 0.05f, light->getAttenuationRadius());
1408
1409 ConvexVolume localFrustum = ConvexVolume(proj);
1410 RenderAPI::instance().convertProjectionMatrix(proj, proj);
1411
1412 mapInfo.shadowVPTransform = proj * view;
1413
1414 gShadowParamsDef.gDepthBias.set(shadowParamsBuffer, mapInfo.depthBias);
1415 gShadowParamsDef.gInvDepthRange.set(shadowParamsBuffer, 1.0f / mapInfo.depthRange);
1416 gShadowParamsDef.gMatViewProj.set(shadowParamsBuffer, mapInfo.shadowVPTransform);
1417 gShadowParamsDef.gNDCZToDeviceZ.set(shadowParamsBuffer, RendererView::getNDCZToDeviceZ());
1418
1419 const Vector<Plane>& frustumPlanes = localFrustum.getPlanes();
1420 Matrix4 worldMatrix = view.inverseAffine();
1421
1422 Vector<Plane> worldPlanes(frustumPlanes.size());
1423 UINT32 j = 0;
1424 for (auto& plane : frustumPlanes)
1425 {
1426 worldPlanes[j] = worldMatrix.multiplyAffine(plane);
1427 j++;
1428 }
1429
1430 ConvexVolume worldFrustum(worldPlanes);
1431
1432 // Render all renderables into the shadow map
1433 ShadowRenderQueueSpotOptions spotOptions(
1434 worldFrustum,
1435 shadowParamsBuffer);
1436
1437 ShadowRenderQueue::execute(scene, frameInfo, spotOptions);
1438
1439 // Restore viewport
1440 rapi.setViewport(Rect2(0.0f, 0.0f, 1.0f, 1.0f));
1441
1442 LightShadows& lightShadows = mSpotLightShadows[options.lightIdx];
1443
1444 mShadowInfos[lightShadows.startIdx + lightShadows.numShadows] = mapInfo;
1445 lightShadows.numShadows++;
1446 }
1447
1448 void ShadowRendering::renderRadialShadowMap(const RendererLight& rendererLight,
1449 const ShadowMapOptions& options, RendererScene& scene, const FrameInfo& frameInfo)
1450 {
1451 Light* light = rendererLight.internal;
1452
1453 SPtr<GpuParamBlockBuffer> shadowParamsBuffer = gShadowParamsDef.createBuffer();
1454
1455 ShadowInfo mapInfo;
1456 mapInfo.lightIdx = options.lightIdx;
1457 mapInfo.textureIdx = -1;
1458 mapInfo.fadePerView = options.fadePercents;
1459 mapInfo.cascadeIdx = -1;
1460 mapInfo.area = Rect2I(0, 0, options.mapSize, options.mapSize);
1461 mapInfo.updateNormArea(options.mapSize);
1462
1463 for (UINT32 i = 0; i < (UINT32)mShadowCubemaps.size(); i++)
1464 {
1465 ShadowCubemap& cubemap = mShadowCubemaps[i];
1466
1467 if (!cubemap.isUsed() && cubemap.getSize() == options.mapSize)
1468 {
1469 mapInfo.textureIdx = i;
1470 cubemap.markAsUsed();
1471
1472 break;
1473 }
1474 }
1475
1476 if (mapInfo.textureIdx == (UINT32)-1)
1477 {
1478 mapInfo.textureIdx = (UINT32)mShadowCubemaps.size();
1479 mShadowCubemaps.push_back(ShadowCubemap(options.mapSize));
1480
1481 ShadowCubemap& cubemap = mShadowCubemaps.back();
1482 cubemap.markAsUsed();
1483 }
1484
1485 ShadowCubemap& cubemap = mShadowCubemaps[mapInfo.textureIdx];
1486
1487 mapInfo.depthNear = 0.05f;
1488 mapInfo.depthFar = light->getAttenuationRadius();
1489 mapInfo.depthFade = mapInfo.depthFar;
1490 mapInfo.fadeRange = 0.0f;
1491 mapInfo.depthRange = mapInfo.depthFar - mapInfo.depthNear;
1492 mapInfo.depthBias = getDepthBias(*light, light->getBounds().getRadius(), mapInfo.depthRange, options.mapSize);
1493 mapInfo.subjectBounds = light->getBounds();
1494
1495 // Note: Projecting on positive Z axis, because cubemaps use a left-handed coordinate system
1496 Matrix4 proj = Matrix4::projectionPerspective(Degree(90.0f), 1.0f, 0.05f, light->getAttenuationRadius(), true);
1497 ConvexVolume localFrustum(proj);
1498
1499 ProfileGPUBlock profileSample("Project radial light shadows");
1500
1501 const RenderAPICapabilities& caps = gCaps();
1502 const Conventions& rapiConventions = gCaps().conventions;
1503
1504 RenderAPI& rapi = RenderAPI::instance();
1505 rapi.convertProjectionMatrix(proj, proj);
1506
1507 // Render cubemaps upside down if necessary
1508 Matrix4 adjustedProj = proj;
1509 if(caps.conventions.uvYAxis == Conventions::Axis::Up)
1510 {
1511 // All big APIs use the same cubemap sampling coordinates, as well as the same face order. But APIs that
1512 // use bottom-up UV coordinates require the cubemap faces to be stored upside down in order to get the same
1513 // behaviour. APIs that use an upside-down NDC Y axis have the same problem as the rendered image will be
1514 // upside down, but this is handled by the projection matrix. If both of those are enabled, then the effect
1515 // cancels out.
1516
1517 adjustedProj[1][1] = -proj[1][1];
1518 }
1519
1520 bool renderAllFacesAtOnce = caps.hasCapability(RSC_RENDER_TARGET_LAYERS);
1521
1522 SPtr<GpuParamBlockBuffer> shadowCubeMatricesBuffer;
1523 SPtr<GpuParamBlockBuffer> shadowCubeMasksBuffer;
1524 if(renderAllFacesAtOnce)
1525 {
1526 shadowCubeMatricesBuffer = gShadowCubeMatricesDef.createBuffer();
1527 shadowCubeMasksBuffer = gShadowCubeMasksDef.createBuffer();
1528 }
1529
1530 gShadowParamsDef.gDepthBias.set(shadowParamsBuffer, mapInfo.depthBias);
1531 gShadowParamsDef.gInvDepthRange.set(shadowParamsBuffer, 1.0f / mapInfo.depthRange);
1532 gShadowParamsDef.gMatViewProj.set(shadowParamsBuffer, Matrix4::IDENTITY);
1533 gShadowParamsDef.gNDCZToDeviceZ.set(shadowParamsBuffer, RendererView::getNDCZToDeviceZ());
1534
1535 ConvexVolume frustums[6];
1536 Vector<Plane> boundingPlanes;
1537 for (UINT32 i = 0; i < 6; i++)
1538 {
1539 // Calculate view matrix
1540 Vector3 forward;
1541 Vector3 up = Vector3::UNIT_Y;
1542
1543 switch (i)
1544 {
1545 case CF_PositiveX:
1546 forward = Vector3::UNIT_X;
1547 break;
1548 case CF_NegativeX:
1549 forward = -Vector3::UNIT_X;
1550 break;
1551 case CF_PositiveY:
1552 forward = Vector3::UNIT_Y;
1553 up = -Vector3::UNIT_Z;
1554 break;
1555 case CF_NegativeY:
1556 forward = -Vector3::UNIT_Y;
1557 up = Vector3::UNIT_Z;
1558 break;
1559 case CF_PositiveZ:
1560 forward = Vector3::UNIT_Z;
1561 break;
1562 case CF_NegativeZ:
1563 forward = -Vector3::UNIT_Z;
1564 break;
1565 }
1566
1567 Vector3 right = Vector3::cross(up, forward);
1568 Matrix3 viewRotationMat = Matrix3(right, up, forward);
1569
1570 Vector3 lightPos = light->getTransform().getPosition();
1571 Matrix4 viewOffsetMat = Matrix4::translation(-lightPos);
1572
1573 Matrix4 view = Matrix4(viewRotationMat.transpose()) * viewOffsetMat;
1574 mapInfo.shadowVPTransforms[i] = proj * view;
1575
1576 Matrix4 shadowViewProj = adjustedProj * view;
1577
1578 // Calculate world frustum for culling
1579 const Vector<Plane>& frustumPlanes = localFrustum.getPlanes();
1580
1581 Matrix4 worldMatrix = Matrix4::translation(lightPos) * Matrix4(viewRotationMat);
1582
1583 Vector<Plane> worldPlanes(frustumPlanes.size());
1584 UINT32 j = 0;
1585 for (auto& plane : frustumPlanes)
1586 {
1587 worldPlanes[j] = worldMatrix.multiplyAffine(plane);
1588 j++;
1589 }
1590
1591 ConvexVolume frustum(worldPlanes);
1592
1593 if(renderAllFacesAtOnce)
1594 {
1595 frustums[i] = frustum;
1596
1597 // Register far plane of all frustums
1598 boundingPlanes.push_back(worldPlanes[FRUSTUM_PLANE_FAR]);
1599 gShadowCubeMatricesDef.gFaceVPMatrices.set(shadowCubeMatricesBuffer, shadowViewProj, i);
1600 }
1601 else
1602 {
1603 gShadowParamsDef.gMatViewProj.set(shadowParamsBuffer, shadowViewProj);
1604
1605 RENDER_TEXTURE_DESC rtDesc;
1606 rtDesc.depthStencilSurface.texture = cubemap.getTexture();
1607 rtDesc.depthStencilSurface.face = i;
1608 rtDesc.depthStencilSurface.numFaces = 1;
1609
1610 SPtr<RenderTarget> faceRt = RenderTexture::create(rtDesc);
1611
1612 rapi.setRenderTarget(faceRt);
1613 rapi.clearRenderTarget(FBT_DEPTH);
1614
1615 // Render all renderables into the shadow map
1616 ConvexVolume boundingVolume(boundingPlanes);
1617 ShadowRenderQueueCubeSingleOptions cubeOptions(
1618 frustum,
1619 shadowParamsBuffer
1620 );
1621
1622 ShadowRenderQueue::execute(scene, frameInfo, cubeOptions);
1623 }
1624 }
1625
1626 if(renderAllFacesAtOnce)
1627 {
1628 rapi.setRenderTarget(cubemap.getTarget());
1629 rapi.clearRenderTarget(FBT_DEPTH);
1630
1631 // Render all renderables into the shadow map
1632 ConvexVolume boundingVolume(boundingPlanes);
1633 ShadowRenderQueueCubeOptions cubeOptions(
1634 frustums,
1635 boundingVolume,
1636 shadowParamsBuffer,
1637 shadowCubeMatricesBuffer,
1638 shadowCubeMasksBuffer
1639 );
1640
1641 ShadowRenderQueue::execute(scene, frameInfo, cubeOptions);
1642 }
1643
1644 LightShadows& lightShadows = mRadialLightShadows[options.lightIdx];
1645
1646 mShadowInfos[lightShadows.startIdx + lightShadows.numShadows] = mapInfo;
1647 lightShadows.numShadows++;
1648 }
1649
1650 void ShadowRendering::calcShadowMapProperties(const RendererLight& light, const RendererViewGroup& viewGroup,
1651 UINT32 border, UINT32& size, SmallVector<float, 6>& fadePercents, float& maxFadePercent) const
1652 {
1653 const static float SHADOW_TEXELS_PER_PIXEL = 1.0f;
1654
1655 // Find a view in which the light has the largest radius
1656 float maxMapSize = 0.0f;
1657 maxFadePercent = 0.0f;
1658 for (int i = 0; i < (int)viewGroup.getNumViews(); ++i)
1659 {
1660 const RendererView& view = *viewGroup.getView(i);
1661 const RendererViewProperties& viewProps = view.getProperties();
1662 const RenderSettings& viewSettings = view.getRenderSettings();
1663
1664 if(!viewSettings.enableShadows)
1665 fadePercents.add(0.0f);
1666 else
1667 {
1668 // Approximation for screen space sphere radius: screenSize * 0.5 * cot(fov) * radius / Z, where FOV is the
1669 // largest one
1670 //// First get sphere depth
1671 const Matrix4& viewVP = viewProps.viewProjTransform;
1672 float depth = viewVP.multiply(Vector4(light.internal->getTransform().getPosition(), 1.0f)).w;
1673
1674 // This is just 1/tan(fov), for both horz. and vert. FOV
1675 float viewScaleX = viewProps.projTransform[0][0];
1676 float viewScaleY = viewProps.projTransform[1][1];
1677
1678 float screenScaleX = viewScaleX * viewProps.target.viewRect.width * 0.5f;
1679 float screenScaleY = viewScaleY * viewProps.target.viewRect.height * 0.5f;
1680
1681 float screenScale = std::max(screenScaleX, screenScaleY);
1682
1683 //// Calc radius (clamp if too close to avoid massive numbers)
1684 float radiusNDC = light.internal->getBounds().getRadius() / std::max(depth, 1.0f);
1685
1686 //// Radius of light bounds in percent of the view surface, multiplied by screen size in pixels
1687 float radiusScreen = radiusNDC * screenScale;
1688
1689 float optimalMapSize = SHADOW_TEXELS_PER_PIXEL * radiusScreen;
1690 maxMapSize = std::max(maxMapSize, optimalMapSize);
1691
1692 // Determine if the shadow should fade out
1693 float fadePercent = Math::invLerp(optimalMapSize, (float)MIN_SHADOW_MAP_SIZE, (float)SHADOW_MAP_FADE_SIZE);
1694 fadePercents.add(fadePercent);
1695 maxFadePercent = std::max(maxFadePercent, fadePercent);
1696 }
1697 }
1698
1699 // If light fully (or nearly fully) covers the screen, use full shadow map resolution, otherwise
1700 // scale it down to smaller power of two, while clamping to minimal allowed resolution
1701 UINT32 effectiveMapSize = Bitwise::nextPow2((UINT32)maxMapSize);
1702 effectiveMapSize = Math::clamp(effectiveMapSize, MIN_SHADOW_MAP_SIZE, mShadowMapSize);
1703
1704 // Leave room for border
1705 size = std::max(effectiveMapSize - 2 * border, 1u);
1706 }
1707
1708 void ShadowRendering::drawNearFarPlanes(float near, float far, bool drawNear) const
1709 {
1710 const Conventions& rapiConventions = gCaps().conventions;
1711 float flipY = (rapiConventions.ndcYAxis == Conventions::Axis::Down) ? -1.0f : 1.0f;
1712
1713 // Update VB with new vertices
1714 Vector3 vertices[8] =
1715 {
1716 // Near plane
1717 { -1.0f, -1.0f * flipY, near },
1718 { 1.0f, -1.0f * flipY, near },
1719 { 1.0f, 1.0f * flipY, near },
1720 { -1.0f, 1.0f * flipY, near },
1721
1722 // Far plane
1723 { -1.0f, -1.0f * flipY, far },
1724 { 1.0f, -1.0f * flipY, far },
1725 { 1.0f, 1.0f * flipY, far },
1726 { -1.0f, 1.0f * flipY, far },
1727 };
1728
1729 mPlaneVB->writeData(0, sizeof(vertices), vertices, BWT_DISCARD);
1730
1731 // Draw the mesh
1732 RenderAPI& rapi = RenderAPI::instance();
1733 rapi.setVertexDeclaration(mPositionOnlyVD);
1734 rapi.setVertexBuffers(0, &mPlaneVB, 1);
1735 rapi.setIndexBuffer(mPlaneIB);
1736 rapi.setDrawOperation(DOT_TRIANGLE_LIST);
1737
1738 rapi.drawIndexed(0, drawNear ? 12 : 6, 0, drawNear ? 8 : 4);
1739 }
1740
1741 void ShadowRendering::drawFrustum(const std::array<Vector3, 8>& corners) const
1742 {
1743 RenderAPI& rapi = RenderAPI::instance();
1744
1745 // Update VB with new vertices
1746 mFrustumVB->writeData(0, sizeof(Vector3) * 8, corners.data(), BWT_DISCARD);
1747
1748 // Draw the mesh
1749 rapi.setVertexDeclaration(mPositionOnlyVD);
1750 rapi.setVertexBuffers(0, &mFrustumVB, 1);
1751 rapi.setIndexBuffer(mFrustumIB);
1752 rapi.setDrawOperation(DOT_TRIANGLE_LIST);
1753
1754 rapi.drawIndexed(0, 36, 0, 8);
1755 }
1756
1757 UINT32 ShadowRendering::getShadowQuality(UINT32 requestedQuality, UINT32 shadowMapResolution, UINT32 minAllowedQuality)
1758 {
1759 static const UINT32 TARGET_RESOLUTION = 512;
1760
1761 // If shadow map resolution is smaller than some target resolution drop the number of PCF samples (shadow quality)
1762 // so that the penumbra better matches with larger sized shadow maps.
1763 while(requestedQuality > minAllowedQuality && shadowMapResolution < TARGET_RESOLUTION)
1764 {
1765 shadowMapResolution *= 2;
1766 requestedQuality = std::max(requestedQuality - 1, 1U);
1767 }
1768
1769 return requestedQuality;
1770 }
1771
1772 ConvexVolume ShadowRendering::getCSMSplitFrustum(const RendererView& view, const Vector3& lightDir, UINT32 cascade,
1773 UINT32 numCascades, Sphere& outBounds)
1774 {
1775 // Determine split range
1776 float splitNear = getCSMSplitDistance(view, cascade, numCascades);
1777 float splitFar = getCSMSplitDistance(view, cascade + 1, numCascades);
1778
1779 // Increase by fade range, unless last cascade
1780 if ((UINT32)(cascade + 1) < numCascades)
1781 splitFar += CASCADE_FRACTION_FADE * (splitFar - splitNear);
1782
1783 // Calculate the eight vertices of the split frustum
1784 auto& viewProps = view.getProperties();
1785
1786 const Matrix4& projMat = viewProps.projTransform;
1787
1788 float aspect;
1789 float nearHalfWidth, nearHalfHeight;
1790 float farHalfWidth, farHalfHeight;
1791 if(viewProps.projType == PT_PERSPECTIVE)
1792 {
1793 aspect = fabs(projMat[0][0] / projMat[1][1]);
1794 float tanHalfFOV = 1.0f / projMat[0][0];
1795
1796 nearHalfWidth = splitNear * tanHalfFOV;
1797 nearHalfHeight = nearHalfWidth * aspect;
1798
1799 farHalfWidth = splitFar * tanHalfFOV;
1800 farHalfHeight = farHalfWidth * aspect;
1801 }
1802 else
1803 {
1804 aspect = projMat[0][0] / projMat[1][1];
1805
1806 nearHalfWidth = farHalfWidth = projMat[0][0] / 4.0f;
1807 nearHalfHeight = farHalfHeight = projMat[1][1] / 4.0f;
1808 }
1809
1810 const Matrix4& viewMat = viewProps.viewTransform;
1811 Vector3 cameraRight = Vector3(viewMat[0]);
1812 Vector3 cameraUp = Vector3(viewMat[1]);
1813
1814 const Vector3& viewOrigin = viewProps.viewOrigin;
1815 const Vector3& viewDir = viewProps.viewDirection;
1816
1817 Vector3 frustumVerts[] =
1818 {
1819 viewOrigin + viewDir * splitNear - cameraRight * nearHalfWidth + cameraUp * nearHalfHeight, // Near, left, top
1820 viewOrigin + viewDir * splitNear + cameraRight * nearHalfWidth + cameraUp * nearHalfHeight, // Near, right, top
1821 viewOrigin + viewDir * splitNear + cameraRight * nearHalfWidth - cameraUp * nearHalfHeight, // Near, right, bottom
1822 viewOrigin + viewDir * splitNear - cameraRight * nearHalfWidth - cameraUp * nearHalfHeight, // Near, left, bottom
1823 viewOrigin + viewDir * splitFar - cameraRight * farHalfWidth + cameraUp * farHalfHeight, // Far, left, top
1824 viewOrigin + viewDir * splitFar + cameraRight * farHalfWidth + cameraUp * farHalfHeight, // Far, right, top
1825 viewOrigin + viewDir * splitFar + cameraRight * farHalfWidth - cameraUp * farHalfHeight, // Far, right, bottom
1826 viewOrigin + viewDir * splitFar - cameraRight * farHalfWidth - cameraUp * farHalfHeight, // Far, left, bottom
1827 };
1828
1829 // Calculate the bounding sphere of the frustum
1830 float diagonalNearSq = nearHalfWidth * nearHalfWidth + nearHalfHeight * nearHalfHeight;
1831 float diagonalFarSq = farHalfWidth * farHalfWidth + farHalfHeight * farHalfHeight;
1832
1833 float length = splitFar - splitNear;
1834 float offset = (diagonalNearSq - diagonalFarSq) / (2 * length) + length * 0.5f;
1835 float distToCenter = Math::clamp(splitFar - offset, splitNear, splitFar);
1836
1837 Vector3 center = viewOrigin + viewDir * distToCenter;
1838
1839 float radius = 0.0f;
1840 for (auto& entry : frustumVerts)
1841 radius = std::max(radius, center.squaredDistance(entry));
1842
1843 radius = std::max((float)sqrt(radius), 1.0f);
1844 outBounds = Sphere(center, radius);
1845
1846 // Generate light frustum planes
1847 Plane viewPlanes[6];
1848 viewPlanes[FRUSTUM_PLANE_NEAR] = Plane(frustumVerts[0], frustumVerts[1], frustumVerts[2]);
1849 viewPlanes[FRUSTUM_PLANE_FAR] = Plane(frustumVerts[5], frustumVerts[4], frustumVerts[7]);
1850 viewPlanes[FRUSTUM_PLANE_LEFT] = Plane(frustumVerts[4], frustumVerts[0], frustumVerts[3]);
1851 viewPlanes[FRUSTUM_PLANE_RIGHT] = Plane(frustumVerts[1], frustumVerts[5], frustumVerts[6]);
1852 viewPlanes[FRUSTUM_PLANE_TOP] = Plane(frustumVerts[4], frustumVerts[5], frustumVerts[1]);
1853 viewPlanes[FRUSTUM_PLANE_BOTTOM] = Plane(frustumVerts[3], frustumVerts[2], frustumVerts[6]);
1854
1855 //// Add camera's planes facing towards the lights (forming the back of the volume)
1856 Vector<Plane> lightVolume;
1857 for(auto& entry : viewPlanes)
1858 {
1859 if (entry.normal.dot(lightDir) < 0.0f)
1860 lightVolume.push_back(entry);
1861 }
1862
1863 //// Determine edge planes by testing adjacent planes with different facing
1864 ////// Pairs of frustum planes that share an edge
1865 UINT32 adjacentPlanes[][2] =
1866 {
1867 { FRUSTUM_PLANE_NEAR, FRUSTUM_PLANE_LEFT },
1868 { FRUSTUM_PLANE_NEAR, FRUSTUM_PLANE_RIGHT },
1869 { FRUSTUM_PLANE_NEAR, FRUSTUM_PLANE_TOP },
1870 { FRUSTUM_PLANE_NEAR, FRUSTUM_PLANE_BOTTOM },
1871
1872 { FRUSTUM_PLANE_FAR, FRUSTUM_PLANE_LEFT },
1873 { FRUSTUM_PLANE_FAR, FRUSTUM_PLANE_RIGHT },
1874 { FRUSTUM_PLANE_FAR, FRUSTUM_PLANE_TOP },
1875 { FRUSTUM_PLANE_FAR, FRUSTUM_PLANE_BOTTOM },
1876
1877 { FRUSTUM_PLANE_LEFT, FRUSTUM_PLANE_TOP },
1878 { FRUSTUM_PLANE_TOP, FRUSTUM_PLANE_RIGHT },
1879 { FRUSTUM_PLANE_RIGHT, FRUSTUM_PLANE_BOTTOM },
1880 { FRUSTUM_PLANE_BOTTOM, FRUSTUM_PLANE_LEFT },
1881 };
1882
1883 ////// Vertex indices of edges on the boundary between two planes
1884 UINT32 sharedEdges[][2] =
1885 {
1886 { 3, 0 },{ 1, 2 },{ 0, 1 },{ 2, 3 },
1887 { 4, 7 },{ 6, 5 },{ 5, 4 },{ 7, 6 },
1888 { 4, 0 },{ 5, 1 },{ 6, 2 },{ 7, 3 }
1889 };
1890
1891 for(UINT32 i = 0; i < 12; i++)
1892 {
1893 const Plane& planeA = viewPlanes[adjacentPlanes[i][0]];
1894 const Plane& planeB = viewPlanes[adjacentPlanes[i][1]];
1895
1896 float dotA = planeA.normal.dot(lightDir);
1897 float dotB = planeB.normal.dot(lightDir);
1898
1899 if((dotA * dotB) < 0.0f)
1900 {
1901 const Vector3& vertA = frustumVerts[sharedEdges[i][0]];
1902 const Vector3& vertB = frustumVerts[sharedEdges[i][1]];
1903 Vector3 vertC = vertA + lightDir;
1904
1905 if (dotA < 0.0f)
1906 lightVolume.push_back(Plane(vertA, vertB, vertC));
1907 else
1908 lightVolume.push_back(Plane(vertB, vertA, vertC));
1909 }
1910 }
1911
1912 return ConvexVolume(lightVolume);
1913 }
1914
1915 float ShadowRendering::getCSMSplitDistance(const RendererView& view, UINT32 index, UINT32 numCascades)
1916 {
1917 auto& shadowSettings = view.getRenderSettings().shadowSettings;
1918 float distributionExponent = shadowSettings.cascadeDistributionExponent;
1919
1920 // First determine the scale of the split, relative to the entire range
1921 float scaleModifier = 1.0f;
1922 float scale = 0.0f;
1923 float totalScale = 0.0f;
1924
1925 //// Split 0 corresponds to near plane
1926 if (index > 0)
1927 {
1928 for (UINT32 i = 0; i < numCascades; i++)
1929 {
1930 if (i < index)
1931 scale += scaleModifier;
1932
1933 totalScale += scaleModifier;
1934 scaleModifier *= distributionExponent;
1935 }
1936
1937 scale = scale / totalScale;
1938 }
1939
1940 // Calculate split distance in Z
1941 auto& viewProps = view.getProperties();
1942 float near = viewProps.nearPlane;
1943 float far = Math::clamp(shadowSettings.directionalShadowDistance, viewProps.nearPlane, viewProps.farPlane);
1944
1945 return near + (far - near) * scale;
1946 }
1947
1948 float ShadowRendering::getDepthBias(const Light& light, float radius, float depthRange, UINT32 mapSize)
1949 {
1950 const static float RADIAL_LIGHT_BIAS = 0.005f;
1951 const static float SPOT_DEPTH_BIAS = 0.01f;
1952 const static float DIR_DEPTH_BIAS = 0.001f; // In clip space units
1953 const static float DEFAULT_RESOLUTION = 512.0f;
1954
1955 // Increase bias if map size smaller than some resolution
1956 float resolutionScale = 1.0f;
1957
1958 if (light.getType() != LightType::Directional)
1959 resolutionScale = DEFAULT_RESOLUTION / (float)mapSize;
1960
1961 // Adjust range because in shader we compare vs. clip space depth
1962 float rangeScale = 1.0f;
1963 if (light.getType() == LightType::Spot)
1964 rangeScale = 1.0f / depthRange;
1965
1966 const RenderAPICapabilities& caps = gCaps();
1967 float deviceDepthRange = caps.maxDepth - caps.minDepth;
1968
1969 float defaultBias = 1.0f;
1970 switch(light.getType())
1971 {
1972 case LightType::Directional:
1973 defaultBias = DIR_DEPTH_BIAS * deviceDepthRange;
1974
1975 // Use larger bias for further away cascades
1976 defaultBias *= depthRange * 0.01f;
1977 break;
1978 case LightType::Radial:
1979 defaultBias = RADIAL_LIGHT_BIAS;
1980 break;
1981 case LightType::Spot:
1982 defaultBias = SPOT_DEPTH_BIAS;
1983 break;
1984 default:
1985 break;
1986 }
1987
1988 return defaultBias * light.getShadowBias() * resolutionScale * rangeScale;
1989 }
1990
1991 float ShadowRendering::getFadeTransition(const Light& light, float radius, float depthRange, UINT32 mapSize)
1992 {
1993 const static float SPOT_LIGHT_SCALE = 1000.0f;
1994 const static float DIR_LIGHT_SCALE = 50000000.0f;
1995
1996 // Note: Currently fade transitions are only used in spot & directional (non omni-directional) lights, so no need
1997 // to account for radial light type.
1998 if (light.getType() == LightType::Directional)
1999 {
2000 // Just use a large value, as we want a minimal transition region
2001 return DIR_LIGHT_SCALE;
2002 }
2003 else
2004 return fabs(light.getShadowBias()) * SPOT_LIGHT_SCALE;
2005 }
2006}}
2007