(Monogame/HLSL) ShadowMapping 问题 - 阴影取决于相机位置
(Monogame/HLSL) Problems with ShadowMapping - Shadow dependent on Camera position
我在这个问题上苦苦思索了好久,终于意识到我需要认真的帮助...
所以基本上我想在我用 Monogame 编写的项目中实现适当的阴影。为此,我使用多个教程在 HLSL 中编写了一个延迟的 dhader,主要用于旧的 XNA。
问题是,虽然我的灯光和阴影适用于聚光灯,但我场景地板上的灯光非常依赖于我的相机,正如您在图像中看到的那样:https://imgur.com/a/TU7y0bs
我尝试了很多不同的方法来解决这个问题:
- 更大的 DepthBias 会扩大半径,即 "shadow free" 大量的平移,并且所描述的问题根本没有解决。
- 一篇论文建议使用指数阴影贴图,但我根本不喜欢这个结果,因为光渗出是难以忍受的,而且较小的阴影(比如墙上手电筒后面的阴影)不会被渲染。
- 我将 GBuffer DepthMap 切换为 1-z/w 以获得更高的精度,但这也没有解决问题。
我正在使用
new RenderTarget2D(device,
Width, Height, false, SurfaceFormat.Vector2, DepthFormat.Depth24Stencil8)
从灯光角度存储深度。
我使用这个 PixelShader 函数计算阴影:
请注意,我想在未来将此着色器调整为点光源 - 这就是为什么我只使用长度(LightPos - PixelPos)。
SpotLight.fx - 像素着色器
float4 PS(VSO input) : SV_TARGET0
{
// Fancy Lighting equations
input.ScreenPosition.xy /= input.ScreenPosition.w;
float2 UV = 0.5f * (float2(input.ScreenPosition.x, -input.ScreenPosition.y) + 1) - float2(1.0f / GBufferTextureSize.xy);
// Sample Depth from DepthMap
float Depth = DepthMap.Sample(SampleTypeClamp, UV).x;
// Getting the PixelPosition in WorldSpace
float4 Position = 1.0f;
Position.xy = input.ScreenPosition.xy;
Position.z = Depth;
// Transform Position to WorldSpace
Position = mul(Position, InverseViewProjection);
Position /= Position.w;
float4 LightScreenPos = mul(Position, LightViewProjection);
LightScreenPos /= LightScreenPos.w;
// Calculate Projected UV from Light POV -> ScreenPos is in [-1;1] Space
float2 LightUV = 0.5f * (float2(LightScreenPos.x, -LightScreenPos.y) + 1.0f);
float lightDepth = ShadowMap.Sample(SampleDot, LightUV).r;
// Linear depth model
float closestDepth = lightDepth * LightFarplane; // Depth is stored in [0, 1]; bring it to [0, farplane]
float currentDepth = length(LightPosition.xyz - Position.xyz) - DepthBias;
ShadowFactor = step(currentDepth, closestDepth); // closestDepth > currentDepth -> Occluded, Shadow.
float4 phong = Phong(...);
return ShadowFactor * phong;
}
LightViewProjection 就是 light.View * light.Projection
InverseViewProjection 是 Matrix.Invert(camera.View * Camera.Projection)
Phong() 是我调用来完成照明的函数
lightDepthMap 仅存储 length(lightPos - Position)
我希望图片中显示的工件消失,以便能够使代码也适用于点光源。
这可能是我从屏幕 space 检索世界位置的方式有问题吗?我的深度分辨率太低了?
非常感谢您的帮助!
---更新---
我更改了光照着色器以显示存储在 shadowMap 中的距离与在 Pixelshader 中现场计算的距离之间的差异:
float4 PixelShaderFct(...) : SV_TARGET0
{
// Get Depth from Texture
float4 Position = 1.0f;
Position.xy = input.ScreenPosition.xy;
Position.z = Depth;
Position = mul(Position, InverseViewProjection);
Position /= Position.w;
float4 LightScreenPos = mul(Position, LightViewProjection);
LightScreenPos /= LightScreenPos.w;
// Calculate Projected UV from Light POV -> ScreenPos is in [-1;1] Space
float2 LUV = 0.5f * (float2(LightScreenPos.x, -LightScreenPos.y) + 1.0f);
float lightZ = ShadowMap.Sample(SampleDot, LUV).r;
float Attenuation = AttenuationMap.Sample(SampleType, LUV).r;
float ShadowFactor = 1;
// Linear depth model; lightZ stores (LightPos - Pos)/LightFarPlane
float closestDepth = lightZ * LightFarPlane;
float currentDepth = length(LightPosition.xyz - Position.xyz) -DepthBias;
return (closestDepth - currentDepth);
}
因为我基本上是在输出 Length - (Length - Bias),所以人们会希望得到一个颜色为 "DepthBias" 的图像。但这不是我得到的结果:
根据这个结果,我假设我遇到了精度问题(我觉得这很奇怪,因为我正在处理 [0.1, 50] 的近平面和远平面),或者有什么问题我从 DepthMap 恢复给定像素的 worldPosition 的方式。
我终于找到了解决方案,如果有人遇到同样的问题,我会把它发布在这里:
我使用的教程是针对 XNA / DX9 的。但由于我的目标是 DX10+,所以需要做一点小改动:
在XNA / DX9 中,UV 坐标与实际像素不对齐,需要对齐。这就是 float2 UV = 0.5f * (float2(input.ScreenPosition.x, -input.ScreenPosition.y) + 1) - float2(1.0f / GBufferTextureSize.xy);
中的 - float2(1.0f / GBufferTextureSize.xy);
的用途。这在 DX10 及更高版本中不需要,并且会导致我遇到的问题。
解决方案:
全屏四边形的 UV 坐标:
对于 XNA/DX9:
input.ScreenPosition.xy /= input.ScreenPosition.w;
float2 UV = 0.5f * (float2(input.ScreenPosition.x, -input.ScreenPosition.y) + 1) - float2(1.0f / GBufferTextureSize.xy);
对于 Monogame / DX10+
input.ScreenPosition.xy /= input.ScreenPosition.w;
float2 UV = 0.5f * (float2(input.ScreenPosition.x, -input.ScreenPosition.y) + 1)
我在这个问题上苦苦思索了好久,终于意识到我需要认真的帮助...
所以基本上我想在我用 Monogame 编写的项目中实现适当的阴影。为此,我使用多个教程在 HLSL 中编写了一个延迟的 dhader,主要用于旧的 XNA。 问题是,虽然我的灯光和阴影适用于聚光灯,但我场景地板上的灯光非常依赖于我的相机,正如您在图像中看到的那样:https://imgur.com/a/TU7y0bs
我尝试了很多不同的方法来解决这个问题:
- 更大的 DepthBias 会扩大半径,即 "shadow free" 大量的平移,并且所描述的问题根本没有解决。
- 一篇论文建议使用指数阴影贴图,但我根本不喜欢这个结果,因为光渗出是难以忍受的,而且较小的阴影(比如墙上手电筒后面的阴影)不会被渲染。
- 我将 GBuffer DepthMap 切换为 1-z/w 以获得更高的精度,但这也没有解决问题。
我正在使用
new RenderTarget2D(device,
Width, Height, false, SurfaceFormat.Vector2, DepthFormat.Depth24Stencil8)
从灯光角度存储深度。
我使用这个 PixelShader 函数计算阴影: 请注意,我想在未来将此着色器调整为点光源 - 这就是为什么我只使用长度(LightPos - PixelPos)。 SpotLight.fx - 像素着色器
float4 PS(VSO input) : SV_TARGET0
{
// Fancy Lighting equations
input.ScreenPosition.xy /= input.ScreenPosition.w;
float2 UV = 0.5f * (float2(input.ScreenPosition.x, -input.ScreenPosition.y) + 1) - float2(1.0f / GBufferTextureSize.xy);
// Sample Depth from DepthMap
float Depth = DepthMap.Sample(SampleTypeClamp, UV).x;
// Getting the PixelPosition in WorldSpace
float4 Position = 1.0f;
Position.xy = input.ScreenPosition.xy;
Position.z = Depth;
// Transform Position to WorldSpace
Position = mul(Position, InverseViewProjection);
Position /= Position.w;
float4 LightScreenPos = mul(Position, LightViewProjection);
LightScreenPos /= LightScreenPos.w;
// Calculate Projected UV from Light POV -> ScreenPos is in [-1;1] Space
float2 LightUV = 0.5f * (float2(LightScreenPos.x, -LightScreenPos.y) + 1.0f);
float lightDepth = ShadowMap.Sample(SampleDot, LightUV).r;
// Linear depth model
float closestDepth = lightDepth * LightFarplane; // Depth is stored in [0, 1]; bring it to [0, farplane]
float currentDepth = length(LightPosition.xyz - Position.xyz) - DepthBias;
ShadowFactor = step(currentDepth, closestDepth); // closestDepth > currentDepth -> Occluded, Shadow.
float4 phong = Phong(...);
return ShadowFactor * phong;
}
LightViewProjection 就是 light.View * light.Projection
InverseViewProjection 是 Matrix.Invert(camera.View * Camera.Projection)
Phong() 是我调用来完成照明的函数
lightDepthMap 仅存储 length(lightPos - Position)
我希望图片中显示的工件消失,以便能够使代码也适用于点光源。
这可能是我从屏幕 space 检索世界位置的方式有问题吗?我的深度分辨率太低了?
非常感谢您的帮助!
---更新---
我更改了光照着色器以显示存储在 shadowMap 中的距离与在 Pixelshader 中现场计算的距离之间的差异:
float4 PixelShaderFct(...) : SV_TARGET0
{
// Get Depth from Texture
float4 Position = 1.0f;
Position.xy = input.ScreenPosition.xy;
Position.z = Depth;
Position = mul(Position, InverseViewProjection);
Position /= Position.w;
float4 LightScreenPos = mul(Position, LightViewProjection);
LightScreenPos /= LightScreenPos.w;
// Calculate Projected UV from Light POV -> ScreenPos is in [-1;1] Space
float2 LUV = 0.5f * (float2(LightScreenPos.x, -LightScreenPos.y) + 1.0f);
float lightZ = ShadowMap.Sample(SampleDot, LUV).r;
float Attenuation = AttenuationMap.Sample(SampleType, LUV).r;
float ShadowFactor = 1;
// Linear depth model; lightZ stores (LightPos - Pos)/LightFarPlane
float closestDepth = lightZ * LightFarPlane;
float currentDepth = length(LightPosition.xyz - Position.xyz) -DepthBias;
return (closestDepth - currentDepth);
}
因为我基本上是在输出 Length - (Length - Bias),所以人们会希望得到一个颜色为 "DepthBias" 的图像。但这不是我得到的结果:
根据这个结果,我假设我遇到了精度问题(我觉得这很奇怪,因为我正在处理 [0.1, 50] 的近平面和远平面),或者有什么问题我从 DepthMap 恢复给定像素的 worldPosition 的方式。
我终于找到了解决方案,如果有人遇到同样的问题,我会把它发布在这里:
我使用的教程是针对 XNA / DX9 的。但由于我的目标是 DX10+,所以需要做一点小改动:
在XNA / DX9 中,UV 坐标与实际像素不对齐,需要对齐。这就是 float2 UV = 0.5f * (float2(input.ScreenPosition.x, -input.ScreenPosition.y) + 1) - float2(1.0f / GBufferTextureSize.xy);
中的 - float2(1.0f / GBufferTextureSize.xy);
的用途。这在 DX10 及更高版本中不需要,并且会导致我遇到的问题。
解决方案:
全屏四边形的 UV 坐标:
对于 XNA/DX9:
input.ScreenPosition.xy /= input.ScreenPosition.w;
float2 UV = 0.5f * (float2(input.ScreenPosition.x, -input.ScreenPosition.y) + 1) - float2(1.0f / GBufferTextureSize.xy);
对于 Monogame / DX10+
input.ScreenPosition.xy /= input.ScreenPosition.w;
float2 UV = 0.5f * (float2(input.ScreenPosition.x, -input.ScreenPosition.y) + 1)