Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/source/renderer/ShadowMap.cpp
/* Copyright (C) 2019 Wildfire Games. | /* Copyright (C) 2020 Wildfire Games. | ||||
* This file is part of 0 A.D. | * This file is part of 0 A.D. | ||||
* | * | ||||
* 0 A.D. is free software: you can redistribute it and/or modify | * 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 | * it under the terms of the GNU General Public License as published by | ||||
* the Free Software Foundation, either version 2 of the License, or | * the Free Software Foundation, either version 2 of the License, or | ||||
* (at your option) any later version. | * (at your option) any later version. | ||||
* | * | ||||
* 0 A.D. is distributed in the hope that it will be useful, | * 0 A.D. is distributed in the hope that it will be useful, | ||||
▲ Show 20 Lines • Show All 63 Lines • ▼ Show 20 Lines | struct ShadowMapInternals | ||||
// transform light space into world space | // transform light space into world space | ||||
CMatrix3D InvLightTransform; | CMatrix3D InvLightTransform; | ||||
// bounding box of shadowed objects in light space | // bounding box of shadowed objects in light space | ||||
CBoundingBoxAligned ShadowCasterBound; | CBoundingBoxAligned ShadowCasterBound; | ||||
CBoundingBoxAligned ShadowReceiverBound; | CBoundingBoxAligned ShadowReceiverBound; | ||||
CBoundingBoxAligned ShadowRenderBound; | CBoundingBoxAligned ShadowRenderBound; | ||||
CBoundingBoxAligned FixedFrustumBounds; | |||||
bool FixedShadowsEnabled; | |||||
float FixedShadowsDistance; | |||||
// Camera transformed into light space | // Camera transformed into light space | ||||
CCamera LightspaceCamera; | CCamera LightspaceCamera; | ||||
// Some drivers (at least some Intel Mesa ones) appear to handle alpha testing | // Some drivers (at least some Intel Mesa ones) appear to handle alpha testing | ||||
// incorrectly when the FBO has only a depth attachment. | // incorrectly when the FBO has only a depth attachment. | ||||
// When m_ShadowAlphaFix is true, we use DummyTexture to store a useless | // When m_ShadowAlphaFix is true, we use DummyTexture to store a useless | ||||
// alpha texture which is attached to the FBO as a workaround. | // alpha texture which is attached to the FBO as a workaround. | ||||
GLuint DummyTexture; | GLuint DummyTexture; | ||||
Show All 28 Lines | ShadowMap::ShadowMap() | ||||
// DepthTextureBits: 24/32 are very much faster than 16, on GeForce 4 and FX; | // DepthTextureBits: 24/32 are very much faster than 16, on GeForce 4 and FX; | ||||
// but they're very much slower on Radeon 9800. | // but they're very much slower on Radeon 9800. | ||||
// In both cases, the default (no specified depth) is fast, so we just use | // 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 | // 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.) | // do some kind of hardware detection to work out what to use.) | ||||
// Avoid using uninitialised values in AddShadowedBound if SetupFrame wasn't called first | // Avoid using uninitialised values in AddShadowedBound if SetupFrame wasn't called first | ||||
m->LightTransform.SetIdentity(); | m->LightTransform.SetIdentity(); | ||||
m->FixedShadowsEnabled = false; | |||||
m->FixedShadowsDistance = 300.0f; | |||||
CFG_GET_VAL("shadowsfixed", m->FixedShadowsEnabled); | |||||
CFG_GET_VAL("shadowsfixeddistance", m->FixedShadowsDistance); | |||||
} | } | ||||
ShadowMap::~ShadowMap() | ShadowMap::~ShadowMap() | ||||
{ | { | ||||
if (m->Texture) | if (m->Texture) | ||||
glDeleteTextures(1, &m->Texture); | glDeleteTextures(1, &m->Texture); | ||||
if (m->DummyTexture) | if (m->DummyTexture) | ||||
Show All 25 Lines | |||||
////////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////////// | ||||
// SetupFrame: camera and light direction for this frame | // SetupFrame: camera and light direction for this frame | ||||
void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir) | void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir) | ||||
{ | { | ||||
if (!m->Texture) | if (!m->Texture) | ||||
m->CreateTexture(); | m->CreateTexture(); | ||||
CVector3D z = lightdir; | CVector3D x, eyepos; | ||||
CVector3D y; | if (!m->FixedShadowsEnabled) | ||||
CVector3D x = camera.m_Orientation.GetIn(); | { | ||||
CVector3D eyepos = camera.m_Orientation.GetTranslation(); | x = camera.m_Orientation.GetIn(); | ||||
eyepos = camera.m_Orientation.GetTranslation(); | |||||
} | |||||
else | |||||
x = CVector3D(0, 1, 0); | |||||
CVector3D z = lightdir; | |||||
z.Normalize(); | z.Normalize(); | ||||
x -= z * z.Dot(x); | x -= z * z.Dot(x); | ||||
if (x.Length() < 0.001) | if (x.Length() < 0.001) | ||||
{ | { | ||||
// this is invoked if the camera and light directions almost coincide | // this is invoked if the camera and light directions almost coincide | ||||
// assumption: light direction has a significant Z component | // assumption: light direction has a significant Z component | ||||
x = CVector3D(1.0, 0.0, 0.0); | x = CVector3D(1.0, 0.0, 0.0); | ||||
x -= z * z.Dot(x); | x -= z * z.Dot(x); | ||||
} | } | ||||
x.Normalize(); | x.Normalize(); | ||||
y = z.Cross(x); | CVector3D y = z.Cross(x); | ||||
// X axis perpendicular to light direction, flowing along with view direction | // X axis perpendicular to light direction, flowing along with view direction | ||||
m->LightTransform._11 = x.X; | m->LightTransform._11 = x.X; | ||||
m->LightTransform._12 = x.Y; | m->LightTransform._12 = x.Y; | ||||
m->LightTransform._13 = x.Z; | m->LightTransform._13 = x.Z; | ||||
// Y axis perpendicular to light and view direction | // Y axis perpendicular to light and view direction | ||||
m->LightTransform._21 = y.X; | m->LightTransform._21 = y.X; | ||||
Show All 18 Lines | void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir) | ||||
m->LightTransform.GetInverse(m->InvLightTransform); | m->LightTransform.GetInverse(m->InvLightTransform); | ||||
m->ShadowCasterBound.SetEmpty(); | m->ShadowCasterBound.SetEmpty(); | ||||
m->ShadowReceiverBound.SetEmpty(); | m->ShadowReceiverBound.SetEmpty(); | ||||
// | // | ||||
m->LightspaceCamera = camera; | m->LightspaceCamera = camera; | ||||
m->LightspaceCamera.m_Orientation = m->LightTransform * camera.m_Orientation; | m->LightspaceCamera.m_Orientation = m->LightTransform * camera.m_Orientation; | ||||
m->LightspaceCamera.UpdateFrustum(); | m->LightspaceCamera.UpdateFrustum(); | ||||
if (m->FixedShadowsEnabled) | |||||
{ | |||||
// 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 * camera.GetNearPlane(); | |||||
const CVector3D centerDist = cameraTranslation + cameraIn * m->FixedShadowsDistance; | |||||
// 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; | |||||
camera.GetViewQuad(camera.GetNearPlane(), corners); | |||||
const CVector3D cornerNear = camera.GetOrientation().Transform(corners[0]); | |||||
camera.GetViewQuad(m->FixedShadowsDistance, corners); | |||||
const CVector3D cornerDist = camera.GetOrientation().Transform(corners[0]); | |||||
// 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; | |||||
CVector3D position = cameraTranslation + cameraIn * (camera.GetNearPlane() + distanceToCenter); | |||||
const float radius = (cornerNear - position).Length(); | |||||
// We need to convert the bounding box to the light space. | |||||
position = m->LightTransform.Rotate(position); | |||||
const float insets = 0.2f; | |||||
m->FixedFrustumBounds = CBoundingBoxAligned(position, position); | |||||
m->FixedFrustumBounds.Expand(radius); | |||||
m->FixedFrustumBounds.Expand(insets); | |||||
} | |||||
} | } | ||||
////////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////////// | ||||
// AddShadowedBound: add a world-space bounding box to the bounds of shadowed | // AddShadowedBound: add a world-space bounding box to the bounds of shadowed | ||||
// objects | // objects | ||||
void ShadowMap::AddShadowCasterBound(const CBoundingBoxAligned& bounds) | void ShadowMap::AddShadowCasterBound(const CBoundingBoxAligned& bounds) | ||||
{ | { | ||||
Show All 39 Lines | CFrustum ShadowMap::GetShadowCasterCullFrustum() | ||||
return frustum; | return frustum; | ||||
} | } | ||||
/////////////////////////////////////////////////////////////////////////////////////////////////// | /////////////////////////////////////////////////////////////////////////////////////////////////// | ||||
// CalcShadowMatrices: calculate required matrices for shadow map generation - the light's | // CalcShadowMatrices: calculate required matrices for shadow map generation - the light's | ||||
// projection and transformation matrices | // projection and transformation matrices | ||||
void ShadowMapInternals::CalcShadowMatrices() | void ShadowMapInternals::CalcShadowMatrices() | ||||
{ | { | ||||
if (FixedShadowsEnabled) | |||||
{ | |||||
ShadowRenderBound = FixedFrustumBounds; | |||||
// 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 = ShadowCasterBound[0].Z - 2.f; | |||||
ShadowRenderBound[1].Z = ShadowCasterBound[1].Z + 2.f; | |||||
} | |||||
else | |||||
{ | |||||
// Start building the shadow map to cover all objects that will receive shadows | // Start building the shadow map to cover all objects that will receive shadows | ||||
CBoundingBoxAligned receiverBound = ShadowReceiverBound; | CBoundingBoxAligned receiverBound = ShadowReceiverBound; | ||||
// Intersect with the camera frustum, so the shadow map doesn't have to get | // Intersect with the camera frustum, so the shadow map doesn't have to get | ||||
// stretched to cover the off-screen parts of large models | // stretched to cover the off-screen parts of large models | ||||
receiverBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum()); | receiverBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum()); | ||||
// Intersect with the shadow caster bounds, because there's no point | // 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 | // wasting space around the edges of the shadow map that we're not going | ||||
// to draw into | // to draw into | ||||
ShadowRenderBound[0].X = std::max(receiverBound[0].X, ShadowCasterBound[0].X); | ShadowRenderBound[0].X = std::max(receiverBound[0].X, ShadowCasterBound[0].X); | ||||
ShadowRenderBound[0].Y = std::max(receiverBound[0].Y, ShadowCasterBound[0].Y); | ShadowRenderBound[0].Y = std::max(receiverBound[0].Y, ShadowCasterBound[0].Y); | ||||
ShadowRenderBound[1].X = std::min(receiverBound[1].X, ShadowCasterBound[1].X); | ShadowRenderBound[1].X = std::min(receiverBound[1].X, ShadowCasterBound[1].X); | ||||
ShadowRenderBound[1].Y = std::min(receiverBound[1].Y, ShadowCasterBound[1].Y); | ShadowRenderBound[1].Y = std::min(receiverBound[1].Y, ShadowCasterBound[1].Y); | ||||
// Set the near and far planes to include just the shadow casters, | // 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 | // 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 | // delta so we don't accidentally clip objects that are directly on | ||||
// the planes. | // the planes. | ||||
ShadowRenderBound[0].Z = ShadowCasterBound[0].Z - 2.f; | ShadowRenderBound[0].Z = ShadowCasterBound[0].Z - 2.f; | ||||
ShadowRenderBound[1].Z = ShadowCasterBound[1].Z + 2.f; | ShadowRenderBound[1].Z = ShadowCasterBound[1].Z + 2.f; | ||||
// ShadowBound might have been empty to begin with, producing an empty result | // ShadowBound might have been empty to begin with, producing an empty result | ||||
if (ShadowRenderBound.IsEmpty()) | if (ShadowRenderBound.IsEmpty()) | ||||
{ | { | ||||
// no-op | // no-op | ||||
LightProjection.SetIdentity(); | LightProjection.SetIdentity(); | ||||
TextureMatrix = LightTransform; | TextureMatrix = LightTransform; | ||||
return; | return; | ||||
} | } | ||||
// round off the shadow boundaries to sane increments to help reduce swim effect | // round off the shadow boundaries to sane increments to help reduce swim effect | ||||
float boundInc = 16.0f; | float boundInc = 16.0f; | ||||
ShadowRenderBound[0].X = floor(ShadowRenderBound[0].X / boundInc) * boundInc; | ShadowRenderBound[0].X = floor(ShadowRenderBound[0].X / boundInc) * boundInc; | ||||
ShadowRenderBound[0].Y = floor(ShadowRenderBound[0].Y / boundInc) * boundInc; | ShadowRenderBound[0].Y = floor(ShadowRenderBound[0].Y / boundInc) * boundInc; | ||||
ShadowRenderBound[1].X = ceil(ShadowRenderBound[1].X / boundInc) * boundInc; | ShadowRenderBound[1].X = ceil(ShadowRenderBound[1].X / boundInc) * boundInc; | ||||
ShadowRenderBound[1].Y = ceil(ShadowRenderBound[1].Y / boundInc) * boundInc; | ShadowRenderBound[1].Y = ceil(ShadowRenderBound[1].Y / boundInc) * boundInc; | ||||
} | |||||
// Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering | // Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering | ||||
CVector3D scale = ShadowRenderBound[1] - ShadowRenderBound[0]; | CVector3D scale = ShadowRenderBound[1] - ShadowRenderBound[0]; | ||||
CVector3D shift = (ShadowRenderBound[1] + ShadowRenderBound[0]) * -0.5; | CVector3D shift = (ShadowRenderBound[1] + ShadowRenderBound[0]) * -0.5; | ||||
if (scale.X < 1.0) | if (scale.X < 1.0) | ||||
scale.X = 1.0; | scale.X = 1.0; | ||||
if (scale.Y < 1.0) | if (scale.Y < 1.0) | ||||
▲ Show 20 Lines • Show All 209 Lines • ▼ Show 20 Lines | #endif | ||||
{ | { | ||||
LOGWARNING("Framebuffer object incomplete: 0x%04X", status); | LOGWARNING("Framebuffer object incomplete: 0x%04X", status); | ||||
// Disable shadow rendering (but let the user try again if they want) | // Disable shadow rendering (but let the user try again if they want) | ||||
g_RenderingOptions.SetShadows(false); | g_RenderingOptions.SetShadows(false); | ||||
} | } | ||||
} | } | ||||
/////////////////////////////////////////////////////////////////////////////////////////////////// | /////////////////////////////////////////////////////////////////////////////////////////////////// | ||||
// Set up to render into shadow map texture | // Set up to render into shadow map texture | ||||
void ShadowMap::BeginRender() | void ShadowMap::BeginRender() | ||||
{ | { | ||||
// HACK HACK: this depends in non-obvious ways on the behaviour of the caller | // HACK HACK: this depends in non-obvious ways on the behaviour of the caller | ||||
// save caller's FBO | // save caller's FBO | ||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &m->SavedViewFBO); | glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &m->SavedViewFBO); | ||||
▲ Show 20 Lines • Show All 247 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator