You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复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) );
}

问题根源

  1. 静态顶点偏移适配性差:固定值shadowMapOffset无法适配不同模型尺寸和光照角度,易导致阴影错位或过度偏移。
  2. 固定阴影偏置局限性:固定0.005的偏置值在光照与表面夹角较小时(如高角度俯视),无法解决深度冲突,引发大面积自阴影。
  3. 法线空间错误:顶点着色器中使用视图空间法线计算偏移,会受相机视角影响,导致阴影计算偏差。

修复方案与修改后代码

核心修复点

  • 移除静态顶点偏移,改用动态阴影偏置,根据光照与表面法线的夹角自动调整偏置值
  • 切换到世界空间法线计算,避免相机视角干扰
  • 约束阴影贴图采样范围,防止越界错误

修改后片段着色器

$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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 11:27:35