如何修复BGFX阴影映射着色器中的自阴影伪影问题?
阴影映射自阴影问题排查与修复
问题现象
- 飞船阴影错位,无法正确贴合模型表面
- 高角度俯视飞船时,整艘船完全处于阴影中
原始着色器代码
片段着色器
$input v_texcoord0, v_normal, v_wpos, v_shadowcoord #include "bgfx_shader.sh" #include "common.sh" // Camera and lighting uniforms uniform float4 u_CameraPos; uniform float4 u_LightDir; uniform float4 u_LightColour; uniform float4 u_AmbientLightColour; uniform float4 u_LightParams; // x = LightStrength, y = AmbientStrength uniform float4 u_SpecularParams; // x = SpecularStrength, y = SpecularPower uniform float4 u_ShadowSize; // Textures SAMPLER2D(s_texColor, 0); SAMPLER2DSHADOW(s_shadowMap, 1); // Sample shadow with bias float hardShadow(vec4 _shadowCoord, float _bias) { vec3 texCoord = _shadowCoord.xyz / _shadowCoord.w; return bgfxShadow2D(s_shadowMap, vec3(texCoord.xy, texCoord.z - _bias)); } void main() { float shadowMapBias = 0.005; // Normalize vectors vec3 normal = normalize(v_normal); vec3 lightDir = normalize(-u_LightDir.xyz); vec3 viewDir = normalize(u_CameraPos.xyz - v_wpos); // Diffuse lighting float diff = max(dot(normal, lightDir), 0.0); vec3 diffuse = diff * u_LightColour.xyz; // Specular lighting vec3 reflectDir = reflect(-lightDir, normal); float spec = pow(max(dot(viewDir, reflectDir), 0.0), u_SpecularParams.y); vec3 specular = spec * u_LightColour.xyz * u_SpecularParams.x; // Shadow visibility (simple hard shadow) float visibility = hardShadow(v_shadowcoord, shadowMapBias); // Combine ambient, diffuse, specular with shadow vec3 ambient = u_AmbientLightColour.xyz * u_LightParams.y; vec3 lighting = ambient + visibility * (diffuse * u_LightParams.x + specular); // Apply texture color vec4 texColor = texture2D(s_texColor, v_texcoord0); gl_FragColor = vec4(texColor.rgb * lighting, texColor.a); }
顶点着色器
$input a_position, a_texcoord0, a_normal $output v_texcoord0, v_normal, v_wpos, v_shadowcoord #include "bgfx_shader.sh" uniform mat4 u_LightMtx; void main() { gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0) ); v_normal = normalize(mul(u_modelView, vec4(a_normal.xyz, 0.0) ).xyz); v_texcoord0 = a_texcoord0; const float shadowMapOffset = 0.001; vec3 posOffset = a_position + a_normal.xyz * shadowMapOffset; v_shadowcoord = mul(u_LightMtx, vec4(posOffset, 1.0) ); }
问题根源
- 静态顶点偏移适配性差:固定值
shadowMapOffset无法适配不同模型尺寸和光照角度,易导致阴影错位或过度偏移。 - 固定阴影偏置局限性:固定
0.005的偏置值在光照与表面夹角较小时(如高角度俯视),无法解决深度冲突,引发大面积自阴影。 - 法线空间错误:顶点着色器中使用视图空间法线计算偏移,会受相机视角影响,导致阴影计算偏差。
修复方案与修改后代码
核心修复点
- 移除静态顶点偏移,改用动态阴影偏置,根据光照与表面法线的夹角自动调整偏置值
- 切换到世界空间法线计算,避免相机视角干扰
- 约束阴影贴图采样范围,防止越界错误
修改后片段着色器
$input v_texcoord0, v_normal, v_wpos, v_shadowcoord #include "bgfx_shader.sh" #include "common.sh" // Camera and lighting uniforms uniform float4 u_CameraPos; uniform float4 u_LightDir; uniform float4 u_LightColour; uniform float4 u_AmbientLightColour; uniform float4 u_LightParams; // x = LightStrength, y = AmbientStrength uniform float4 u_SpecularParams; // x = SpecularStrength, y = SpecularPower uniform float4 u_ShadowSize; // Textures SAMPLER2D(s_texColor, 0); SAMPLER2DSHADOW(s_shadowMap, 1); // Sample shadow with dynamic bias float hardShadow(vec4 _shadowCoord, vec3 _normal, vec3 _lightDir) { vec3 texCoord = _shadowCoord.xyz / _shadowCoord.w; // 约束UV在[0.001, 0.999]范围,避免采样超出阴影贴图边界 texCoord.xy = clamp(texCoord.xy, 0.001, 0.999); // 动态偏置:表面越平行于光照,偏置值越大,解决深度冲突 float bias = max(0.005 * (1.0 - dot(_normal, _lightDir)), 0.0005); return bgfxShadow2D(s_shadowMap, vec3(texCoord.xy, texCoord.z - bias)); } void main() { // Normalize vectors vec3 normal = normalize(v_normal); vec3 lightDir = normalize(-u_LightDir.xyz); vec3 viewDir = normalize(u_CameraPos.xyz - v_wpos); // Diffuse lighting float diff = max(dot(normal, lightDir), 0.0); vec3 diffuse = diff * u_LightColour.xyz; // Specular lighting vec3 reflectDir = reflect(-lightDir, normal); float spec = pow(max(dot(viewDir, reflectDir), 0.0), u_SpecularParams.y); vec3 specular = spec * u_LightColour.xyz * u_SpecularParams.x; // Shadow visibility with dynamic bias float visibility = hardShadow(v_shadowcoord, normal, lightDir); // Combine ambient, diffuse, specular with shadow vec3 ambient = u_AmbientLightColour.xyz * u_LightParams.y; vec3 lighting = ambient + visibility * (diffuse * u_LightParams.x + specular); // Apply texture color vec4 texColor = texture2D(s_texColor, v_texcoord0); gl_FragColor = vec4(texColor.rgb * lighting, texColor.a); }
修改后顶点着色器
$input a_position, a_texcoord0, a_normal $output v_texcoord0, v_normal, v_wpos, v_shadowcoord #include "bgfx_shader.sh" uniform mat4 u_LightMtx; uniform mat4 u_model; // 新增模型矩阵,用于计算世界空间法线和位置 void main() { gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0) ); // 使用模型矩阵计算世界空间法线,避免视图空间的视角干扰 v_normal = normalize(mul(u_model, vec4(a_normal.xyz, 0.0) ).xyz); v_texcoord0 = a_texcoord0; // 移除静态顶点偏移,直接用原始位置计算阴影坐标 v_shadowcoord = mul(u_LightMtx, vec4(a_position, 1.0) ); // 计算世界空间位置(确保v_wpos正确赋值) v_wpos = mul(u_model, vec4(a_position, 1.0) ).xyz; }
额外注意事项
- 确保应用层正确传递
u_model矩阵uniform到顶点着色器 - 可根据模型尺寸和阴影贴图分辨率,微调动态偏置的参数(
0.005和0.0005),找到阴影质量与偏移的平衡点 - 若阴影边缘锯齿明显,可替换硬阴影为PCF(百分比渐近过滤)采样,提升阴影平滑度
内容的提问来源于stack exchange,提问作者Jevan Dean
相关产品推荐
相关产品推荐

