#version 450 // IBL ④:BRDF 积分 LUT(2D,纯数学,与环境内容无关)。 // (NdotV, roughness) → (F 缩放 A, F 偏置 B):specular = prefiltered·(F·A + B)。 // 与环境切换无关,但随生成链一起跑(GPU 亚毫秒级),省去跨环境缓存。 #include "ibl_common.glsl" layout(local_size_x = 8, local_size_y = 8) in; layout(set = 0, binding = 0, rg16f) uniform restrict writeonly image2D outLut; const uint SAMPLES = 128; void main() { ivec2 gid = ivec2(gl_GlobalInvocationID); if (gid.x >= imageSize(outLut).x || gid.y >= imageSize(outLut).y) return; vec2 uv = (vec2(gid) + 0.5) / vec2(imageSize(outLut)); float nDotV = uv.x; float roughness = uv.y; // V 在以 N 为 +Z 的切线空间里(积分只依赖这两角度) vec3 V = vec3(sqrt(1.0 - nDotV * nDotV), 0.0, nDotV); vec3 Nz = vec3(0.0, 0.0, 1.0); float a = 0.0, b = 0.0; float k = roughness * roughness * 0.5; // IBL 版 Smith k for (uint i = 0u; i < SAMPLES; ++i) { vec3 H = iblImportanceSampleGGX(iblHammersley(i, SAMPLES), Nz, roughness); vec3 L = normalize(2.0 * dot(V, H) * H - V); float lDotN = L.z; float nDotL = max(lDotN, 0.0); float nDotH = max(H.z, 0.0); float vDotH = max(dot(V, H), 0.0); if (nDotL <= 0.0) continue; float g = (nDotV / (nDotV * (1.0 - k) + k)) * (nDotL / (nDotL * (1.0 - k) + k)); // 高 roughness 时部分样本 H.z 恰为 0(g 也为 0),0/0 = NaN // 会累进 LUT——分母钳 epsilon(与 CPU 版一致) float gVis = g * vDotH / max(nDotH * nDotV, 1e-6); float fc = 1.0 - vDotH; float f = fc * fc * fc * fc * fc; // (1-vDotH)⁵ a += (1.0 - f) * gVis; b += f * gVis; } imageStore(outLut, gid, vec4(a / float(SAMPLES), b / float(SAMPLES), 0.0, 1.0)); }