2022-01-04 14:29:01 +01:00
|
|
|
/* Copyright (C) 2022 Wildfire Games.
|
2009-04-18 19:00:33 +02:00
|
|
|
* This file is part of 0 A.D.
|
|
|
|
*
|
|
|
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* 0 A.D. is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2006-01-07 02:04:26 +01:00
|
|
|
#include "precompiled.h"
|
|
|
|
|
2019-08-04 05:52:00 +02:00
|
|
|
#include "ShadowMap.h"
|
2006-01-07 02:04:26 +01:00
|
|
|
|
2019-08-04 07:58:55 +02:00
|
|
|
#include "graphics/Camera.h"
|
2021-03-25 13:09:56 +01:00
|
|
|
#include "graphics/LightEnv.h"
|
2012-02-12 14:20:49 +01:00
|
|
|
#include "graphics/ShaderManager.h"
|
2019-08-04 05:52:00 +02:00
|
|
|
#include "lib/bits.h"
|
2011-11-25 07:36:13 +01:00
|
|
|
#include "maths/BoundingBoxAligned.h"
|
2014-06-25 03:11:10 +02:00
|
|
|
#include "maths/Brush.h"
|
2021-03-29 19:28:13 +02:00
|
|
|
#include "maths/Frustum.h"
|
2006-01-07 02:04:26 +01:00
|
|
|
#include "maths/MathUtil.h"
|
|
|
|
#include "maths/Matrix3D.h"
|
2019-08-04 05:52:00 +02:00
|
|
|
#include "ps/CLogger.h"
|
|
|
|
#include "ps/ConfigDB.h"
|
2021-05-18 13:09:54 +02:00
|
|
|
#include "ps/CStrInternStatic.h"
|
2019-08-04 05:52:00 +02:00
|
|
|
#include "ps/Profile.h"
|
2021-12-14 07:34:02 +01:00
|
|
|
#include "ps/VideoMode.h"
|
2022-05-09 00:02:46 +02:00
|
|
|
#include "renderer/backend/IDevice.h"
|
|
|
|
#include "renderer/backend/ITexture.h"
|
2021-04-15 20:07:01 +02:00
|
|
|
#include "renderer/DebugRenderer.h"
|
2006-01-07 02:04:26 +01:00
|
|
|
#include "renderer/Renderer.h"
|
2019-08-04 10:28:30 +02:00
|
|
|
#include "renderer/RenderingOptions.h"
|
2022-01-04 14:29:01 +01:00
|
|
|
#include "renderer/SceneRenderer.h"
|
2006-01-07 02:04:26 +01:00
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
#include <array>
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
|
|
|
|
constexpr int MAX_CASCADE_COUNT = 4;
|
|
|
|
|
|
|
|
constexpr float DEFAULT_SHADOWS_CUTOFF_DISTANCE = 300.0f;
|
|
|
|
constexpr float DEFAULT_CASCADE_DISTANCE_RATIO = 1.7f;
|
|
|
|
|
|
|
|
} // anonymous namespace
|
|
|
|
|
2006-01-07 02:04:26 +01:00
|
|
|
/**
|
|
|
|
* Struct ShadowMapInternals: Internal data for the ShadowMap implementation
|
|
|
|
*/
|
|
|
|
struct ShadowMapInternals
|
|
|
|
{
|
2022-05-09 00:02:46 +02:00
|
|
|
std::unique_ptr<Renderer::Backend::IFramebuffer> Framebuffer;
|
|
|
|
std::unique_ptr<Renderer::Backend::ITexture> Texture;
|
2021-06-06 18:44:54 +02:00
|
|
|
|
|
|
|
// bit depth for the depth texture
|
|
|
|
int DepthTextureBits;
|
2006-01-07 02:04:26 +01:00
|
|
|
// width, height of shadow map
|
2007-02-09 18:04:55 +01:00
|
|
|
int Width, Height;
|
2021-06-06 18:44:54 +02:00
|
|
|
// Shadow map quality (-1 - Low, 0 - Medium, 1 - High, 2 - Very High)
|
2017-08-19 14:46:05 +02:00
|
|
|
int QualityLevel;
|
2006-03-26 19:36:33 +02:00
|
|
|
// used width, height of shadow map
|
2007-02-09 18:04:55 +01:00
|
|
|
int EffectiveWidth, EffectiveHeight;
|
2021-06-06 18:44:54 +02:00
|
|
|
|
2006-02-11 01:26:40 +01:00
|
|
|
// Transform world space into light space; calculated on SetupFrame
|
2006-01-07 02:04:26 +01:00
|
|
|
CMatrix3D LightTransform;
|
2006-01-29 18:34:45 +01:00
|
|
|
|
|
|
|
// transform light space into world space
|
|
|
|
CMatrix3D InvLightTransform;
|
2014-06-25 03:11:10 +02:00
|
|
|
CBoundingBoxAligned ShadowReceiverBound;
|
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
int CascadeCount;
|
|
|
|
float CascadeDistanceRatio;
|
|
|
|
float ShadowsCutoffDistance;
|
|
|
|
bool ShadowsCoverMap;
|
2006-01-29 18:34:45 +01:00
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
struct Cascade
|
|
|
|
{
|
|
|
|
// transform light space into projected light space
|
|
|
|
// in projected light space, the shadowbound box occupies the [-1..1] cube
|
|
|
|
// calculated on BeginRender, after the final shadow bounds are known
|
|
|
|
CMatrix3D LightProjection;
|
|
|
|
float Distance;
|
|
|
|
CBoundingBoxAligned FrustumBBAA;
|
|
|
|
CBoundingBoxAligned ConvexBounds;
|
|
|
|
CBoundingBoxAligned ShadowRenderBound;
|
|
|
|
// Bounding box of shadowed objects in the light space.
|
|
|
|
CBoundingBoxAligned ShadowCasterBound;
|
|
|
|
// Transform world space into texture space of the shadow map;
|
|
|
|
// calculated on BeginRender, after the final shadow bounds are known
|
|
|
|
CMatrix3D TextureMatrix;
|
|
|
|
// View port of the shadow texture where the cascade should be rendered.
|
|
|
|
SViewPort ViewPort;
|
|
|
|
};
|
|
|
|
std::array<Cascade, MAX_CASCADE_COUNT> Cascades;
|
2020-01-05 23:44:39 +01:00
|
|
|
|
2006-03-26 23:58:48 +02:00
|
|
|
// Camera transformed into light space
|
|
|
|
CCamera LightspaceCamera;
|
|
|
|
|
2011-03-07 01:34:03 +01:00
|
|
|
// Some drivers (at least some Intel Mesa ones) appear to handle alpha testing
|
|
|
|
// incorrectly when the FBO has only a depth attachment.
|
|
|
|
// When m_ShadowAlphaFix is true, we use DummyTexture to store a useless
|
|
|
|
// alpha texture which is attached to the FBO as a workaround.
|
2022-05-09 00:02:46 +02:00
|
|
|
std::unique_ptr<Renderer::Backend::ITexture> DummyTexture;
|
2011-03-07 01:34:03 +01:00
|
|
|
|
2012-03-19 22:10:14 +01:00
|
|
|
// Copy of renderer's standard view camera, saved between
|
|
|
|
// BeginRender and EndRender while we replace it with the shadow camera
|
|
|
|
CCamera SavedViewCamera;
|
2016-11-23 15:09:58 +01:00
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
void CalculateShadowMatrices(const int cascade);
|
2006-02-11 01:26:40 +01:00
|
|
|
void CreateTexture();
|
2021-06-06 18:44:54 +02:00
|
|
|
void UpdateCascadesParameters();
|
2006-01-07 02:04:26 +01:00
|
|
|
};
|
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
void ShadowMapInternals::UpdateCascadesParameters()
|
|
|
|
{
|
|
|
|
CascadeCount = 1;
|
|
|
|
CFG_GET_VAL("shadowscascadecount", CascadeCount);
|
|
|
|
|
2021-12-14 07:34:02 +01:00
|
|
|
if (CascadeCount < 1 || CascadeCount > MAX_CASCADE_COUNT || g_VideoMode.GetBackend() == CVideoMode::Backend::GL_ARB)
|
2021-06-06 18:44:54 +02:00
|
|
|
CascadeCount = 1;
|
|
|
|
|
|
|
|
ShadowsCoverMap = false;
|
|
|
|
CFG_GET_VAL("shadowscovermap", ShadowsCoverMap);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CalculateBoundsForCascade(
|
2020-06-12 22:09:08 +02:00
|
|
|
const CCamera& camera, const CMatrix3D& lightTransform,
|
2021-06-06 18:44:54 +02:00
|
|
|
const float nearPlane, const float farPlane, CBoundingBoxAligned* bbaa,
|
|
|
|
CBoundingBoxAligned* frustumBBAA)
|
2020-06-12 22:09:08 +02:00
|
|
|
{
|
2021-06-06 18:44:54 +02:00
|
|
|
frustumBBAA->SetEmpty();
|
|
|
|
|
2020-06-12 22:09:08 +02:00
|
|
|
// We need to calculate a circumscribed sphere for the camera to
|
|
|
|
// create a rotation stable bounding box.
|
|
|
|
const CVector3D cameraIn = camera.m_Orientation.GetIn();
|
|
|
|
const CVector3D cameraTranslation = camera.m_Orientation.GetTranslation();
|
|
|
|
const CVector3D centerNear = cameraTranslation + cameraIn * nearPlane;
|
|
|
|
const CVector3D centerDist = cameraTranslation + cameraIn * farPlane;
|
|
|
|
|
|
|
|
// We can solve 3D problem in 2D space, because the frustum is
|
|
|
|
// symmetric by 2 planes. Than means we can use only one corner
|
|
|
|
// to find a circumscribed sphere.
|
|
|
|
CCamera::Quad corners;
|
2021-06-06 18:44:54 +02:00
|
|
|
|
2020-06-12 22:09:08 +02:00
|
|
|
camera.GetViewQuad(nearPlane, corners);
|
2021-06-06 18:44:54 +02:00
|
|
|
for (CVector3D& corner : corners)
|
|
|
|
corner = camera.GetOrientation().Transform(corner);
|
|
|
|
const CVector3D cornerNear = corners[0];
|
|
|
|
for (const CVector3D& corner : corners)
|
|
|
|
*frustumBBAA += lightTransform.Transform(corner);
|
|
|
|
|
2020-06-12 22:09:08 +02:00
|
|
|
camera.GetViewQuad(farPlane, corners);
|
2021-06-06 18:44:54 +02:00
|
|
|
for (CVector3D& corner : corners)
|
|
|
|
corner = camera.GetOrientation().Transform(corner);
|
|
|
|
const CVector3D cornerDist = corners[0];
|
|
|
|
for (const CVector3D& corner : corners)
|
|
|
|
*frustumBBAA += lightTransform.Transform(corner);
|
2020-06-12 22:09:08 +02:00
|
|
|
|
|
|
|
// We solve 2D case for the right trapezoid.
|
|
|
|
const float firstBase = (cornerNear - centerNear).Length();
|
|
|
|
const float secondBase = (cornerDist - centerDist).Length();
|
|
|
|
const float height = (centerDist - centerNear).Length();
|
|
|
|
const float distanceToCenter =
|
|
|
|
(height * height + secondBase * secondBase - firstBase * firstBase) * 0.5f / height;
|
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
CVector3D position = cameraTranslation + cameraIn * (nearPlane + distanceToCenter);
|
2020-06-12 22:09:08 +02:00
|
|
|
const float radius = (cornerNear - position).Length();
|
|
|
|
|
|
|
|
// We need to convert the bounding box to the light space.
|
|
|
|
position = lightTransform.Rotate(position);
|
|
|
|
|
|
|
|
const float insets = 0.2f;
|
|
|
|
*bbaa = CBoundingBoxAligned(position, position);
|
|
|
|
bbaa->Expand(radius);
|
|
|
|
bbaa->Expand(insets);
|
|
|
|
}
|
2006-01-07 02:04:26 +01:00
|
|
|
|
|
|
|
ShadowMap::ShadowMap()
|
|
|
|
{
|
|
|
|
m = new ShadowMapInternals;
|
2006-03-26 19:36:33 +02:00
|
|
|
m->Framebuffer = 0;
|
2006-02-11 19:04:32 +01:00
|
|
|
m->Width = 0;
|
|
|
|
m->Height = 0;
|
2017-08-19 14:46:05 +02:00
|
|
|
m->QualityLevel = 0;
|
2006-03-26 19:36:33 +02:00
|
|
|
m->EffectiveWidth = 0;
|
|
|
|
m->EffectiveHeight = 0;
|
2007-02-10 21:53:48 +01:00
|
|
|
m->DepthTextureBits = 0;
|
|
|
|
// DepthTextureBits: 24/32 are very much faster than 16, on GeForce 4 and FX;
|
|
|
|
// but they're very much slower on Radeon 9800.
|
|
|
|
// In both cases, the default (no specified depth) is fast, so we just use
|
|
|
|
// that by default and hope it's alright. (Otherwise, we'd probably need to
|
|
|
|
// do some kind of hardware detection to work out what to use.)
|
2011-03-14 22:54:44 +01:00
|
|
|
|
|
|
|
// Avoid using uninitialised values in AddShadowedBound if SetupFrame wasn't called first
|
|
|
|
m->LightTransform.SetIdentity();
|
2020-01-05 23:44:39 +01:00
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
m->UpdateCascadesParameters();
|
2006-01-07 02:04:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ShadowMap::~ShadowMap()
|
|
|
|
{
|
2022-02-05 17:59:23 +01:00
|
|
|
m->Framebuffer.reset();
|
2021-12-26 10:48:48 +01:00
|
|
|
m->Texture.reset();
|
|
|
|
m->DummyTexture.reset();
|
|
|
|
|
2006-01-07 02:04:26 +01:00
|
|
|
delete m;
|
|
|
|
}
|
|
|
|
|
2006-05-31 18:42:50 +02:00
|
|
|
// Force the texture/buffer/etc to be recreated, particularly when the renderer's
|
|
|
|
// size has changed
|
|
|
|
void ShadowMap::RecreateTexture()
|
|
|
|
{
|
2022-02-05 17:59:23 +01:00
|
|
|
m->Framebuffer.reset();
|
2021-12-26 10:48:48 +01:00
|
|
|
m->Texture.reset();
|
|
|
|
m->DummyTexture.reset();
|
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
m->UpdateCascadesParameters();
|
|
|
|
|
2006-05-31 18:42:50 +02:00
|
|
|
// (Texture will be constructed in next SetupFrame)
|
|
|
|
}
|
|
|
|
|
2006-03-26 19:36:33 +02:00
|
|
|
// SetupFrame: camera and light direction for this frame
|
2006-02-11 01:26:40 +01:00
|
|
|
void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir)
|
2006-01-29 18:34:45 +01:00
|
|
|
{
|
2006-02-11 01:26:40 +01:00
|
|
|
if (!m->Texture)
|
|
|
|
m->CreateTexture();
|
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
CVector3D x(0, 1, 0), eyepos;
|
2006-01-29 18:34:45 +01:00
|
|
|
|
2020-01-05 23:44:39 +01:00
|
|
|
CVector3D z = lightdir;
|
2006-01-29 18:34:45 +01:00
|
|
|
z.Normalize();
|
|
|
|
x -= z * z.Dot(x);
|
2007-05-02 14:07:08 +02:00
|
|
|
if (x.Length() < 0.001)
|
2006-01-29 18:34:45 +01:00
|
|
|
{
|
|
|
|
// this is invoked if the camera and light directions almost coincide
|
|
|
|
// assumption: light direction has a significant Z component
|
|
|
|
x = CVector3D(1.0, 0.0, 0.0);
|
|
|
|
x -= z * z.Dot(x);
|
|
|
|
}
|
|
|
|
x.Normalize();
|
2020-01-05 23:44:39 +01:00
|
|
|
CVector3D y = z.Cross(x);
|
2006-01-29 18:34:45 +01:00
|
|
|
|
|
|
|
// X axis perpendicular to light direction, flowing along with view direction
|
2006-02-11 01:26:40 +01:00
|
|
|
m->LightTransform._11 = x.X;
|
|
|
|
m->LightTransform._12 = x.Y;
|
|
|
|
m->LightTransform._13 = x.Z;
|
2006-01-29 18:34:45 +01:00
|
|
|
|
|
|
|
// Y axis perpendicular to light and view direction
|
2006-02-11 01:26:40 +01:00
|
|
|
m->LightTransform._21 = y.X;
|
|
|
|
m->LightTransform._22 = y.Y;
|
|
|
|
m->LightTransform._23 = y.Z;
|
2006-01-29 18:34:45 +01:00
|
|
|
|
|
|
|
// Z axis is in direction of light
|
2006-02-11 01:26:40 +01:00
|
|
|
m->LightTransform._31 = z.X;
|
|
|
|
m->LightTransform._32 = z.Y;
|
|
|
|
m->LightTransform._33 = z.Z;
|
2006-01-29 18:34:45 +01:00
|
|
|
|
|
|
|
// eye is at the origin of the coordinate system
|
2006-02-11 01:26:40 +01:00
|
|
|
m->LightTransform._14 = -x.Dot(eyepos);
|
|
|
|
m->LightTransform._24 = -y.Dot(eyepos);
|
|
|
|
m->LightTransform._34 = -z.Dot(eyepos);
|
2006-01-29 18:34:45 +01:00
|
|
|
|
2006-02-11 01:26:40 +01:00
|
|
|
m->LightTransform._41 = 0.0;
|
|
|
|
m->LightTransform._42 = 0.0;
|
|
|
|
m->LightTransform._43 = 0.0;
|
|
|
|
m->LightTransform._44 = 1.0;
|
2006-01-29 18:34:45 +01:00
|
|
|
|
2006-02-11 01:26:40 +01:00
|
|
|
m->LightTransform.GetInverse(m->InvLightTransform);
|
2014-06-25 03:11:10 +02:00
|
|
|
m->ShadowReceiverBound.SetEmpty();
|
2006-03-26 23:58:48 +02:00
|
|
|
|
|
|
|
m->LightspaceCamera = camera;
|
|
|
|
m->LightspaceCamera.m_Orientation = m->LightTransform * camera.m_Orientation;
|
|
|
|
m->LightspaceCamera.UpdateFrustum();
|
2020-01-05 23:44:39 +01:00
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
m->ShadowsCutoffDistance = DEFAULT_SHADOWS_CUTOFF_DISTANCE;
|
|
|
|
m->CascadeDistanceRatio = DEFAULT_CASCADE_DISTANCE_RATIO;
|
|
|
|
CFG_GET_VAL("shadowscutoffdistance", m->ShadowsCutoffDistance);
|
|
|
|
CFG_GET_VAL("shadowscascadedistanceratio", m->CascadeDistanceRatio);
|
|
|
|
m->CascadeDistanceRatio = Clamp(m->CascadeDistanceRatio, 1.1f, 16.0f);
|
|
|
|
|
|
|
|
m->Cascades[GetCascadeCount() - 1].Distance = m->ShadowsCutoffDistance;
|
|
|
|
for (int cascade = GetCascadeCount() - 2; cascade >= 0; --cascade)
|
|
|
|
m->Cascades[cascade].Distance = m->Cascades[cascade + 1].Distance / m->CascadeDistanceRatio;
|
|
|
|
|
|
|
|
if (GetCascadeCount() == 1 || m->ShadowsCoverMap)
|
|
|
|
{
|
|
|
|
m->Cascades[0].ViewPort =
|
|
|
|
SViewPort{1, 1, m->EffectiveWidth - 2, m->EffectiveHeight - 2};
|
|
|
|
if (m->ShadowsCoverMap)
|
|
|
|
m->Cascades[0].Distance = camera.GetFarPlane();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (int cascade = 0; cascade < GetCascadeCount(); ++cascade)
|
|
|
|
{
|
|
|
|
const int offsetX = (cascade & 0x1) ? m->EffectiveWidth / 2 : 0;
|
|
|
|
const int offsetY = (cascade & 0x2) ? m->EffectiveHeight / 2 : 0;
|
|
|
|
m->Cascades[cascade].ViewPort =
|
|
|
|
SViewPort{offsetX + 1, offsetY + 1,
|
|
|
|
m->EffectiveWidth / 2 - 2, m->EffectiveHeight / 2 - 2};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int cascadeIdx = 0; cascadeIdx < GetCascadeCount(); ++cascadeIdx)
|
|
|
|
{
|
|
|
|
ShadowMapInternals::Cascade& cascade = m->Cascades[cascadeIdx];
|
|
|
|
|
|
|
|
const float nearPlane = cascadeIdx > 0 ?
|
|
|
|
m->Cascades[cascadeIdx - 1].Distance : camera.GetNearPlane();
|
|
|
|
const float farPlane = cascade.Distance;
|
|
|
|
|
|
|
|
CalculateBoundsForCascade(camera, m->LightTransform,
|
|
|
|
nearPlane, farPlane, &cascade.ConvexBounds, &cascade.FrustumBBAA);
|
|
|
|
cascade.ShadowCasterBound.SetEmpty();
|
|
|
|
}
|
2006-01-29 18:34:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// AddShadowedBound: add a world-space bounding box to the bounds of shadowed
|
|
|
|
// objects
|
2021-06-06 18:44:54 +02:00
|
|
|
void ShadowMap::AddShadowCasterBound(const int cascade, const CBoundingBoxAligned& bounds)
|
2014-06-25 03:11:10 +02:00
|
|
|
{
|
|
|
|
CBoundingBoxAligned lightspacebounds;
|
|
|
|
|
|
|
|
bounds.Transform(m->LightTransform, lightspacebounds);
|
2021-06-06 18:44:54 +02:00
|
|
|
m->Cascades[cascade].ShadowCasterBound += lightspacebounds;
|
2014-06-25 03:11:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void ShadowMap::AddShadowReceiverBound(const CBoundingBoxAligned& bounds)
|
2006-01-29 18:34:45 +01:00
|
|
|
{
|
2011-11-25 07:36:13 +01:00
|
|
|
CBoundingBoxAligned lightspacebounds;
|
2006-01-29 18:34:45 +01:00
|
|
|
|
2006-02-11 01:26:40 +01:00
|
|
|
bounds.Transform(m->LightTransform, lightspacebounds);
|
2014-06-25 03:11:10 +02:00
|
|
|
m->ShadowReceiverBound += lightspacebounds;
|
2006-01-07 02:04:26 +01:00
|
|
|
}
|
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
CFrustum ShadowMap::GetShadowCasterCullFrustum(const int cascade)
|
2014-06-25 03:11:10 +02:00
|
|
|
{
|
|
|
|
// Get the bounds of all objects that can receive shadows
|
|
|
|
CBoundingBoxAligned bound = m->ShadowReceiverBound;
|
|
|
|
|
|
|
|
// Intersect with the camera frustum, so the shadow map doesn't have to get
|
|
|
|
// stretched to cover the off-screen parts of large models
|
2021-06-06 18:44:54 +02:00
|
|
|
bound.IntersectFrustumConservative(m->Cascades[cascade].FrustumBBAA.ToFrustum());
|
2014-06-25 03:11:10 +02:00
|
|
|
|
|
|
|
// ShadowBound might have been empty to begin with, producing an empty result
|
|
|
|
if (bound.IsEmpty())
|
|
|
|
{
|
|
|
|
// CFrustum can't easily represent nothingness, so approximate it with
|
|
|
|
// a single point which won't match many objects
|
|
|
|
bound += CVector3D(0.0f, 0.0f, 0.0f);
|
|
|
|
return bound.ToFrustum();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extend the bounds a long way towards the light source, to encompass
|
|
|
|
// all objects that might cast visible shadows.
|
|
|
|
// (The exact constant was picked entirely arbitrarily.)
|
|
|
|
bound[0].Z -= 1000.f;
|
|
|
|
|
|
|
|
CFrustum frustum = bound.ToFrustum();
|
|
|
|
frustum.Transform(m->InvLightTransform);
|
|
|
|
return frustum;
|
|
|
|
}
|
2006-01-07 02:04:26 +01:00
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
// CalculateShadowMatrices: calculate required matrices for shadow map generation - the light's
|
2006-01-07 02:04:26 +01:00
|
|
|
// projection and transformation matrices
|
2021-06-06 18:44:54 +02:00
|
|
|
void ShadowMapInternals::CalculateShadowMatrices(const int cascade)
|
2006-01-07 02:04:26 +01:00
|
|
|
{
|
2021-06-06 18:44:54 +02:00
|
|
|
CBoundingBoxAligned& shadowRenderBound = Cascades[cascade].ShadowRenderBound;
|
|
|
|
shadowRenderBound = Cascades[cascade].ConvexBounds;
|
|
|
|
|
|
|
|
if (ShadowsCoverMap)
|
2020-01-05 23:44:39 +01:00
|
|
|
{
|
|
|
|
// Start building the shadow map to cover all objects that will receive shadows
|
|
|
|
CBoundingBoxAligned receiverBound = ShadowReceiverBound;
|
|
|
|
|
|
|
|
// Intersect with the camera frustum, so the shadow map doesn't have to get
|
|
|
|
// stretched to cover the off-screen parts of large models
|
|
|
|
receiverBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum());
|
|
|
|
|
|
|
|
// Intersect with the shadow caster bounds, because there's no point
|
|
|
|
// wasting space around the edges of the shadow map that we're not going
|
|
|
|
// to draw into
|
2021-06-06 18:44:54 +02:00
|
|
|
shadowRenderBound[0].X = std::max(receiverBound[0].X, Cascades[cascade].ShadowCasterBound[0].X);
|
|
|
|
shadowRenderBound[0].Y = std::max(receiverBound[0].Y, Cascades[cascade].ShadowCasterBound[0].Y);
|
|
|
|
shadowRenderBound[1].X = std::min(receiverBound[1].X, Cascades[cascade].ShadowCasterBound[1].X);
|
|
|
|
shadowRenderBound[1].Y = std::min(receiverBound[1].Y, Cascades[cascade].ShadowCasterBound[1].Y);
|
|
|
|
}
|
|
|
|
else if (CascadeCount > 1)
|
|
|
|
{
|
|
|
|
// We need to offset the cascade to its place on the texture.
|
|
|
|
const CVector3D size = (shadowRenderBound[1] - shadowRenderBound[0]) * 0.5f;
|
|
|
|
if (!(cascade & 0x1))
|
|
|
|
shadowRenderBound[1].X += size.X * 2.0f;
|
|
|
|
else
|
|
|
|
shadowRenderBound[0].X -= size.X * 2.0f;
|
|
|
|
if (!(cascade & 0x2))
|
|
|
|
shadowRenderBound[1].Y += size.Y * 2.0f;
|
|
|
|
else
|
|
|
|
shadowRenderBound[0].Y -= size.Y * 2.0f;
|
2020-01-05 23:44:39 +01:00
|
|
|
}
|
2006-03-26 23:58:48 +02:00
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
// Set the near and far planes to include just the shadow casters,
|
|
|
|
// so we make full use of the depth texture's range. Add a bit of a
|
|
|
|
// delta so we don't accidentally clip objects that are directly on
|
|
|
|
// the planes.
|
|
|
|
shadowRenderBound[0].Z = Cascades[cascade].ShadowCasterBound[0].Z - 2.f;
|
|
|
|
shadowRenderBound[1].Z = Cascades[cascade].ShadowCasterBound[1].Z + 2.f;
|
|
|
|
|
2006-02-11 01:26:40 +01:00
|
|
|
// Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering
|
2021-06-06 18:44:54 +02:00
|
|
|
CVector3D scale = shadowRenderBound[1] - shadowRenderBound[0];
|
|
|
|
CVector3D shift = (shadowRenderBound[1] + shadowRenderBound[0]) * -0.5;
|
2006-01-07 02:04:26 +01:00
|
|
|
|
2006-02-11 01:26:40 +01:00
|
|
|
if (scale.X < 1.0)
|
|
|
|
scale.X = 1.0;
|
|
|
|
if (scale.Y < 1.0)
|
|
|
|
scale.Y = 1.0;
|
|
|
|
if (scale.Z < 1.0)
|
|
|
|
scale.Z = 1.0;
|
2006-01-07 02:04:26 +01:00
|
|
|
|
2006-02-11 01:26:40 +01:00
|
|
|
scale.X = 2.0 / scale.X;
|
|
|
|
scale.Y = 2.0 / scale.Y;
|
|
|
|
scale.Z = 2.0 / scale.Z;
|
2006-01-07 02:04:26 +01:00
|
|
|
|
2011-07-13 01:48:05 +02:00
|
|
|
// make sure a given world position falls on a consistent shadowmap texel fractional offset
|
2021-06-06 18:44:54 +02:00
|
|
|
float offsetX = fmod(shadowRenderBound[0].X - LightTransform._14, 2.0f/(scale.X*EffectiveWidth));
|
|
|
|
float offsetY = fmod(shadowRenderBound[0].Y - LightTransform._24, 2.0f/(scale.Y*EffectiveHeight));
|
|
|
|
|
|
|
|
CMatrix3D& lightProjection = Cascades[cascade].LightProjection;
|
|
|
|
lightProjection.SetZero();
|
|
|
|
lightProjection._11 = scale.X;
|
|
|
|
lightProjection._14 = (shift.X + offsetX) * scale.X;
|
|
|
|
lightProjection._22 = scale.Y;
|
|
|
|
lightProjection._24 = (shift.Y + offsetY) * scale.Y;
|
|
|
|
lightProjection._33 = scale.Z;
|
|
|
|
lightProjection._34 = shift.Z * scale.Z;
|
|
|
|
lightProjection._44 = 1.0;
|
2006-02-11 01:26:40 +01:00
|
|
|
|
|
|
|
// Calculate texture matrix by creating the clip space to texture coordinate matrix
|
|
|
|
// and then concatenating all matrices that have been calculated so far
|
2012-04-06 00:29:01 +02:00
|
|
|
|
2011-07-13 01:48:05 +02:00
|
|
|
float texscalex = scale.X * 0.5f * (float)EffectiveWidth / (float)Width;
|
|
|
|
float texscaley = scale.Y * 0.5f * (float)EffectiveHeight / (float)Height;
|
|
|
|
float texscalez = scale.Z * 0.5f;
|
2006-02-13 15:18:20 +01:00
|
|
|
|
2012-04-06 00:29:01 +02:00
|
|
|
CMatrix3D lightToTex;
|
2006-02-13 15:18:20 +01:00
|
|
|
lightToTex.SetZero();
|
|
|
|
lightToTex._11 = texscalex;
|
2021-06-06 18:44:54 +02:00
|
|
|
lightToTex._14 = (offsetX - shadowRenderBound[0].X) * texscalex;
|
2006-02-13 15:18:20 +01:00
|
|
|
lightToTex._22 = texscaley;
|
2021-06-06 18:44:54 +02:00
|
|
|
lightToTex._24 = (offsetY - shadowRenderBound[0].Y) * texscaley;
|
2006-02-13 15:18:20 +01:00
|
|
|
lightToTex._33 = texscalez;
|
2021-06-06 18:44:54 +02:00
|
|
|
lightToTex._34 = -shadowRenderBound[0].Z * texscalez;
|
2006-02-13 15:18:20 +01:00
|
|
|
lightToTex._44 = 1.0;
|
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
Cascades[cascade].TextureMatrix = lightToTex * LightTransform;
|
2006-01-07 02:04:26 +01:00
|
|
|
}
|
|
|
|
|
2006-02-11 01:26:40 +01:00
|
|
|
// Create the shadow map
|
|
|
|
void ShadowMapInternals::CreateTexture()
|
2006-01-07 02:04:26 +01:00
|
|
|
{
|
2006-03-26 19:36:33 +02:00
|
|
|
// Cleanup
|
2022-02-05 17:59:23 +01:00
|
|
|
Framebuffer.reset();
|
2021-12-26 10:48:48 +01:00
|
|
|
Texture.reset();
|
|
|
|
DummyTexture.reset();
|
|
|
|
|
2022-05-09 00:02:46 +02:00
|
|
|
Renderer::Backend::IDevice* backendDevice = g_VideoMode.GetBackendDevice();
|
2022-02-19 00:17:48 +01:00
|
|
|
|
2021-04-14 22:25:05 +02:00
|
|
|
CFG_GET_VAL("shadowquality", QualityLevel);
|
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
// Get shadow map size as next power of two up from view width/height.
|
|
|
|
int shadowMapSize;
|
2021-04-14 22:25:05 +02:00
|
|
|
switch (QualityLevel)
|
2007-02-09 18:04:55 +01:00
|
|
|
{
|
2021-04-14 22:25:05 +02:00
|
|
|
// Low
|
|
|
|
case -1:
|
2021-06-06 18:44:54 +02:00
|
|
|
shadowMapSize = 512;
|
2021-04-14 22:25:05 +02:00
|
|
|
break;
|
|
|
|
// High
|
|
|
|
case 1:
|
2021-06-06 18:44:54 +02:00
|
|
|
shadowMapSize = 2048;
|
2021-04-14 22:25:05 +02:00
|
|
|
break;
|
|
|
|
// Ultra
|
|
|
|
case 2:
|
2022-06-23 19:04:13 +02:00
|
|
|
shadowMapSize = std::max(round_up_to_pow2(std::max(g_Renderer.GetWidth(), g_Renderer.GetHeight())), 4096);
|
2021-04-14 22:25:05 +02:00
|
|
|
break;
|
|
|
|
// Medium as is
|
|
|
|
default:
|
2021-06-06 18:44:54 +02:00
|
|
|
shadowMapSize = 1024;
|
2021-04-14 22:25:05 +02:00
|
|
|
break;
|
2007-02-09 18:04:55 +01:00
|
|
|
}
|
2017-08-19 14:46:05 +02:00
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
// Clamp to the maximum texture size.
|
2022-02-19 00:17:48 +01:00
|
|
|
shadowMapSize = std::min(
|
|
|
|
shadowMapSize, static_cast<int>(backendDevice->GetCapabilities().maxTextureSize));
|
2021-06-06 18:44:54 +02:00
|
|
|
|
|
|
|
Width = Height = shadowMapSize;
|
2006-03-26 19:36:33 +02:00
|
|
|
|
2011-04-07 00:08:51 +02:00
|
|
|
// Since we're using a framebuffer object, the whole texture is available
|
|
|
|
EffectiveWidth = Width;
|
|
|
|
EffectiveHeight = Height;
|
2006-02-11 01:26:40 +01:00
|
|
|
|
2021-03-25 13:09:56 +01:00
|
|
|
const char* formatName;
|
2021-12-26 10:48:48 +01:00
|
|
|
Renderer::Backend::Format backendFormat = Renderer::Backend::Format::UNDEFINED;
|
2021-03-25 13:09:56 +01:00
|
|
|
#if CONFIG2_GLES
|
|
|
|
formatName = "DEPTH_COMPONENT";
|
2021-12-26 10:48:48 +01:00
|
|
|
backendFormat = Renderer::Backend::Format::D24;
|
2021-03-25 13:09:56 +01:00
|
|
|
#else
|
2021-06-06 18:44:54 +02:00
|
|
|
switch (DepthTextureBits)
|
2006-02-16 00:50:24 +01:00
|
|
|
{
|
2022-01-05 15:49:54 +01:00
|
|
|
case 16: formatName = "Format::D16"; backendFormat = Renderer::Backend::Format::D16; break;
|
|
|
|
case 24: formatName = "Format::D24"; backendFormat = Renderer::Backend::Format::D24; break;
|
2022-05-02 22:57:22 +02:00
|
|
|
case 32: formatName = "Format::D32"; backendFormat = Renderer::Backend::Format::D32; break;
|
2022-01-05 15:49:54 +01:00
|
|
|
default: formatName = "Format::D24"; backendFormat = Renderer::Backend::Format::D24; break;
|
2006-02-16 00:50:24 +01:00
|
|
|
}
|
2021-03-25 13:09:56 +01:00
|
|
|
#endif
|
|
|
|
ENSURE(formatName);
|
2006-02-16 00:50:24 +01:00
|
|
|
|
2015-01-22 21:36:24 +01:00
|
|
|
LOGMESSAGE("Creating shadow texture (size %dx%d) (format = %s)",
|
2021-03-25 13:09:56 +01:00
|
|
|
Width, Height, formatName);
|
2006-02-11 01:26:40 +01:00
|
|
|
|
2019-08-04 10:28:30 +02:00
|
|
|
if (g_RenderingOptions.GetShadowAlphaFix())
|
2006-02-11 19:04:32 +01:00
|
|
|
{
|
2022-02-19 00:00:11 +01:00
|
|
|
DummyTexture = backendDevice->CreateTexture2D("ShadowMapDummy",
|
2022-03-06 23:14:57 +01:00
|
|
|
Renderer::Backend::Format::R8G8B8A8_UNORM, Width, Height,
|
2021-12-26 10:48:48 +01:00
|
|
|
Renderer::Backend::Sampler::MakeDefaultSampler(
|
|
|
|
Renderer::Backend::Sampler::Filter::NEAREST,
|
|
|
|
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
|
2011-04-07 00:08:07 +02:00
|
|
|
}
|
2006-02-16 00:50:24 +01:00
|
|
|
|
2022-02-25 21:02:03 +01:00
|
|
|
Renderer::Backend::Sampler::Desc samplerDesc =
|
2021-12-26 10:48:48 +01:00
|
|
|
Renderer::Backend::Sampler::MakeDefaultSampler(
|
|
|
|
#if CONFIG2_GLES
|
|
|
|
// GLES doesn't do depth comparisons, so treat it as a
|
|
|
|
// basic unfiltered depth texture
|
|
|
|
Renderer::Backend::Sampler::Filter::NEAREST,
|
|
|
|
#else
|
2022-02-26 01:37:27 +01:00
|
|
|
// Use LINEAR to trigger automatic PCF on some devices.
|
2021-12-26 10:48:48 +01:00
|
|
|
Renderer::Backend::Sampler::Filter::LINEAR,
|
|
|
|
#endif
|
2022-02-25 21:02:03 +01:00
|
|
|
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
|
2012-03-19 22:10:14 +01:00
|
|
|
// Enable automatic depth comparisons
|
2022-02-25 21:02:03 +01:00
|
|
|
samplerDesc.compareEnabled = true;
|
|
|
|
samplerDesc.compareOp = Renderer::Backend::CompareOp::LESS_OR_EQUAL;
|
2012-03-19 22:10:14 +01:00
|
|
|
|
2022-02-25 21:02:03 +01:00
|
|
|
Texture = backendDevice->CreateTexture2D("ShadowMapDepth",
|
|
|
|
backendFormat, Width, Height, samplerDesc);
|
2006-03-26 19:36:33 +02:00
|
|
|
|
2022-02-19 00:00:11 +01:00
|
|
|
Framebuffer = backendDevice->CreateFramebuffer("ShadowMapFramebuffer",
|
2022-02-05 17:59:23 +01:00
|
|
|
g_RenderingOptions.GetShadowAlphaFix() ? DummyTexture.get() : nullptr, Texture.get());
|
2006-03-26 19:36:33 +02:00
|
|
|
|
2022-02-05 17:59:23 +01:00
|
|
|
if (!Framebuffer)
|
2011-04-07 00:08:51 +02:00
|
|
|
{
|
2022-02-05 17:59:23 +01:00
|
|
|
LOGERROR("Failed to create shadows framebuffer");
|
2006-03-26 19:36:33 +02:00
|
|
|
|
2022-02-05 17:59:23 +01:00
|
|
|
// Disable shadow rendering (but let the user try again if they want).
|
2019-08-04 10:28:30 +02:00
|
|
|
g_RenderingOptions.SetShadows(false);
|
2006-03-26 19:36:33 +02:00
|
|
|
}
|
2006-01-07 02:04:26 +01:00
|
|
|
}
|
|
|
|
|
2022-10-12 23:51:27 +02:00
|
|
|
void ShadowMap::BeginRender(
|
|
|
|
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
|
2006-01-07 02:04:26 +01:00
|
|
|
{
|
2022-03-14 23:16:14 +01:00
|
|
|
deviceCommandContext->SetGraphicsPipelineState(
|
|
|
|
Renderer::Backend::MakeDefaultGraphicsPipelineStateDesc());
|
2022-02-05 17:59:23 +01:00
|
|
|
|
2006-03-26 19:36:33 +02:00
|
|
|
{
|
2011-02-19 00:41:27 +01:00
|
|
|
PROFILE("bind framebuffer");
|
2022-02-27 21:21:07 +01:00
|
|
|
ENSURE(m->Framebuffer);
|
2022-10-12 23:51:27 +02:00
|
|
|
deviceCommandContext->BeginFramebufferPass(m->Framebuffer.get());
|
2006-03-26 19:36:33 +02:00
|
|
|
}
|
|
|
|
|
2006-01-07 02:04:26 +01:00
|
|
|
// clear buffers
|
2006-02-11 19:04:32 +01:00
|
|
|
{
|
2011-02-19 00:41:27 +01:00
|
|
|
PROFILE("clear depth texture");
|
2012-03-19 22:10:14 +01:00
|
|
|
// In case we used m_ShadowAlphaFix, we ought to clear the unused
|
|
|
|
// color buffer too, else Mali 400 drivers get confused.
|
|
|
|
// Might as well clear stencil too for completeness.
|
2022-02-05 17:59:23 +01:00
|
|
|
deviceCommandContext->ClearFramebuffer();
|
2006-02-11 19:04:32 +01:00
|
|
|
}
|
2006-01-07 02:04:26 +01:00
|
|
|
|
2022-01-04 14:29:01 +01:00
|
|
|
m->SavedViewCamera = g_Renderer.GetSceneRenderer().GetViewCamera();
|
2021-06-06 18:44:54 +02:00
|
|
|
}
|
|
|
|
|
2022-10-12 23:51:27 +02:00
|
|
|
void ShadowMap::PrepareCamera(
|
|
|
|
Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const int cascade)
|
2021-06-06 18:44:54 +02:00
|
|
|
{
|
|
|
|
m->CalculateShadowMatrices(cascade);
|
|
|
|
|
2013-11-28 01:57:39 +01:00
|
|
|
const SViewPort vp = { 0, 0, m->EffectiveWidth, m->EffectiveHeight };
|
|
|
|
g_Renderer.SetViewport(vp);
|
2006-01-07 02:04:26 +01:00
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
CCamera camera = m->SavedViewCamera;
|
|
|
|
camera.SetProjection(m->Cascades[cascade].LightProjection);
|
|
|
|
camera.GetOrientation() = m->InvLightTransform;
|
2022-01-04 14:29:01 +01:00
|
|
|
g_Renderer.GetSceneRenderer().SetViewCamera(camera);
|
2012-03-19 22:10:14 +01:00
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
const SViewPort& cascadeViewPort = m->Cascades[cascade].ViewPort;
|
2022-05-09 00:02:46 +02:00
|
|
|
Renderer::Backend::IDeviceCommandContext::Rect scissorRect;
|
2022-02-01 18:58:21 +01:00
|
|
|
scissorRect.x = cascadeViewPort.m_X;
|
|
|
|
scissorRect.y = cascadeViewPort.m_Y;
|
|
|
|
scissorRect.width = cascadeViewPort.m_Width;
|
|
|
|
scissorRect.height = cascadeViewPort.m_Height;
|
2022-10-12 23:51:27 +02:00
|
|
|
deviceCommandContext->SetScissors(1, &scissorRect);
|
2006-01-07 02:04:26 +01:00
|
|
|
}
|
|
|
|
|
2022-10-12 23:51:27 +02:00
|
|
|
void ShadowMap::EndRender(
|
|
|
|
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
|
2006-01-07 02:04:26 +01:00
|
|
|
{
|
2022-10-12 23:51:27 +02:00
|
|
|
deviceCommandContext->SetScissors(0, nullptr);
|
2006-01-07 02:04:26 +01:00
|
|
|
|
2022-10-12 23:51:27 +02:00
|
|
|
deviceCommandContext->EndFramebufferPass();
|
2012-03-19 22:10:14 +01:00
|
|
|
|
2022-10-12 23:51:27 +02:00
|
|
|
g_Renderer.GetSceneRenderer().SetViewCamera(m->SavedViewCamera);
|
2006-01-07 02:04:26 +01:00
|
|
|
|
2013-11-28 01:57:39 +01:00
|
|
|
const SViewPort vp = { 0, 0, g_Renderer.GetWidth(), g_Renderer.GetHeight() };
|
|
|
|
g_Renderer.SetViewport(vp);
|
2006-01-07 02:04:26 +01:00
|
|
|
}
|
|
|
|
|
2022-05-02 22:57:22 +02:00
|
|
|
void ShadowMap::BindTo(
|
2022-05-09 00:02:46 +02:00
|
|
|
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
|
2022-05-02 22:57:22 +02:00
|
|
|
Renderer::Backend::IShaderProgram* shader) const
|
2006-01-07 02:04:26 +01:00
|
|
|
{
|
2022-05-02 22:57:22 +02:00
|
|
|
const int32_t shadowTexBindingSlot = shader->GetBindingSlot(str_shadowTex);
|
|
|
|
if (shadowTexBindingSlot < 0 || !m->Texture)
|
2021-01-19 20:19:04 +01:00
|
|
|
return;
|
2006-01-07 02:04:26 +01:00
|
|
|
|
2022-05-02 22:57:22 +02:00
|
|
|
deviceCommandContext->SetTexture(shadowTexBindingSlot, m->Texture.get());
|
|
|
|
deviceCommandContext->SetUniform(
|
|
|
|
shader->GetBindingSlot(str_shadowScale), m->Width, m->Height, 1.0f / m->Width, 1.0f / m->Height);
|
2022-01-04 14:29:01 +01:00
|
|
|
const CVector3D cameraForward = g_Renderer.GetSceneRenderer().GetCullCamera().GetOrientation().GetIn();
|
2022-05-02 22:57:22 +02:00
|
|
|
deviceCommandContext->SetUniform(
|
|
|
|
shader->GetBindingSlot(str_cameraForward), cameraForward.X, cameraForward.Y, cameraForward.Z,
|
2022-01-04 14:29:01 +01:00
|
|
|
cameraForward.Dot(g_Renderer.GetSceneRenderer().GetCullCamera().GetOrientation().GetTranslation()));
|
2022-05-02 22:57:22 +02:00
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
if (GetCascadeCount() == 1)
|
|
|
|
{
|
2022-05-02 22:57:22 +02:00
|
|
|
deviceCommandContext->SetUniform(
|
|
|
|
shader->GetBindingSlot(str_shadowTransform),
|
|
|
|
m->Cascades[0].TextureMatrix.AsFloatArray());
|
|
|
|
deviceCommandContext->SetUniform(
|
|
|
|
shader->GetBindingSlot(str_shadowDistance), m->Cascades[0].Distance);
|
2021-06-06 18:44:54 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
std::vector<float> shadowDistances;
|
|
|
|
std::vector<CMatrix3D> shadowTransforms;
|
|
|
|
for (const ShadowMapInternals::Cascade& cascade : m->Cascades)
|
|
|
|
{
|
|
|
|
shadowDistances.emplace_back(cascade.Distance);
|
|
|
|
shadowTransforms.emplace_back(cascade.TextureMatrix);
|
|
|
|
}
|
2022-05-02 22:57:22 +02:00
|
|
|
deviceCommandContext->SetUniform(
|
|
|
|
shader->GetBindingSlot(str_shadowTransform),
|
|
|
|
PS::span<const float>(
|
|
|
|
shadowTransforms[0]._data,
|
|
|
|
shadowTransforms[0].AsFloatArray().size() * GetCascadeCount()));
|
|
|
|
deviceCommandContext->SetUniform(
|
|
|
|
shader->GetBindingSlot(str_shadowDistance),
|
|
|
|
PS::span<const float>(shadowDistances.data(), shadowDistances.size()));
|
2021-06-06 18:44:54 +02:00
|
|
|
}
|
2006-01-07 02:04:26 +01:00
|
|
|
}
|
2006-01-29 18:34:45 +01:00
|
|
|
|
2006-02-16 00:50:24 +01:00
|
|
|
// Depth texture bits
|
|
|
|
int ShadowMap::GetDepthTextureBits() const
|
|
|
|
{
|
|
|
|
return m->DepthTextureBits;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ShadowMap::SetDepthTextureBits(int bits)
|
|
|
|
{
|
|
|
|
if (bits != m->DepthTextureBits)
|
|
|
|
{
|
2021-12-26 10:48:48 +01:00
|
|
|
m->Texture.reset();
|
2006-02-16 00:50:24 +01:00
|
|
|
m->Width = m->Height = 0;
|
|
|
|
|
|
|
|
m->DepthTextureBits = bits;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-19 22:10:14 +01:00
|
|
|
void ShadowMap::RenderDebugBounds()
|
2006-01-29 18:34:45 +01:00
|
|
|
{
|
2014-06-25 03:11:10 +02:00
|
|
|
// Render various shadow bounds:
|
|
|
|
// Yellow = bounds of objects in view frustum that receive shadows
|
|
|
|
// Red = culling frustum used to find potential shadow casters
|
|
|
|
// Blue = frustum used for rendering the shadow map
|
|
|
|
|
2022-01-04 14:29:01 +01:00
|
|
|
const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection() * m->InvLightTransform;
|
2014-06-25 03:11:10 +02:00
|
|
|
|
2022-02-26 01:37:27 +01:00
|
|
|
g_Renderer.GetDebugRenderer().DrawBoundingBox(
|
|
|
|
m->ShadowReceiverBound, CColor(1.0f, 1.0f, 0.0f, 1.0f), transform, true);
|
2014-06-25 03:11:10 +02:00
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
for (int cascade = 0; cascade < GetCascadeCount(); ++cascade)
|
|
|
|
{
|
2022-02-26 01:37:27 +01:00
|
|
|
g_Renderer.GetDebugRenderer().DrawBoundingBox(
|
|
|
|
m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.10f), transform);
|
|
|
|
g_Renderer.GetDebugRenderer().DrawBoundingBox(
|
|
|
|
m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.5f), transform, true);
|
2021-06-06 18:44:54 +02:00
|
|
|
|
|
|
|
const CFrustum frustum = GetShadowCasterCullFrustum(cascade);
|
|
|
|
// We don't have a function to create a brush directly from a frustum, so use
|
|
|
|
// the ugly approach of creating a large cube and then intersecting with the frustum
|
|
|
|
const CBoundingBoxAligned dummy(CVector3D(-1e4, -1e4, -1e4), CVector3D(1e4, 1e4, 1e4));
|
|
|
|
CBrush brush(dummy);
|
|
|
|
CBrush frustumBrush;
|
|
|
|
brush.Intersect(frustum, frustumBrush);
|
|
|
|
|
|
|
|
g_Renderer.GetDebugRenderer().DrawBrush(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.1f));
|
2022-02-26 01:37:27 +01:00
|
|
|
g_Renderer.GetDebugRenderer().DrawBrush(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.1f), true);
|
2021-06-06 18:44:54 +02:00
|
|
|
}
|
2012-03-19 22:10:14 +01:00
|
|
|
}
|
|
|
|
|
2021-06-06 18:44:54 +02:00
|
|
|
int ShadowMap::GetCascadeCount() const
|
|
|
|
{
|
|
|
|
#if CONFIG2_GLES
|
|
|
|
return 1;
|
|
|
|
#else
|
|
|
|
return m->ShadowsCoverMap ? 1 : m->CascadeCount;
|
|
|
|
#endif
|
|
|
|
}
|