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

如何在Vulkan中为漫反射光照应用深度测试以修正异常照亮问题?

解决Vulkan基础漫反射中背光面错误照亮的问题

嘿,我正好之前在Vulkan里踩过这个坑!你遇到的问题本质是缺少自阴影计算——当前的着色器只检查了顶点法线与光源方向的点积,完全没判断这个顶点是否真的处于光源的直接照射范围内。哪怕顶点被模型自身的其他部分挡住了,只要法线朝向光源,着色器就会判定它被照亮,这就导致了背光面出现错误发光的区域。

1. 先搞清楚问题根源

你当前的漫反射计算逻辑大概是这样的(伪代码):

vec3 normal = normalize(inNormal);
vec3 lightDir = normalize(lightPos - inWorldPos);
float diffuse = max(dot(normal, lightDir), 0.0);

这里只关注了法线和光源方向的夹角,完全忽略了顶点到光源的路径是否被遮挡。比如模型的某个凹面,法线刚好朝向光源,但这个顶点其实被前方的面挡住了,处于模型的背光侧,这时候点积依然为正,但实际应该处于阴影中。

2. 核心解决方案:引入阴影映射(Shadow Mapping)

这是实时渲染中处理自阴影的标准方案,针对Vulkan的基础实现分为两步:

步骤1:生成阴影贴图

首先需要从光源的视角渲染整个场景,把深度信息输出到一张纹理(也就是阴影贴图)。在Vulkan里,你需要创建一套单独的渲染管线和帧缓冲:

  • 管线使用光源的投影矩阵(平行光用正交矩阵,点光源用透视矩阵)来变换顶点;
  • 片元着色器只需要输出深度值,不需要计算颜色:
// 阴影贴图专用片元着色器
void main() {
    // 直接让管线输出深度,无需额外操作
}

步骤2:在主着色器中采样阴影贴图判断遮挡

在主渲染的顶点着色器中,把顶点位置变换到光源的裁剪空间,传递给片元着色器:

// 顶点着色器输出
layout(location = 1) out vec4 fragLightSpacePos;

void main() {
    // 常规的世界空间、裁剪空间变换
    vec3 worldPos = (modelMatrix * vec4(inPos, 1.0)).xyz;
    gl_Position = viewProjMatrix * vec4(worldPos, 1.0);
    
    // 将顶点变换到光源视角的裁剪空间
    fragLightSpacePos = lightProjMatrix * lightViewMatrix * vec4(worldPos, 1.0);
}

然后在片元着色器中,将光源空间坐标转换为采样范围,对比阴影贴图的深度值判断是否处于阴影:

layout(location = 1) in vec4 fragLightSpacePos;
layout(binding = 1) uniform sampler2D shadowMap;

float calculateShadow(vec3 normal, vec3 lightDir) {
    // 将齐次坐标转换为NDC,再映射到[0,1]的纹理采样范围
    vec3 projCoords = fragLightSpacePos.xyz / fragLightSpacePos.w;
    projCoords = projCoords * 0.5 + 0.5;
    
    // 采样阴影贴图中该位置的最近深度(即光源视角下的遮挡物深度)
    float closestDepth = texture(shadowMap, projCoords.xy).r;
    // 当前片元在光源视角下的深度
    float currentDepth = projCoords.z;
    
    // 加入偏移量解决阴影 acne(阴影失真)问题
    float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
    // 如果当前深度大于最近深度,说明被遮挡,处于阴影中
    float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;
    
    // 超出阴影贴图范围的区域(比如场景外)不计算阴影
    if(projCoords.z > 1.0) shadow = 0.0;
    
    return shadow;
}

void main() {
    vec3 normal = normalize(inNormal);
    vec3 lightDir = normalize(lightPos - inWorldPos);
    float diffuseFactor = max(dot(normal, lightDir), 0.0);
    
    // 计算阴影,用(1 - shadow)来削弱漫反射亮度
    float shadow = calculateShadow(normal, lightDir);
    float diffuse = diffuseFactor * (1.0 - shadow);
    
    // 最终颜色计算
    vec3 finalColor = diffuse * albedo;
    outColor = vec4(finalColor, 1.0);
}

3. 临时简化方案(快速验证)

如果暂时不想折腾完整的阴影映射,你可以先检查模型的背面剔除是否开启——虽然这没法解决凹面的自阴影问题,但能快速排除那些法线完全背离相机的背光面错误发光的情况。在Vulkan中,你可以在管线创建时设置cullMode为VK_CULL_MODE_BACK_BIT来开启背面剔除。

不过要注意,背面剔除只能处理简单的凸模型,对于有凹面的模型,还是得用阴影映射才能彻底解决问题。

内容的提问来源于stack exchange,提问作者Warpspeed SCP

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 07:13:13