1
1
forked from 0ad/0ad

Implements configurable cascade shadows.

Tested By: Langbart, Stan, wraitii
Differential Revision: https://code.wildfiregames.com/D3972
This was SVN commit r25711.
This commit is contained in:
Vladislav Belov 2021-06-06 16:44:54 +00:00
parent ca6fcb28ab
commit 30e135693e
10 changed files with 465 additions and 219 deletions

View File

@ -82,11 +82,17 @@ waterrefraction = true
waterreflection = true
shadows = true
shadowquality = 0 ; Shadow map resolution. (-2 - Very Low, -1 - Low, 0 - Medium, 1 - High, 2 - Very High)
shadowquality = 0 ; Shadow map resolution. (-1 - Low, 0 - Medium, 1 - High, 2 - Very High)
; High values can crash the game when using a graphics card with low memory!
shadowpcf = true
shadowsfixed = false ; When enabled shadows are rendered only on the
shadowsfixeddistance = 300.0 ; fixed distance and without swimming effect.
; Increases details closer to the camera but decreases performance
; especially on low hardware.
shadowscascadecount = 1
shadowscascadedistanceratio = 1.7
; Hides shadows after the distance.
shadowscutoffdistance = 300.0
; If true shadows cover the whole map instead of the camera frustum.
shadowscovermap = false
texturequality = 5 ; Texture resolution and quality (0 - Lowest, 1 Very Low, 2 - Low, 3 - Medium, 4 - High, 5 - Very High, 6 - Ultra)
vsync = false

View File

@ -249,12 +249,11 @@
},
{
"type": "dropdown",
"label": "Shadow quality",
"label": "Quality",
"tooltip": "Shadow map resolution. High values can crash the game when using a graphics card with low memory!",
"dependencies": ["shadows"],
"config": "shadowquality",
"list": [
{ "value": -2, "label": "Very Low" },
{ "value": -1, "label": "Low" },
{ "value": 0, "label": "Medium" },
{ "value": 1, "label": "High" },
@ -263,11 +262,27 @@
},
{
"type": "boolean",
"label": "Shadow filtering",
"label": "Filtering",
"tooltip": "Smooth shadows.",
"dependencies": ["shadows"],
"config": "shadowpcf"
},
{
"type": "slider",
"label": "Cutoff distance",
"tooltip": "Hides shadows beyond a certain distance from a camera.",
"dependencies": ["shadows"],
"config": "shadowscutoffdistance",
"min": 100,
"max": 1500
},
{
"type": "boolean",
"label": "Cover whole map",
"tooltip": "When ON shadows cover the whole map and shadows cutoff distance is ignored. Useful for making screenshots of a whole map.",
"dependencies": ["shadows"],
"config": "shadowscovermap"
},
{
"type": "boolean",
"label": "Water effects",

View File

@ -2,7 +2,7 @@
#define INCLUDED_SHADOWS_FRAGMENT
#if USE_SHADOW
varying vec4 v_shadow;
varying float v_depth;
#if USE_SHADOW_SAMPLER
uniform sampler2DShadow shadowTex;
#if USE_SHADOW_PCF
@ -11,45 +11,86 @@
#else
uniform sampler2D shadowTex;
#endif
#if SHADOWS_CASCADE_COUNT == 1
uniform float shadowDistance;
varying vec4 v_shadow;
#else
uniform float shadowDistances[SHADOWS_CASCADE_COUNT];
varying vec4 v_shadow[SHADOWS_CASCADE_COUNT];
#endif
#endif
float getShadowImpl(float shadowBias)
float getShadowImpl(vec4 shadowVertex, float shadowBias)
{
#if USE_SHADOW && !DISABLE_RECEIVE_SHADOWS
float biasedShdwZ = v_shadow.z - shadowBias;
#if USE_SHADOW_SAMPLER
#if USE_SHADOW_PCF
vec2 offset = fract(v_shadow.xy - 0.5);
vec4 size = vec4(offset + 1.0, 2.0 - offset);
vec4 weight = (vec4(1.0, 1.0, -0.5, -0.5) + (v_shadow.xy - 0.5*offset).xyxy) * shadowScale.zwzw;
return (1.0/9.0)*dot(size.zxzx*size.wwyy,
vec4(shadow2D(shadowTex, vec3(weight.zw, biasedShdwZ)).r,
shadow2D(shadowTex, vec3(weight.xw, biasedShdwZ)).r,
shadow2D(shadowTex, vec3(weight.zy, biasedShdwZ)).r,
shadow2D(shadowTex, vec3(weight.xy, biasedShdwZ)).r));
#else
return shadow2D(shadowTex, vec3(v_shadow.xy, biasedShdwZ)).r;
#endif
#if USE_SHADOW && !DISABLE_RECEIVE_SHADOWS
float biasedShdwZ = shadowVertex.z - shadowBias;
#if USE_SHADOW_SAMPLER
#if USE_SHADOW_PCF
vec2 offset = fract(shadowVertex.xy - 0.5);
vec4 size = vec4(offset + 1.0, 2.0 - offset);
vec4 weight = (vec4(1.0, 1.0, -0.5, -0.5) + (shadowVertex.xy - 0.5*offset).xyxy) * shadowScale.zwzw;
return (1.0/9.0)*dot(size.zxzx*size.wwyy,
vec4(shadow2D(shadowTex, vec3(weight.zw, biasedShdwZ)).r,
shadow2D(shadowTex, vec3(weight.xw, biasedShdwZ)).r,
shadow2D(shadowTex, vec3(weight.zy, biasedShdwZ)).r,
shadow2D(shadowTex, vec3(weight.xy, biasedShdwZ)).r));
#else
if (biasedShdwZ >= 1.0)
return 1.0;
return (biasedShdwZ < texture2D(shadowTex, v_shadow.xy).x ? 1.0 : 0.0);
return shadow2D(shadowTex, vec3(shadowVertex.xy, biasedShdwZ)).r;
#endif
#else
return 1.0;
if (biasedShdwZ >= 1.0)
return 1.0;
return (biasedShdwZ < texture2D(shadowTex, shadowVertex.xy).x ? 1.0 : 0.0);
#endif
#else
return 1.0;
#endif // USE_SHADOW && !DISABLE_RECEIVE_SHADOWS
}
float getShadowWithFade(float shadowBias)
{
#if USE_SHADOW
#if SHADOWS_CASCADE_COUNT == 1
float blendWidth = 8.0;
float distanceBlend = clamp((shadowDistance - v_depth - blendWidth) / blendWidth, 0.0, 1.0);
float shadow = getShadowImpl(v_shadow, shadowBias);
return mix(1.0, shadow, distanceBlend);
#else
int firstCascade = SHADOWS_CASCADE_COUNT;
for (int cascade = 0; cascade < SHADOWS_CASCADE_COUNT; ++cascade)
{
if (v_depth <= shadowDistances[cascade])
{
firstCascade = cascade;
break;
}
}
if (firstCascade == SHADOWS_CASCADE_COUNT)
return 1.0;
float shadow = getShadowImpl(v_shadow[firstCascade], shadowBias);
float blendWidth = clamp(shadowDistances[firstCascade] / 8.0, 2.0, 32.0);
float cascadeBlend = clamp(
(shadowDistances[firstCascade] - v_depth - blendWidth) / blendWidth, 0.0, 1.0);
if (firstCascade == SHADOWS_CASCADE_COUNT - 1)
return mix(1.0, shadow, cascadeBlend);
else
return mix(getShadowImpl(v_shadow[firstCascade + 1], shadowBias), shadow, cascadeBlend);
#endif
#else
return 1.0;
#endif // USE_SHADOW
}
float getShadow()
{
float shadowBias = 0.003;
return getShadowImpl(shadowBias);
return getShadowWithFade(shadowBias);
}
float getShadowOnLandscape()
{
float shadowBias = 0.0005;
return getShadowImpl(shadowBias);
return getShadowWithFade(shadowBias);
}
#endif // INCLUDED_SHADOWS_FRAGMENT

View File

@ -2,21 +2,41 @@
#define INCLUDED_SHADOWS_VERTEX
#if USE_SHADOW
uniform mat4 shadowTransform;
varying vec4 v_shadow;
#if USE_SHADOW_SAMPLER && USE_SHADOW_PCF
uniform vec4 shadowScale;
#endif
#endif
uniform vec4 cameraForward;
varying float v_depth;
#if SHADOWS_CASCADE_COUNT == 1
uniform mat4 shadowTransform;
varying vec4 v_shadow;
#else
uniform mat4 shadowTransforms[SHADOWS_CASCADE_COUNT];
varying vec4 v_shadow[SHADOWS_CASCADE_COUNT];
#endif
#if USE_SHADOW_SAMPLER && USE_SHADOW_PCF
uniform vec4 shadowScale;
#endif
#endif // USE_SHADOW
void calculatePositionInShadowSpace(vec4 position)
{
#if USE_SHADOW
v_depth = dot(cameraForward.xyz, position.xyz) - cameraForward.w;
#if SHADOWS_CASCADE_COUNT == 1
v_shadow = shadowTransform * position;
#if USE_SHADOW_SAMPLER && USE_SHADOW_PCF
v_shadow.xy *= shadowScale.xy;
#endif
#if USE_SHADOW_SAMPLER && USE_SHADOW_PCF
v_shadow.xy *= shadowScale.xy;
#endif
#else
for (int cascade = 0; cascade < SHADOWS_CASCADE_COUNT; ++cascade)
{
v_shadow[cascade] = shadowTransforms[cascade] * position;
#if USE_SHADOW_SAMPLER && USE_SHADOW_PCF
v_shadow[cascade].xy *= shadowScale.xy;
#endif
}
#else
#endif
#endif // USE_SHADOW
}
#endif // INCLUDED_SHADOWS_VERTEX

View File

@ -36,6 +36,8 @@
X(0)
X(1)
X(2)
X(3)
X(4)
X(ALPHABLEND_PASS_BLEND)
X(ALPHABLEND_PASS_OPAQUE)
X(BLEND)
@ -54,6 +56,7 @@ X(MODE_SHADOWCAST)
X(MODE_SILHOUETTEDISPLAY)
X(MODE_SILHOUETTEOCCLUDER)
X(MODE_WIREFRAME)
X(SHADOWS_CASCADE_COUNT)
X(SYS_HAS_ARB)
X(SYS_HAS_GLSL)
X(SYS_PREFER_GLSL)
@ -89,6 +92,7 @@ X(blurTex2)
X(blurTex4)
X(blurTex8)
X(brightness)
X(cameraForward)
X(cameraPos)
X(canvas2d)
X(color)
@ -143,9 +147,14 @@ X2(sans_10, "sans-10");
X(saturation)
X(screenSize)
X(shadingColor)
X(shadowDistance)
X(shadowDistances)
X2(shadowDistances_0, "shadowDistances[0]")
X(shadowScale)
X(shadowTex)
X(shadowTransform)
X(shadowTransforms)
X2(shadowTransforms_0, "shadowTransforms[0]")
X(sharpness)
X(skinBlendMatrices)
X2(skinBlendMatrices_0, "skinBlendMatrices[0]")

View File

@ -513,6 +513,10 @@ void CRenderer::ReloadShaders()
m->globalContext.Add(str_USE_FP_SHADOW, str_1);
if (g_RenderingOptions.GetShadowPCF())
m->globalContext.Add(str_USE_SHADOW_PCF, str_1);
const int cascadeCount = m->shadow.GetCascadeCount();
ENSURE(1 <= cascadeCount && cascadeCount <= 4);
const CStrIntern cascadeCountStr[5] = {str_0, str_1, str_2, str_3, str_4};
m->globalContext.Add(str_SHADOWS_CASCADE_COUNT, cascadeCountStr[cascadeCount]);
#if !CONFIG2_GLES
m->globalContext.Add(str_USE_SHADOW_SAMPLER, str_1);
#endif
@ -685,30 +689,38 @@ void CRenderer::RenderShadowMap(const CShaderDefines& context)
{
PROFILE3_GPU("shadow map");
m->shadow.BeginRender();
{
PROFILE("render patches");
glCullFace(GL_FRONT);
glEnable(GL_CULL_FACE);
m->terrainRenderer.RenderPatches(CULL_SHADOWS);
glCullFace(GL_BACK);
}
CShaderDefines contextCast = context;
contextCast.Add(str_MODE_SHADOWCAST, str_1);
{
PROFILE("render models");
m->CallModelRenderers(contextCast, CULL_SHADOWS, MODELFLAG_CASTSHADOWS);
}
m->shadow.BeginRender();
const int cascadeCount = m->shadow.GetCascadeCount();
ENSURE(0 <= cascadeCount && cascadeCount <= 4);
for (int cascade = 0; cascade < cascadeCount; ++cascade)
{
PROFILE("render transparent models");
// disable face-culling for two-sided models
glDisable(GL_CULL_FACE);
m->CallTranspModelRenderers(contextCast, CULL_SHADOWS, MODELFLAG_CASTSHADOWS);
glEnable(GL_CULL_FACE);
m->shadow.PrepareCamera(cascade);
const int cullGroup = CULL_SHADOWS_CASCADE_0 + cascade;
{
PROFILE("render patches");
glCullFace(GL_FRONT);
glEnable(GL_CULL_FACE);
m->terrainRenderer.RenderPatches(cullGroup);
glCullFace(GL_BACK);
}
{
PROFILE("render models");
m->CallModelRenderers(contextCast, cullGroup, MODELFLAG_CASTSHADOWS);
}
{
PROFILE("render transparent models");
// disable face-culling for two-sided models
glDisable(GL_CULL_FACE);
m->CallTranspModelRenderers(contextCast, cullGroup, MODELFLAG_CASTSHADOWS);
glEnable(GL_CULL_FACE);
}
}
m->shadow.EndRender();
@ -1509,9 +1521,10 @@ void CRenderer::Submit(CPatch* patch)
m->silhouetteRenderer.AddOccluder(patch);
}
if (m_CurrentCullGroup == CULL_SHADOWS)
if (CULL_SHADOWS_CASCADE_0 <= m_CurrentCullGroup && m_CurrentCullGroup <= CULL_SHADOWS_CASCADE_3)
{
m->shadow.AddShadowCasterBound(patch->GetWorldBounds());
const int cascade = m_CurrentCullGroup - CULL_SHADOWS_CASCADE_0;
m->shadow.AddShadowCasterBound(cascade, patch->GetWorldBounds());
}
m->terrainRenderer.Submit(m_CurrentCullGroup, patch);
@ -1576,12 +1589,13 @@ void CRenderer::SubmitNonRecursive(CModel* model)
m->silhouetteRenderer.AddCaster(model);
}
if (m_CurrentCullGroup == CULL_SHADOWS)
if (CULL_SHADOWS_CASCADE_0 <= m_CurrentCullGroup && m_CurrentCullGroup <= CULL_SHADOWS_CASCADE_3)
{
if (!(model->GetFlags() & MODELFLAG_CASTSHADOWS))
return;
m->shadow.AddShadowCasterBound(model->GetWorldBounds());
const int cascade = m_CurrentCullGroup - CULL_SHADOWS_CASCADE_0;
m->shadow.AddShadowCasterBound(cascade, model->GetWorldBounds());
}
bool requiresSkinning = (model->GetModelDef()->GetNumBones() != 0);
@ -1633,10 +1647,12 @@ void CRenderer::RenderScene(Scene& scene)
if (m_Caps.m_Shadows && g_RenderingOptions.GetShadows())
{
m_CurrentCullGroup = CULL_SHADOWS;
CFrustum shadowFrustum = m->shadow.GetShadowCasterCullFrustum();
scene.EnumerateObjects(shadowFrustum, this);
for (int cascade = 0; cascade <= m->shadow.GetCascadeCount(); ++cascade)
{
m_CurrentCullGroup = CULL_SHADOWS_CASCADE_0 + cascade;
const CFrustum shadowFrustum = m->shadow.GetShadowCasterCullFrustum(cascade);
scene.EnumerateObjects(shadowFrustum, this);
}
}
CBoundingBoxAligned waterScissor;

View File

@ -76,7 +76,10 @@ public:
enum CullGroup
{
CULL_DEFAULT,
CULL_SHADOWS,
CULL_SHADOWS_CASCADE_0,
CULL_SHADOWS_CASCADE_1,
CULL_SHADOWS_CASCADE_2,
CULL_SHADOWS_CASCADE_3,
CULL_REFLECTIONS,
CULL_REFRACTIONS,
CULL_SILHOUETTE_OCCLUDER,

View File

@ -116,12 +116,32 @@ void CRenderingOptions::ReadConfigAndSetupHooks()
bool enabled;
CFG_GET_VAL("preferglsl", enabled);
SetPreferGLSL(enabled);
if (CRenderer::IsInitialised())
g_Renderer.GetShadowMap().RecreateTexture();
});
m_ConfigHooks->Setup("shadowquality", []() {
if (CRenderer::IsInitialised())
g_Renderer.GetShadowMap().RecreateTexture();
});
m_ConfigHooks->Setup("shadowscascadecount", []() {
if (CRenderer::IsInitialised())
{
g_Renderer.GetShadowMap().RecreateTexture();
g_Renderer.MakeShadersDirty();
}
});
m_ConfigHooks->Setup("shadowscovermap", []() {
if (CRenderer::IsInitialised())
{
g_Renderer.GetShadowMap().RecreateTexture();
g_Renderer.MakeShadersDirty();
}
});
m_ConfigHooks->Setup("shadowscutoffdistance", []() {
if (CRenderer::IsInitialised())
g_Renderer.GetShadowMap().RecreateTexture();
});
m_ConfigHooks->Setup("shadows", [this]() {
bool enabled;

View File

@ -38,44 +38,68 @@
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#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
/**
* Struct ShadowMapInternals: Internal data for the ShadowMap implementation
*/
struct ShadowMapInternals
{
// bit depth for the depth texture
int DepthTextureBits;
// the EXT_framebuffer_object framebuffer
GLuint Framebuffer;
// handle of shadow map
GLuint Texture;
// bit depth for the depth texture
int DepthTextureBits;
// width, height of shadow map
int Width, Height;
// Shadow map quality (-2 - Very Low, -1 - Low, 0 - Medium, 1 - High, 2 - Very High)
// Shadow map quality (-1 - Low, 0 - Medium, 1 - High, 2 - Very High)
int QualityLevel;
// used width, height of shadow map
int EffectiveWidth, EffectiveHeight;
// 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;
// Transform world space into light space; calculated on SetupFrame
CMatrix3D LightTransform;
// Transform world space into texture space of the shadow map;
// calculated on BeginRender, after the final shadow bounds are known
CMatrix3D TextureMatrix;
// transform light space into world space
CMatrix3D InvLightTransform;
// bounding box of shadowed objects in light space
CBoundingBoxAligned ShadowCasterBound;
CBoundingBoxAligned ShadowReceiverBound;
CBoundingBoxAligned ShadowRenderBound;
int CascadeCount;
float CascadeDistanceRatio;
float ShadowsCutoffDistance;
bool ShadowsCoverMap;
CBoundingBoxAligned FixedFrustumBounds;
bool FixedShadowsEnabled;
float FixedShadowsDistance;
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;
// Camera transformed into light space
CCamera LightspaceCamera;
@ -93,15 +117,30 @@ struct ShadowMapInternals
// Save the caller's FBO so it can be restored
GLint SavedViewFBO;
// Helper functions
void CalcShadowMatrices();
void CalculateShadowMatrices(const int cascade);
void CreateTexture();
void UpdateCascadesParameters();
};
void CalculateBoundsForFixedShadows(
const CCamera& camera, const CMatrix3D& lightTransform,
const float nearPlane, const float farPlane, CBoundingBoxAligned* bbaa)
void ShadowMapInternals::UpdateCascadesParameters()
{
CascadeCount = 1;
CFG_GET_VAL("shadowscascadecount", CascadeCount);
if (CascadeCount < 1 || CascadeCount > MAX_CASCADE_COUNT || !g_RenderingOptions.GetPreferGLSL())
CascadeCount = 1;
ShadowsCoverMap = false;
CFG_GET_VAL("shadowscovermap", ShadowsCoverMap);
}
void CalculateBoundsForCascade(
const CCamera& camera, const CMatrix3D& lightTransform,
const float nearPlane, const float farPlane, CBoundingBoxAligned* bbaa,
CBoundingBoxAligned* frustumBBAA)
{
frustumBBAA->SetEmpty();
// We need to calculate a circumscribed sphere for the camera to
// create a rotation stable bounding box.
const CVector3D cameraIn = camera.m_Orientation.GetIn();
@ -113,10 +152,20 @@ void CalculateBoundsForFixedShadows(
// symmetric by 2 planes. Than means we can use only one corner
// to find a circumscribed sphere.
CCamera::Quad corners;
camera.GetViewQuad(nearPlane, corners);
const CVector3D cornerNear = camera.GetOrientation().Transform(corners[0]);
for (CVector3D& corner : corners)
corner = camera.GetOrientation().Transform(corner);
const CVector3D cornerNear = corners[0];
for (const CVector3D& corner : corners)
*frustumBBAA += lightTransform.Transform(corner);
camera.GetViewQuad(farPlane, corners);
const CVector3D cornerDist = camera.GetOrientation().Transform(corners[0]);
for (CVector3D& corner : corners)
corner = camera.GetOrientation().Transform(corner);
const CVector3D cornerDist = corners[0];
for (const CVector3D& corner : corners)
*frustumBBAA += lightTransform.Transform(corner);
// We solve 2D case for the right trapezoid.
const float firstBase = (cornerNear - centerNear).Length();
@ -125,7 +174,7 @@ void CalculateBoundsForFixedShadows(
const float distanceToCenter =
(height * height + secondBase * secondBase - firstBase * firstBase) * 0.5f / height;
CVector3D position = cameraTranslation + cameraIn * (camera.GetNearPlane() + distanceToCenter);
CVector3D position = cameraTranslation + cameraIn * (nearPlane + distanceToCenter);
const float radius = (cornerNear - position).Length();
// We need to convert the bounding box to the light space.
@ -158,10 +207,7 @@ ShadowMap::ShadowMap()
// Avoid using uninitialised values in AddShadowedBound if SetupFrame wasn't called first
m->LightTransform.SetIdentity();
m->FixedShadowsEnabled = false;
m->FixedShadowsDistance = 300.0f;
CFG_GET_VAL("shadowsfixed", m->FixedShadowsEnabled);
CFG_GET_VAL("shadowsfixeddistance", m->FixedShadowsDistance);
m->UpdateCascadesParameters();
}
ShadowMap::~ShadowMap()
@ -191,6 +237,8 @@ void ShadowMap::RecreateTexture()
m->DummyTexture = 0;
m->Framebuffer = 0;
m->UpdateCascadesParameters();
// (Texture will be constructed in next SetupFrame)
}
@ -200,14 +248,7 @@ void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir)
if (!m->Texture)
m->CreateTexture();
CVector3D x, eyepos;
if (!m->FixedShadowsEnabled)
{
x = camera.m_Orientation.GetIn();
eyepos = camera.m_Orientation.GetTranslation();
}
else
x = CVector3D(0, 1, 0);
CVector3D x(0, 1, 0), eyepos;
CVector3D z = lightdir;
z.Normalize();
@ -248,26 +289,63 @@ void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir)
m->LightTransform._44 = 1.0;
m->LightTransform.GetInverse(m->InvLightTransform);
m->ShadowCasterBound.SetEmpty();
m->ShadowReceiverBound.SetEmpty();
//
m->LightspaceCamera = camera;
m->LightspaceCamera.m_Orientation = m->LightTransform * camera.m_Orientation;
m->LightspaceCamera.UpdateFrustum();
if (m->FixedShadowsEnabled)
CalculateBoundsForFixedShadows(camera, m->LightTransform, camera.GetNearPlane(), m->FixedShadowsDistance, &m->FixedFrustumBounds);
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();
}
}
// AddShadowedBound: add a world-space bounding box to the bounds of shadowed
// objects
void ShadowMap::AddShadowCasterBound(const CBoundingBoxAligned& bounds)
void ShadowMap::AddShadowCasterBound(const int cascade, const CBoundingBoxAligned& bounds)
{
CBoundingBoxAligned lightspacebounds;
bounds.Transform(m->LightTransform, lightspacebounds);
m->ShadowCasterBound += lightspacebounds;
m->Cascades[cascade].ShadowCasterBound += lightspacebounds;
}
void ShadowMap::AddShadowReceiverBound(const CBoundingBoxAligned& bounds)
@ -278,14 +356,14 @@ void ShadowMap::AddShadowReceiverBound(const CBoundingBoxAligned& bounds)
m->ShadowReceiverBound += lightspacebounds;
}
CFrustum ShadowMap::GetShadowCasterCullFrustum()
CFrustum ShadowMap::GetShadowCasterCullFrustum(const int cascade)
{
// 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
bound.IntersectFrustumConservative(m->LightspaceCamera.GetFrustum());
bound.IntersectFrustumConservative(m->Cascades[cascade].FrustumBBAA.ToFrustum());
// ShadowBound might have been empty to begin with, producing an empty result
if (bound.IsEmpty())
@ -306,22 +384,14 @@ CFrustum ShadowMap::GetShadowCasterCullFrustum()
return frustum;
}
// CalcShadowMatrices: calculate required matrices for shadow map generation - the light's
// CalculateShadowMatrices: calculate required matrices for shadow map generation - the light's
// projection and transformation matrices
void ShadowMapInternals::CalcShadowMatrices()
void ShadowMapInternals::CalculateShadowMatrices(const int cascade)
{
if (FixedShadowsEnabled)
{
ShadowRenderBound = FixedFrustumBounds;
CBoundingBoxAligned& shadowRenderBound = Cascades[cascade].ShadowRenderBound;
shadowRenderBound = Cascades[cascade].ConvexBounds;
// 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
if (ShadowsCoverMap)
{
// Start building the shadow map to cover all objects that will receive shadows
CBoundingBoxAligned receiverBound = ShadowReceiverBound;
@ -333,38 +403,35 @@ void ShadowMapInternals::CalcShadowMatrices()
// 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
ShadowRenderBound[0].X = std::max(receiverBound[0].X, ShadowCasterBound[0].X);
ShadowRenderBound[0].Y = std::max(receiverBound[0].Y, ShadowCasterBound[0].Y);
ShadowRenderBound[1].X = std::min(receiverBound[1].X, ShadowCasterBound[1].X);
ShadowRenderBound[1].Y = std::min(receiverBound[1].Y, ShadowCasterBound[1].Y);
// 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;
// ShadowBound might have been empty to begin with, producing an empty result
if (ShadowRenderBound.IsEmpty())
{
// no-op
LightProjection.SetIdentity();
TextureMatrix = LightTransform;
return;
}
// round off the shadow boundaries to sane increments to help reduce swim effect
float boundInc = 16.0f;
ShadowRenderBound[0].X = floor(ShadowRenderBound[0].X / boundInc) * boundInc;
ShadowRenderBound[0].Y = floor(ShadowRenderBound[0].Y / boundInc) * boundInc;
ShadowRenderBound[1].X = ceil(ShadowRenderBound[1].X / boundInc) * boundInc;
ShadowRenderBound[1].Y = ceil(ShadowRenderBound[1].Y / boundInc) * boundInc;
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;
}
// 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;
// Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering
CVector3D scale = ShadowRenderBound[1] - ShadowRenderBound[0];
CVector3D shift = (ShadowRenderBound[1] + ShadowRenderBound[0]) * -0.5;
CVector3D scale = shadowRenderBound[1] - shadowRenderBound[0];
CVector3D shift = (shadowRenderBound[1] + shadowRenderBound[0]) * -0.5;
if (scale.X < 1.0)
scale.X = 1.0;
@ -378,17 +445,18 @@ void ShadowMapInternals::CalcShadowMatrices()
scale.Z = 2.0 / scale.Z;
// make sure a given world position falls on a consistent shadowmap texel fractional offset
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));
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));
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;
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;
// Calculate texture matrix by creating the clip space to texture coordinate matrix
// and then concatenating all matrices that have been calculated so far
@ -400,14 +468,14 @@ void ShadowMapInternals::CalcShadowMatrices()
CMatrix3D lightToTex;
lightToTex.SetZero();
lightToTex._11 = texscalex;
lightToTex._14 = (offsetX - ShadowRenderBound[0].X) * texscalex;
lightToTex._14 = (offsetX - shadowRenderBound[0].X) * texscalex;
lightToTex._22 = texscaley;
lightToTex._24 = (offsetY - ShadowRenderBound[0].Y) * texscaley;
lightToTex._24 = (offsetY - shadowRenderBound[0].Y) * texscaley;
lightToTex._33 = texscalez;
lightToTex._34 = -ShadowRenderBound[0].Z * texscalez;
lightToTex._34 = -shadowRenderBound[0].Z * texscalez;
lightToTex._44 = 1.0;
TextureMatrix = lightToTex * LightTransform;
Cascades[cascade].TextureMatrix = lightToTex * LightTransform;
}
// Create the shadow map
@ -437,35 +505,32 @@ void ShadowMapInternals::CreateTexture()
CFG_GET_VAL("shadowquality", QualityLevel);
// get shadow map size as next power of two up from view width/height
int shadow_map_size = (int)round_up_to_pow2((unsigned)std::max(g_Renderer.GetWidth(), g_Renderer.GetHeight()));
// Get shadow map size as next power of two up from view width/height.
int shadowMapSize;
switch (QualityLevel)
{
// Very Low
case -2:
shadow_map_size /= 4;
break;
// Low
case -1:
shadow_map_size /= 2;
shadowMapSize = 512;
break;
// High
case 1:
shadow_map_size *= 2;
shadowMapSize = 2048;
break;
// Ultra
case 2:
shadow_map_size *= 4;
shadowMapSize = std::max(round_up_to_pow2(std::max(g_Renderer.GetWidth(), g_Renderer.GetHeight())) * 4, 4096);
break;
// Medium as is
default:
shadowMapSize = 1024;
break;
}
Width = Height = shadow_map_size;
// Clamp to the maximum texture size
Width = std::min(Width, (int)ogl_max_tex_size);
Height = std::min(Height, (int)ogl_max_tex_size);
// Clamp to the maximum texture size.
shadowMapSize = std::min(shadowMapSize, static_cast<int>(ogl_max_tex_size));
Width = Height = shadowMapSize;
// Since we're using a framebuffer object, the whole texture is available
EffectiveWidth = Width;
@ -477,7 +542,7 @@ void ShadowMapInternals::CreateTexture()
format = GL_DEPTH_COMPONENT;
formatName = "DEPTH_COMPONENT";
#else
switch ( DepthTextureBits )
switch (DepthTextureBits)
{
case 16: format = GL_DEPTH_COMPONENT16; formatName = "DEPTH_COMPONENT16"; break;
case 24: format = GL_DEPTH_COMPONENT24; formatName = "DEPTH_COMPONENT24"; break;
@ -570,9 +635,6 @@ void ShadowMapInternals::CreateTexture()
// Set up to render into shadow map texture
void ShadowMap::BeginRender()
{
// Calc remaining shadow matrices
m->CalcShadowMatrices();
{
PROFILE("bind framebuffer");
glBindTexture(GL_TEXTURE_2D, 0);
@ -585,23 +647,36 @@ void ShadowMap::BeginRender()
// 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.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glColorMask(0,0,0,0);
if (g_RenderingOptions.GetShadowAlphaFix())
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glColorMask(0, 0, 0, 0);
}
else
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
// setup viewport
const SViewPort vp = { 0, 0, m->EffectiveWidth, m->EffectiveHeight };
g_Renderer.SetViewport(vp);
m->SavedViewCamera = g_Renderer.GetViewCamera();
CCamera c = m->SavedViewCamera;
c.SetProjection(m->LightProjection);
c.GetOrientation() = m->InvLightTransform;
g_Renderer.SetViewCamera(c);
glEnable(GL_SCISSOR_TEST);
glScissor(1,1, m->EffectiveWidth-2, m->EffectiveHeight-2);
}
void ShadowMap::PrepareCamera(const int cascade)
{
m->CalculateShadowMatrices(cascade);
const SViewPort vp = { 0, 0, m->EffectiveWidth, m->EffectiveHeight };
g_Renderer.SetViewport(vp);
CCamera camera = m->SavedViewCamera;
camera.SetProjection(m->Cascades[cascade].LightProjection);
camera.GetOrientation() = m->InvLightTransform;
g_Renderer.SetViewCamera(camera);
const SViewPort& cascadeViewPort = m->Cascades[cascade].ViewPort;
glScissor(
cascadeViewPort.m_X, cascadeViewPort.m_Y,
cascadeViewPort.m_Width, cascadeViewPort.m_Height);
}
// Finish rendering into shadow map texture
@ -619,7 +694,8 @@ void ShadowMap::EndRender()
const SViewPort vp = { 0, 0, g_Renderer.GetWidth(), g_Renderer.GetHeight() };
g_Renderer.SetViewport(vp);
glColorMask(1,1,1,1);
if (g_RenderingOptions.GetShadowAlphaFix())
glColorMask(1, 1, 1, 1);
}
void ShadowMap::BindTo(const CShaderProgramPtr& shader) const
@ -628,8 +704,29 @@ void ShadowMap::BindTo(const CShaderProgramPtr& shader) const
return;
shader->BindTexture(str_shadowTex, m->Texture);
shader->Uniform(str_shadowTransform, m->TextureMatrix);
shader->Uniform(str_shadowScale, m->Width, m->Height, 1.0f / m->Width, 1.0f / m->Height);
const CVector3D cameraForward = g_Renderer.GetCullCamera().GetOrientation().GetIn();
shader->Uniform(str_cameraForward, cameraForward.X, cameraForward.Y, cameraForward.Z,
cameraForward.Dot(g_Renderer.GetCullCamera().GetOrientation().GetTranslation()));
if (GetCascadeCount() == 1)
{
shader->Uniform(str_shadowTransform, m->Cascades[0].TextureMatrix);
shader->Uniform(str_shadowDistance, m->Cascades[0].Distance);
}
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);
}
shader->Uniform(str_shadowTransforms_0, GetCascadeCount(), shadowTransforms.data());
shader->Uniform(str_shadowTransforms, GetCascadeCount(), shadowTransforms.data());
shader->Uniform(str_shadowDistances_0, GetCascadeCount(), shadowDistances.data());
shader->Uniform(str_shadowDistances, GetCascadeCount(), shadowDistances.data());
}
}
// Depth texture bits
@ -661,34 +758,34 @@ void ShadowMap::RenderDebugBounds()
// Render various shadow bounds:
// Yellow = bounds of objects in view frustum that receive shadows
// Red = culling frustum used to find potential shadow casters
// Green = bounds of objects in culling frustum that cast shadows
// Blue = frustum used for rendering the shadow map
const CMatrix3D transform = g_Renderer.GetViewCamera().GetViewProjection() * m->InvLightTransform;
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
g_Renderer.GetDebugRenderer().DrawBoundingBoxOutline(m->ShadowReceiverBound, CColor(1.0f, 1.0f, 0.0f, 1.0f), transform);
g_Renderer.GetDebugRenderer().DrawBoundingBoxOutline(m->ShadowCasterBound, CColor(0.0f, 1.0f, 0.0f, 1.0f), transform);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
g_Renderer.GetDebugRenderer().DrawBoundingBox(m->ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.25f), transform);
glDisable(GL_BLEND);
g_Renderer.GetDebugRenderer().DrawBoundingBoxOutline(m->ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 1.0f), transform);
// Render light frustum
for (int cascade = 0; cascade < GetCascadeCount(); ++cascade)
{
glEnable(GL_BLEND);
g_Renderer.GetDebugRenderer().DrawBoundingBox(m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.10f), transform);
g_Renderer.GetDebugRenderer().DrawBoundingBoxOutline(m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.5f), transform);
glDisable(GL_BLEND);
CFrustum frustum = GetShadowCasterCullFrustum();
// 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
CBoundingBoxAligned dummy(CVector3D(-1e4, -1e4, -1e4), CVector3D(1e4, 1e4, 1e4));
CBrush brush(dummy);
CBrush frustumBrush;
brush.Intersect(frustum, frustumBrush);
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);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
g_Renderer.GetDebugRenderer().DrawBrush(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.25f));
glDisable(GL_BLEND);
g_Renderer.GetDebugRenderer().DrawBrushOutline(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 1.0f));
glEnable(GL_BLEND);
g_Renderer.GetDebugRenderer().DrawBrush(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.1f));
g_Renderer.GetDebugRenderer().DrawBrushOutline(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.5f));
glDisable(GL_BLEND);
}
glEnable(GL_CULL_FACE);
glDepthMask(1);
@ -741,3 +838,12 @@ void ShadowMap::RenderDebugTexture()
ogl_WarnIfError();
}
int ShadowMap::GetCascadeCount() const
{
#if CONFIG2_GLES
return 1;
#else
return m->ShadowsCoverMap ? 1 : m->CascadeCount;
#endif
}

View File

@ -80,7 +80,7 @@ public:
*
* @param bounds world space bounding box
*/
void AddShadowCasterBound(const CBoundingBoxAligned& bounds);
void AddShadowCasterBound(const int cascade, const CBoundingBoxAligned& bounds);
/**
* Add the bounding box of an object that will receive a shadow.
@ -98,7 +98,7 @@ public:
* shadow. Those objects should be passed to AddShadowCasterBound and
* then should be rendered into the shadow map.
*/
CFrustum GetShadowCasterCullFrustum();
CFrustum GetShadowCasterCullFrustum(const int cascade);
/**
* BeginRender: Set OpenGL state for rendering into the shadow map texture.
@ -114,6 +114,16 @@ public:
*/
void EndRender();
/**
* Returns the current number of used cascades.
*/
int GetCascadeCount() const;
/**
* Sets the renderer camera for the cascade.
*/
void PrepareCamera(const int cascade);
/**
* Binds all needed resources and uniforms to draw shadows using the shader.
*/