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 | |
16 | namespace 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 | |