#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 sampler2D roughnessMap; // 独立粗糙度图(R 通道,specular 工作流) layout(set = 1, binding = 6) uniform MaterialParams { vec4 factors; // x = metallic 因子,y = roughness 因子,z = 独立粗糙度图开关,w = MASK 镂空开关 vec4 baseColor; // glTF baseColorFactor:rgb 调色 albedo,a = BLEND 透明度 } 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) { // 阴影总开关(ubo.cascadeSplits.w 由 CPU 每帧写入):关闭即全亮, // 配合 ShadowPass 空转(深度图无内容也读不到这里) if (ubo.cascadeSplits.w < 0.5) return 1.0; 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() { vec4 albedoTex = texture(albedoMap, fragUV); vec3 albedo = albedoTex.rgb * matParams.baseColor.rgb; vec4 mr = texture(metallicRoughnessMap, fragUV); // 粗糙度源按 factors.z 切换:glTF 合图工作流读 MR.g; // FBX/OBJ specular 工作流(z=1)换用独立粗糙度图 R 通道 float roughness = clamp(mix(mr.g, texture(roughnessMap, fragUV).r, matParams.factors.z) * 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; // 第 0 盏光的阴影因子(环境光同步衰减用) 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); // 阴影区环境同步衰减(简化遮蔽) // alpha = albedo 贴图覆盖率 × baseColorFactor.a(glTF alphaMode=BLEND // 混合源;玻璃类无 baseColor 贴图材质的透明度全靠 factor.a)。 // 预乘在 tonemap 段完成。 outColor = vec4(ambient + Lo + emissive, albedoTex.a * matParams.baseColor.a); outVelocity = fragVelocity; // 深度语义:真实深度测试 + 真实写入(depthWrite 状态保持 TRUE—— // 绕开 NVIDIA shader-object "depthWrite=FALSE 下绘制" 的灾难病态)。 // 早期版本在此写 gl_FragDepth=1.0 模拟"只测不写",但深度比较符是 // eLess:片元自身的 1.0 对清屏值 1.0 与不透明深度(更小)永远测试 // 失败,透明队列一个像素都出不来(Transparent 调试视图实锤)。 // 透明段是帧内最后一个 pass 且深度每帧清空,真实写入无副作用; // back-to-front 排序下近面后画恒通过,混合顺序仍由排序保证。 }