feat: IBL 预积分 GPU compute 化 + 环境资产入 Assets 层(动态切换)

CPU 逐像素卷积(venice 4k 阻塞 7.36s)→ 4 个 compute shader 一次性
提交:equirect→cube / irradiance / prefilter×5mip / BRDF LUT。
Cubemap 瘦身为纯资源持有类,生成职责移交 IblGenerator;
环境经 AssetManager 缓存(EnvRef),Renderer::SetEnvironment
动态换绑(RenderPass::OnEnvironmentChanged 广播)。
调试视图 +4:EnvCube / Irradiance / Prefilter / BRDF LUT。

性能前后对比(RTX 5060 Laptop,venice_dawn_1_4k.hdr):
- 改造前 CPU:7.36 s(主线程阻塞)
- 改造后 GPU:HDR decode 127-134ms + 资源 22-35ms + compute 17-19ms
  ≈ 165-190ms(约 40×);Neutral 兜底 ~6ms
- 运行中动态切换环境同量级(ms 级 GPU,decode 占大头)

冒烟:两构建目录 0 警告;运行期 0 validation(含两次环境切换 +
调试视图 9-12 轮播自动化验证)。
This commit is contained in:
2026-09-24 13:23:59 +08:00
parent 705250df76
commit 1d690067bd
30 changed files with 1162 additions and 824 deletions
+54
View File
@@ -0,0 +1,54 @@
#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));
}