Files
HelloVulkan/shaders/pbr/simple_shader_translucent.frag
T

298 lines
14 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 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 透明度
vec4 surface; // x = ior,y = transmission(KHR_materials_transmission),z = 自发光强度,w = 体积厚度
vec4 misc; // x = unlit(透射材质不会是 unlit,仅为与 simple_shader 布局一致)
vec4 emissiveFactor; // 自发光颜色(rgb × emissiveMap × surface.z;黑 = 不发光)
vec4 uvA[6]; // 每槽 UV 线性变换(KHR_texture_transform,v-up 空间)(a11,a12,a21,a22),槽序 = binding
vec4 uvT[6]; // 每槽 UV 平移 (tx, ty, 0, 0)
} matParams;
// 每槽 UV 变换:uv' = A·uv + t(与 simple_shader.frag 同款,6 = kSlotCount)
vec2 ApplyUv(int slot, vec2 uv) {
vec4 a = matParams.uvA[slot];
vec4 t = matParams.uvT[slot];
return vec2(a.x * uv.x + a.y * uv.y + t.x,
a.z * uv.x + a.w * uv.y + t.y);
}
// 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)
layout(set = 2, binding = 6) uniform sampler2D sceneColor; // 不透明段快照(透射折射采样源)
// 第 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, ApplyUv(0, fragUV));
vec3 albedo = albedoTex.rgb * matParams.baseColor.rgb;
vec4 mr = texture(metallicRoughnessMap, ApplyUv(2, fragUV));
// 粗糙度源按 factors.z 切换:glTF 合图工作流读 MR.g;
// FBX/OBJ specular 工作流(z=1)换用独立粗糙度图 R 通道
float roughness = clamp(mix(mr.g, texture(roughnessMap, ApplyUv(5, fragUV)).r,
matParams.factors.z) * matParams.factors.y,
0.045, 1.0);
float metallic = clamp(mr.b * matParams.factors.x, 0.0, 1.0);
// 自发光强度(KHR_materials_emissive_strength,缺省 1)
vec3 emissive = texture(emissiveMap, ApplyUv(3, fragUV)).rgb
* matParams.emissiveFactor.rgb * matParams.surface.z;
// glTF 表面扩展:透射率(KHR_materials_transmission)/
// 折射率(ior,缺省 1.5)/ 体积厚度(KHR_materials_volume)
const float transmission = clamp(matParams.surface.y, 0.0, 1.0);
const float ior = max(matParams.surface.x, 1.0);
const float thickness = max(matParams.surface.w, 0.0);
vec3 N = MappedNormal(normalize(fragNormal), fragTangent, ApplyUv(1, fragUV));
vec3 V = normalize(ubo.cameraPos.xyz - fragWorldPos);
// 双面法线:cull 关闭,背面片元(玻璃壳内壁)几何法线背向相机,
// 光照/折射方向全反——按外表面等价翻转到视侧
if (dot(N, V) < 0.0) N = -N;
float NdotV = max(dot(N, V), 1e-4);
// F0 按折射率换算(KHR_materials_ior):((ior-1)/(ior+1))²;
// glTF 缺省 ior=1.5 → 0.04,与旧固定值一致
vec3 F0 = mix(vec3((ior - 1.0) / (ior + 1.0)
* (ior - 1.0) / (ior + 1.0)),
albedo, metallic); // 电介质 F0 / 金属 albedo
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;
// 漫反射能量让位给透射:真玻璃无漫反射(入射光 = 反射 F +
// 透射 1-F),transmission=1 时 kD→0 = 纯镜面电介质;透射光
// 由场景色副本折射采样补回(见下方 transmitted)
vec3 kD = (1.0 - kS) * (1.0 - metallic) * (1.0 - transmission);
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 = 用户可调强度/色调乘子
// 漫反射侧同步乘 (1-transmission)(同直接光能量重分配)
vec3 F_amb = FresnelSchlickRoughness(NdotV, F0, roughness);
vec3 kD_amb = (1.0 - F_amb) * (1.0 - metallic) * (1.0 - transmission);
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); // 阴影区环境同步衰减(简化遮蔽)
// ---- 透射(KHR_materials_transmission):屏幕空间折射 ----
// 采样源 = 不透明段快照(天空盒 + 不透明几何,Forward 后
// ForwardTrans 前拷贝)。玻璃互相不可见(副本不含透明队列),
// 与 UE 屏幕空间透射同一限制。
vec3 transmitted = vec3(0.0);
if (transmission > 0.0) {
// 进入介质 eta = 1/ior;全内反射(TIR)退化为反射方向
vec3 refrDir = refract(-V, N, 1.0 / ior);
if (dot(refrDir, refrDir) < 1e-6) refrDir = reflect(-V, N);
// 世界空间步进 thickness 投回屏幕取折射像素(thickness=0 =
// 直透同位采样,无弯曲);projNoJitter 保证折射样本位置
// 无 TAA 抖动(时域稳定收敛)
vec4 clip = ubo.projNoJitter * ubo.view *
vec4(fragWorldPos + refrDir * thickness, 1.0);
vec2 refrUV = (clip.xy / clip.w) * 0.5 + 0.5;
refrUV = clamp(refrUV, vec2(0.0), vec2(1.0));
vec3 scene = texture(sceneColor, refrUV).rgb;
// albedo 染色 = 有色玻璃近似(Beer-Lambert 体积吸收后续演进)
transmitted = scene * albedo * transmission;
}
// alpha = max(覆盖率, transmission):transmission 已烘进 rgb(透射
// 背景是颜色的一部分),t≈1 的玻璃对混合方程是完全替换像素
//(src*1 + dst*0)——不再有"白 albedo×a 混合"的奶白罩。
// 非 transmission 的 BLEND 材质(软粒子/光效)行为不变。
// 预乘在 tonemap 段完成。
outColor = vec4(ambient + Lo + transmitted + emissive,
max(albedoTex.a * matParams.baseColor.a, transmission));
outVelocity = fragVelocity;
// 深度语义:真实深度测试 + 真实写入(depthWrite 状态保持 TRUE——
// 绕开 NVIDIA shader-object "depthWrite=FALSE 下绘制" 的灾难病态)。
// 早期版本在此写 gl_FragDepth=1.0 模拟"只测不写",但深度比较符是
// eLess:片元自身的 1.0 对清屏值 1.0 与不透明深度(更小)永远测试
// 失败,透明队列一个像素都出不来(Transparent 调试视图实锤)。
// 透明段是帧内最后一个 pass 且深度每帧清空,真实写入无副作用;
// back-to-front 排序下近面后画恒通过,混合顺序仍由排序保证。
}