Files
HelloVulkan/shaders/pbr/simple_shader.frag
T

269 lines
12 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(F0 换算),y = transmission(不透明段恒 0 语义),z = 自发光强度,w = 体积厚度
vec4 misc; // x = unlit(KHR_materials_unlit,1 = 早退直出 albedo)
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(identity 时退化为直通)。slot 必须
// 字面量调用点(编译期展开索引);6 = Material::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)
// 第 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));
// alphaMode=MASK 镂空(表针/栅栏等硬边贴图):低于阈值的纹素直接丢弃
if (matParams.factors.w > 0.5 && albedoTex.a < 0.5) discard;
vec3 albedo = albedoTex.rgb * matParams.baseColor.rgb;
// KHR_materials_unlit:无光照语义(文字标签/示意图),albedo 直出
if (matParams.misc.x > 0.5) {
outColor = vec4(albedo, albedoTex.a * matParams.baseColor.a);
outVelocity = fragVelocity;
return;
}
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);
// emissiveFactor = 颜色因子(无贴图时发光的入口——兜底贴图恒黑)
vec3 emissive = texture(emissiveMap, ApplyUv(3, fragUV)).rgb
* matParams.emissiveFactor.rgb * matParams.surface.z;
vec3 N = MappedNormal(normalize(fragNormal), fragTangent, ApplyUv(1, fragUV));
vec3 V = normalize(ubo.cameraPos.xyz - fragWorldPos);
// 双面法线:cull 关闭 + glTF doubleSided=true,背面片元(表壳后盖/
// 内壁等从背面看到的表面)几何法线背向相机,NdotL≤0 → 直接光全灭、
// IBL 采错半球 = 纯黑壳(three.js DoubleSide 对背面自动翻法线,
// 这里对齐该语义;与 translucent shader 的翻转同款)
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,与旧固定值一致
float ior = max(matParams.surface.x, 1.0);
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;
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, ApplyUv(4, 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
// 混合源);不透明资产两者恒 1 行为不变。预乘在 tonemap 段完成。
outColor = vec4(ambient + Lo + emissive, albedoTex.a * matParams.baseColor.a);
// ==== DEBUG B1: 纯 albedo 直出(验证贴图绑定/UV 采样)====
// outColor = vec4(albedo, 1.0);
outVelocity = fragVelocity;
}