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:
parent
ca6fcb28ab
commit
30e135693e
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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]")
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user