Graphics/ShadowMapping: Replace bias by a position scale

Inspired by Godot (see https://github.com/godotengine/godot-proposals/issues/4517)
This commit is contained in:
SirLynix 2023-10-21 19:31:07 +02:00
parent b04d7662ce
commit 6a9177a109
6 changed files with 60 additions and 12 deletions

View File

@ -264,6 +264,8 @@ namespace Nz
settings.AddPass(depthPassIndex, depthPass);
MaterialPass shadowPass = depthPass;
shadowPass.options[CRC32("ShadowPass")] = true;
shadowPass.states.frontFace = FrontFace::Clockwise;
shadowPass.states.depthClamp = enabledFeatures.depthClamping;
settings.AddPass(shadowPassIndex, shadowPass);
@ -290,6 +292,8 @@ namespace Nz
settings.AddPass(depthPassIndex, depthPass);
MaterialPass shadowPass = depthPass;
shadowPass.options[CRC32("ShadowPass")] = true;
shadowPass.states.frontFace = FrontFace::Clockwise;
shadowPass.states.depthClamp = enabledFeatures.depthClamping;
settings.AddPass(shadowPassIndex, shadowPass);
@ -316,6 +320,8 @@ namespace Nz
settings.AddPass(depthPassIndex, depthPass);
MaterialPass shadowPass = depthPass;
shadowPass.options[CRC32("ShadowPass")] = true;
shadowPass.states.frontFace = FrontFace::Clockwise;
shadowPass.states.depthClamp = enabledFeatures.depthClamping;
settings.AddPass(shadowPassIndex, shadowPass);

View File

@ -16,6 +16,8 @@ namespace Nz
settings.AddValueProperty<Color>("BaseColor", Color::White());
settings.AddValueProperty<bool>("AlphaTest", false);
settings.AddValueProperty<float>("AlphaTestThreshold", 0.2f);
settings.AddValueProperty<float>("ShadowMapNormalOffset", 0.f);
settings.AddValueProperty<float>("ShadowPosScale", 1.f - 0.0025f);
settings.AddTextureProperty("BaseColorMap", ImageType::E2D);
settings.AddTextureProperty("AlphaMap", ImageType::E2D);
settings.AddPropertyHandler(std::make_unique<OptionValuePropertyHandler>("AlphaTest", "AlphaTest"));
@ -23,6 +25,8 @@ namespace Nz
settings.AddPropertyHandler(std::make_unique<TexturePropertyHandler>("AlphaMap", "HasAlphaTexture"));
settings.AddPropertyHandler(std::make_unique<UniformValuePropertyHandler>("BaseColor"));
settings.AddPropertyHandler(std::make_unique<UniformValuePropertyHandler>("AlphaTestThreshold"));
settings.AddPropertyHandler(std::make_unique<UniformValuePropertyHandler>("ShadowMapNormalOffset"));
settings.AddPropertyHandler(std::make_unique<UniformValuePropertyHandler>("ShadowPosScale"));
}
void PredefinedMaterials::AddPbrSettings(MaterialSettings& settings)

View File

@ -9,6 +9,7 @@ import SkinLinearPosition from Engine.SkinningLinear;
// Pass-specific options
option DepthPass: bool = false;
option DistanceDepth: bool = false;
option ShadowPass: bool = false;
// Material options
option HasBaseColorTexture: bool = false;
@ -23,12 +24,14 @@ option BillboardSizeRotLocation: i32 = -1;
// Vertex declaration related options
option VertexColorLoc: i32 = -1;
option VertexNormalLoc: i32 = -1;
option VertexPositionLoc: i32;
option VertexUvLoc: i32 = -1;
option VertexJointIndicesLoc: i32 = -1;
option VertexJointWeightsLoc: i32 = -1;
const HasNormal = (VertexNormalLoc >= 0);
const HasVertexColor = (VertexColorLoc >= 0);
const HasColor = (HasVertexColor || Billboard);
const HasUV = (VertexUvLoc >= 0);
@ -40,6 +43,12 @@ struct MaterialSettings
[tag("AlphaTestThreshold")]
AlphaThreshold: f32,
[tag("ShadowMapNormalOffset")]
ShadowMapNormalOffset: f32,
[tag("ShadowPosScale")]
ShadowPosScale: f32,
[tag("BaseColor")]
BaseColor: vec4[f32]
}
@ -157,6 +166,9 @@ struct VertIn
[cond(HasVertexColor), location(VertexColorLoc)]
color: vec4[f32],
[cond(HasNormal), location(VertexNormalLoc)]
normal: vec3[f32],
[cond(HasUV), location(VertexUvLoc)]
uv: vec2[f32],
@ -230,6 +242,13 @@ fn main(input: VertIn) -> VertToFrag
else
pos = input.pos;
const if (ShadowPass)
{
pos *= settings.ShadowPosScale;
const if (HasNormal)
pos -= input.normal * settings.ShadowMapNormalOffset;
}
let worldPosition = instanceData.worldMatrix * vec4[f32](pos, 1.0);
let output: VertToFrag;

View File

@ -33,10 +33,6 @@ fn ComputeDirectionalLightShadow(light: DirectionalLight, shadowmap: depth_sampl
let lightProjPos = light.viewProjMatrices[cascadeIndex] * vec4[f32](worldPos, 1.0);
let shadowCoords = lightProjPos.xyz / lightProjPos.w;
// calculate bias (based on depth map resolution and slope)
let bias = max(0.05 * (1.0 - lambert), 0.005);
bias *= 1.0 / (light.cascadeDistances[cascadeIndex] * 0.05);
shadowFactor = 0.0;
[unroll]
for x in -1 -> 2
@ -45,7 +41,7 @@ fn ComputeDirectionalLightShadow(light: DirectionalLight, shadowmap: depth_sampl
for y in -1 -> 2
{
let coords = shadowCoords.xy + vec2[f32](f32(x), f32(y)) * light.invShadowMapSize;
shadowFactor += shadowmap.SampleDepthComp(vec3[f32](coords, f32(cascadeIndex)), shadowCoords.z - bias).r;
shadowFactor += shadowmap.SampleDepthComp(vec3[f32](coords, f32(cascadeIndex)), shadowCoords.z).r;
}
}
shadowFactor /= 9.0;
@ -74,8 +70,6 @@ fn ComputePointLightShadow(light: PointLight, shadowmap: sampler_cube[f32], dist
const start = vec3[f32](offset * 0.5, offset * 0.5, offset * 0.5);
const shadowContribution = 1.0 / f32(sampleCount * sampleCount * sampleCount);
let bias = 0.05;
[unroll]
for x in 0 -> sampleCount
{
@ -91,7 +85,7 @@ fn ComputePointLightShadow(light: PointLight, shadowmap: sampler_cube[f32], dist
let closestDepth = shadowmap.Sample(sampleDir).r;
closestDepth *= light.radius;
if (closestDepth > dist - bias)
if (closestDepth > dist)
shadowFactor += shadowContribution;
}
}
@ -110,9 +104,6 @@ fn ComputeSpotLightShadow(light: SpotLight, shadowmap: depth_sampler2D[f32], wor
{
if (light.invShadowMapSize.x > 0.0)
{
let bias = 0.0005 * tan(acos(lambert));
bias = clamp(bias, 0.0, 0.01);
let lightProjPos = light.viewProjMatrix * vec4[f32](worldPos, 1.0);
let shadowCoords = lightProjPos.xyz / lightProjPos.w;
@ -124,7 +115,7 @@ fn ComputeSpotLightShadow(light: SpotLight, shadowmap: depth_sampler2D[f32], wor
for y in -1 -> 2
{
let coords = shadowCoords.xy + vec2[f32](f32(x), f32(y)) * light.invShadowMapSize;
shadowFactor += shadowmap.SampleDepthComp(coords, shadowCoords.z - bias).r;
shadowFactor += shadowmap.SampleDepthComp(coords, shadowCoords.z).r;
}
}
shadowFactor /= 9.0;

View File

@ -12,6 +12,7 @@ import SkinLinearPosition, SkinLinearPositionNormal from Engine.SkinningLinear;
// Pass-specific options
option DepthPass: bool = false;
option DistanceDepth: bool = false;
option ShadowPass: bool = false;
// Basic material options
option HasBaseColorTexture: bool = false;
@ -58,6 +59,12 @@ struct MaterialSettings
[tag("AlphaTestThreshold")]
AlphaThreshold: f32,
[tag("ShadowMapNormalOffset")]
ShadowMapNormalOffset: f32,
[tag("ShadowPosScale")]
ShadowPosScale: f32,
[tag("BaseColor")]
BaseColor: vec4[f32],
@ -403,6 +410,13 @@ fn main(input: VertIn) -> VertToFrag
normal = input.normal;
}
const if (ShadowPass)
{
pos *= settings.ShadowPosScale;
const if (HasNormal)
pos -= input.normal * settings.ShadowMapNormalOffset;
}
let worldPosition = instanceData.worldMatrix * vec4[f32](pos, 1.0);
let output: VertToFrag;

View File

@ -12,6 +12,7 @@ import SkinLinearPosition, SkinLinearPositionNormal from Engine.SkinningLinear;
// Pass-specific options
option DepthPass: bool = false;
option DistanceDepth: bool = false;
option ShadowPass: bool = false;
// Basic material options
option HasBaseColorTexture: bool = false;
@ -59,6 +60,12 @@ struct MaterialSettings
[tag("AlphaTestThreshold")]
AlphaThreshold: f32,
[tag("ShadowMapNormalOffset")]
ShadowMapNormalOffset: f32,
[tag("ShadowPosScale")]
ShadowPosScale: f32,
[tag("BaseColor")]
BaseColor: vec4[f32],
}
@ -387,6 +394,13 @@ fn main(input: VertIn) -> VertToFrag
normal = input.normal;
}
const if (ShadowPass)
{
pos *= settings.ShadowPosScale;
const if (HasNormal)
pos -= input.normal * settings.ShadowMapNormalOffset;
}
let worldPosition = instanceData.worldMatrix * vec4[f32](pos, 1.0);
let output: VertToFrag;