如何在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
相关产品推荐
相关产品推荐

