Files
HelloVulkan/shaders/simple_shader.frag
T
atdunbg d3c3d0cdd9 fix: 影子中镜面高光残留导致"影子比天空亮"
掠射角下 GGX spec 分母 (4·NdotV·NdotL) 变小,HDR 值可达 8+;
阴影只把它乘 0.3,30% 的刺眼白仍比天空亮——观感即"影子发白"。

- spec 项按 shade 重映射(0.3→0,1.0→1):光源被遮挡,其镜面像
  应完全消失;diffuse 保留 0.3 作间接近似
- ShadowPass 改正面剔除渲阴影图(标准 acne 治法):封闭网格只写
  背面深度,正面深度天然比 ref 大一个表面厚度,从根上消自阴影
  acne;代价是单面薄片在光背面时漏写(plane 单面朝上不受影响)
2026-09-24 12:10:15 +08:00

221 lines
9.0 KiB
GLSL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#version 450
const int MAX_LIGHTS = 4;
const float PI = 3.14159265359;
const int kCascadeRes = 1024; // CSM 级联图边长(与引擎 SceneTypes.h 同步)
struct Light {
vec4 color; // rgb = 颜色,a = 强度
vec4 direction; // 方向光:xyz = 方向(已归一)
vec4 position; // 点光:xyz = 世界位置
vec4 params; // x = 类型(0=方向,1=点),y = 衰减半径
};
layout(location = 0) in vec2 fragUV;
layout(location = 1) in vec3 fragNormal;
layout(location = 2) in vec3 fragWorldPos;
layout(location = 3) in float fragViewDepth;
layout(location = 4) in vec2 fragVelocity;
layout(location = 5) in vec3 fragTangent;
layout(location = 0) out vec4 outColor;
layout(location = 1) out vec2 outVelocity; // TAA velocity 附件
layout(set = 0, binding = 0) uniform UniformBufferObject {
mat4 view;
mat4 proj;
mat4 lightSpace[3]; // CSM 各级联光空间 VP
vec4 cascadeSplits; // xyz = 级联 0..2 远边界(视图深度)
vec4 ambientColor;
Light lights[MAX_LIGHTS];
mat4 projNoJitter;
mat4 prevView;
mat4 prevProj;
vec4 cameraPos;
} ubo;
// 材质纹理槽(binding = Material::Slot)
layout(set = 1, binding = 0) uniform sampler2D albedoMap;
layout(set = 1, binding = 1) uniform sampler2D normalMap;
layout(set = 1, binding = 2) uniform sampler2D metallicRoughnessMap; // G=粗糙度 B=金属度
layout(set = 1, binding = 3) uniform sampler2D emissiveMap;
layout(set = 1, binding = 4) uniform sampler2D aoMap; // 环境光遮蔽(R 通道)
layout(set = 1, binding = 5) uniform MaterialParams {
vec4 factors; // x = metallic 因子,y = roughness 因子
} matParams;
// CSM 阴影(sampler2DShadow:单次采样 = 硬件 2×2 深度比较 PCF)
layout(set = 2, binding = 0) uniform sampler2DShadow shadowMap0; // CSM 级联 0(最近)
layout(set = 2, binding = 1) uniform sampler2DShadow shadowMap1; // 级联 1
layout(set = 2, binding = 2) uniform sampler2DShadow shadowMap2; // 级联 2(最远)
layout(set = 2, binding = 3) uniform samplerCube irradianceMap; // 漫反射环境光(IBL 阶段②)
layout(set = 2, binding = 4) uniform samplerCube prefilteredMap; // 镜面环境(mip 分 roughness,阶段③)
layout(set = 2, binding = 5) uniform sampler2D brdfLut; // BRDF 积分表 (NdotV, roughness) → (scale, bias)
// 第 0 盏方向光(KeyLight)的 CSM 阴影因子。
// 级联按视图深度选择;3×3 纹素偏移采样(每 tap 硬件 2×2 PCF,
// 等效 36 次比较)→ 软阴影边缘;采样器 clamp-to-border 白色:
// 光视锥外自动 1.0(无阴影),省出界分支
float ShadowFactor(vec3 N, float viewDepth) {
int cascade = 2;
for (int i = 0; i < 3; ++i) {
if (viewDepth < ubo.cascadeSplits[i]) { cascade = i; break; }
}
vec4 ls = ubo.lightSpace[cascade] * vec4(fragWorldPos, 1.0);
vec3 proj = ls.xyz / ls.w;
vec2 uv = proj.xy * 0.5 + 0.5;
vec3 L = normalize(-ubo.lights[0].direction.xyz);
float bias = max(0.004 * (1.0 - dot(N, L)), 0.0012);
float texel = 1.0 / float(kCascadeRes); // 与引擎 kCascadeRes 同步
float shadow = 0.0;
for (int y = -1; y <= 1; ++y) {
for (int x = -1; x <= 1; ++x) {
float lit;
if (cascade == 0)
lit = texture(shadowMap0, vec3(uv + vec2(x, y) * texel,
proj.z - bias));
else if (cascade == 1)
lit = texture(shadowMap1, vec3(uv + vec2(x, y) * texel,
proj.z - bias));
else
lit = texture(shadowMap2, vec3(uv + vec2(x, y) * texel,
proj.z - bias));
shadow += mix(0.3, 1.0, lit); // 影中保留 0.3 环境感
}
}
return shadow / 9.0;
}
// 法线贴图 → 世界空间法线(TBN 构建)。
// 无切线数据(零向量)或平法线贴图时退回几何法线。
vec3 MappedNormal(vec3 geomN, vec3 T, vec2 uv) {
if (dot(T, T) < 1e-4) return geomN;
vec3 N = geomN;
vec3 nT = normalize(T - N * dot(N, T)); // 正交化
vec3 B = cross(N, nT);
vec3 sampled = texture(normalMap, uv).xyz * 2.0 - 1.0;
if (dot(sampled, sampled) < 1e-4) return geomN;
return normalize(nT * sampled.x + B * sampled.y + N * sampled.z);
}
// ---- Cook-Torrance BRDF 各项 ----
float DistributionGGX(float NdotH, float roughness) {
float a = roughness * roughness;
float a2 = a * a;
float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
return a2 / (PI * d * d);
}
float GeometrySmith(float NdotV, float NdotL, float roughness) {
float r = roughness + 1.0;
float k = (r * r) / 8.0; // 解析光源用此 k(IBL 才用 IBL 版本)
float gv = NdotV / (NdotV * (1.0 - k) + k);
float gl = NdotL / (NdotL * (1.0 - k) + k);
return gv * gl;
}
vec3 FresnelSchlick(float cosTheta, vec3 F0) {
return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}
// 粗糙度感知的 Fresnel(IBL 用):粗糙表面掠射边缘的 F 上升更平缓
vec3 FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) {
vec3 Fmax = max(vec3(1.0 - roughness), F0);
return F0 + (Fmax - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}
void main() {
vec3 albedo = texture(albedoMap, fragUV).rgb;
vec4 mr = texture(metallicRoughnessMap, fragUV);
float roughness = clamp(mr.g * matParams.factors.y, 0.045, 1.0);
float metallic = clamp(mr.b * matParams.factors.x, 0.0, 1.0);
vec3 emissive = texture(emissiveMap, fragUV).rgb;
vec3 N = MappedNormal(normalize(fragNormal), fragTangent, fragUV);
vec3 V = normalize(ubo.cameraPos.xyz - fragWorldPos);
float NdotV = max(dot(N, V), 1e-4);
vec3 F0 = mix(vec3(0.04), albedo, metallic); // 电介质基准反射率 4%
vec3 Lo = vec3(0.0);
float shade0 = 1.0f; // 【诊断】阴影系数可视化用
for (int i = 0; i < MAX_LIGHTS; ++i) {
// 按类型分支:方向光无衰减;点光方向指向光源 + 平方距离衰减
vec3 L;
float atten = 1.0;
if (ubo.lights[i].params.x > 0.5) {
vec3 toLight = ubo.lights[i].position.xyz - fragWorldPos;
float d = length(toLight);
if (d >= ubo.lights[i].params.y) continue;
L = toLight / d;
float a = clamp(1.0 - (d * d) /
(ubo.lights[i].params.y * ubo.lights[i].params.y),
0.0, 1.0);
atten = a * a;
} else {
L = normalize(-ubo.lights[i].direction.xyz);
}
vec3 radiance = ubo.lights[i].color.rgb * ubo.lights[i].color.a * atten;
float NdotL = max(dot(N, L), 0.0);
if (NdotL <= 0.0) continue;
// Cook-Torrance 镜面项
vec3 H = normalize(V + L);
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = max(dot(H, V), 0.0);
float D = DistributionGGX(NdotH, roughness);
float G = GeometrySmith(NdotV, NdotL, roughness);
vec3 F = FresnelSchlick(NdotH2, F0);
vec3 kS = F;
vec3 kD = (1.0 - kS) * (1.0 - metallic); // 金属不参与漫反射
vec3 specular = (D * G * F) /
max(4.0 * NdotV * NdotL, 1e-4);
// 阴影只来自第 0 盏方向光(CSM)
float shade = (i == 0 && ubo.lights[0].params.x < 0.5)
? ShadowFactor(N, fragViewDepth) : 1.0;
if (i == 0) shade0 = shade;
// 漫反射影中保留 0.3(间接光近似);镜面项是"光源的镜面像",
// 光源被遮挡即完全消失——按 shade 重映射(0.3→0,1.0→1)。
// 否则掠射角下 spec(HDR 8)×0.3 仍是白块,影子比天空还亮
float shadeSpec = max((shade - 0.3f) / 0.7f, 0.0f);
Lo += (kD * albedo / PI) * radiance * NdotL * shade
+ specular * radiance * NdotL * shadeSpec;
}
// 环境光:IBL split-sum(阶段②③)。
// 漫反射:N 采样 irradiance cube(半球 Lambert 预卷积)
// 镜面:R 采样 pre-filtered cube(mip = roughness 分级)
// × BRDF LUT 查表得 F 缩放/偏置
// ambientColor = 用户可调强度/色调乘子
vec3 F_amb = FresnelSchlickRoughness(NdotV, F0, roughness);
vec3 kD_amb = (1.0 - F_amb) * (1.0 - metallic);
vec3 irradiance = texture(irradianceMap, N).rgb;
float ao = texture(aoMap, fragUV).r;
vec3 diffuse = kD_amb * irradiance * albedo * ao;
vec3 R = reflect(-V, N);
const float kMaxPrefilterMip = 4.0; // 与 Cubemap::kPrefilterMips - 1 同步
vec3 prefilteredColor = textureLod(prefilteredMap, R,
roughness * kMaxPrefilterMip).rgb;
vec2 envBRDF = texture(brdfLut, vec2(NdotV, roughness)).rg;
vec3 specular = prefilteredColor * (F_amb * envBRDF.x + envBRDF.y);
vec3 ambient = (diffuse + specular) * ubo.ambientColor.rgb
* mix(0.4, 1.0, shade0); // 阴影区环境同步衰减(简化遮蔽)
vec3 color = ambient + Lo + emissive;
outColor = vec4(color, 1.0);
outVelocity = fragVelocity;
}