Files
HelloVulkan/shaders/tonemap.frag
T
atdunbg 1d690067bd 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 轮播自动化验证)。
2026-09-24 13:23:59 +08:00

124 lines
5.5 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
// Tonemap pass:HDR(+bloom)→ 显示空间,兼调试视图路由上屏。
// 这是显示变换的显式建模(对齐 UE/Unity:HDR SceneColor → tonemapper → UI)。
// ACES = Narkowicz 拟合(线性域映射到 [0,1]),可经 push constant 关闭
// 退回纯 gamma(旧观感)。
//
// 调试视图:pc.debugView 整型路由到已绑定的中间目标(绑定超集静态成立,
// 运行时零重绑)。HDR 域资源过同一套 exposure/tonemap 保证观感可比;
// LDR 类(velocity/深度)直接可视化。
layout(location = 0) in vec2 fragUV;
layout(location = 0) out vec4 outColor;
layout(set = 0, binding = 0) uniform sampler2D taaImage; // TAA 后 HDR(主输入)
layout(set = 0, binding = 1) uniform sampler2D bloomImage;
layout(set = 0, binding = 2) uniform sampler2D temporalDiffImage;
layout(set = 0, binding = 3) uniform sampler2D rawHdrImage; // Forward 直出(TAA 前)
layout(set = 0, binding = 4) uniform sampler2D velocityImage;
layout(set = 0, binding = 5) uniform sampler2D shadowMap0Image; // D32 深度(r 分量)
layout(set = 0, binding = 6) uniform sampler2D shadowMap1Image;
layout(set = 0, binding = 7) uniform sampler2D shadowMap2Image;
// 环境调试视图源(随环境切换重绑,静态绑定超集)
layout(set = 0, binding = 8) uniform samplerCube envCubeImage;
layout(set = 0, binding = 9) uniform samplerCube irradianceImage;
layout(set = 0, binding = 10) uniform samplerCube prefilterImage;
layout(set = 0, binding = 11) uniform sampler2D brdfLutImage;
// 与 Passes::DebugView 枚举同步(FullscreenGpu.h)
const int VIEW_FINAL = 0;
const int VIEW_TEMPORALDIFF = 1;
const int VIEW_RAW_HDR = 2;
const int VIEW_TAA = 3;
const int VIEW_BLOOM = 4;
const int VIEW_VELOCITY = 5;
const int VIEW_SHADOW0 = 6;
const int VIEW_SHADOW1 = 7;
const int VIEW_SHADOW2 = 8;
const int VIEW_ENV_CUBE = 9;
const int VIEW_IRRADIANCE = 10;
const int VIEW_PREFILTER = 11;
const int VIEW_BRDF_LUT = 12;
// 全屏 pass 通用 push(16B,程序级统一声明,VUID-08878)
layout(push_constant) uniform PushConstants {
float exposure; // 曝光系数
float aces; // >0.5 启用 ACES 曲线,否则纯 gamma
float bloomStrength; // bloom 叠加强度(0 = 关闭)
float debugView; // 调试视图枚举(整型经 float 传)
} pc;
vec3 acesFilm(vec3 x) {
return clamp((x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0);
}
vec3 tonemap(vec3 hdr) {
hdr *= pc.exposure;
vec3 mapped = (pc.aces > 0.5) ? acesFilm(hdr) : clamp(hdr, 0.0, 1.0);
return pow(mapped, vec3(1.0 / 2.2));
}
// 深度可视化:反相(近=白,与调试截图风格一致)
vec3 depthView(sampler2D depthImage) {
float d = texture(depthImage, fragUV).r;
return vec3(pow(1.0 - d, 2.2));
}
// 全屏 uv → 球面方向(cube 调试视图的经纬展开:横向经度、纵向极角)
vec3 latLongDir(vec2 uv) {
float phi = (uv.x * 2.0 - 1.0) * 3.14159265358979; // -π..π 经度
float theta = (1.0 - uv.y) * 3.14159265358979; // 0..π 极角(顶=天顶)
return vec3(sin(theta) * cos(phi), cos(theta), sin(theta) * sin(phi));
}
// cube 视图统一走 tonemap(HDR 域,观感与场景一致);
// prefilter 纵向映射 mip 链(上锐下糙,直观验证 roughness 分级)
vec3 cubeView(samplerCube cube, float lod) {
return tonemap(textureLod(cube, latLongDir(fragUV), lod).rgb);
}
void main() {
int view = int(pc.debugView + 0.5);
// ---- 调试视图路由 ----
if (view != VIEW_FINAL) {
switch (view) {
case VIEW_TEMPORALDIFF: {
// 帧历史差异:静止=黑,运动边缘发光
vec3 d = clamp(texture(temporalDiffImage, fragUV).rgb, 0.0, 1.0);
outColor = vec4(pow(d, vec3(1.0 / 2.2)), 1.0);
return;
}
case VIEW_RAW_HDR: outColor = vec4(tonemap(texture(rawHdrImage, fragUV).rgb), 1.0); return;
case VIEW_TAA: outColor = vec4(tonemap(texture(taaImage, fragUV).rgb), 1.0); return;
case VIEW_BLOOM: outColor = vec4(tonemap(texture(bloomImage, fragUV).rgb * 4.0), 1.0); return;
case VIEW_VELOCITY: {
// |velocity| × 增益:静止=黑,运动越快越亮
vec2 vel = texture(velocityImage, fragUV).xy;
float mag = clamp(length(vel) * 8.0, 0.0, 1.0);
outColor = vec4(vec3(mag), 1.0);
return;
}
case VIEW_SHADOW0: outColor = vec4(depthView(shadowMap0Image), 1.0); return;
case VIEW_SHADOW1: outColor = vec4(depthView(shadowMap1Image), 1.0); return;
case VIEW_SHADOW2: outColor = vec4(depthView(shadowMap2Image), 1.0); return;
case VIEW_ENV_CUBE: outColor = vec4(cubeView(envCubeImage, 0.0), 1.0); return;
case VIEW_IRRADIANCE: outColor = vec4(cubeView(irradianceImage, 0.0), 1.0); return;
case VIEW_PREFILTER: outColor = vec4(cubeView(prefilterImage, fragUV.y * 4.0), 1.0); return;
case VIEW_BRDF_LUT: {
// LUT 是 (F 缩放, F 偏置) 双通道,R/G 直读可视化
vec2 ab = texture(brdfLutImage, fragUV).rg;
outColor = vec4(ab, 0.0, 1.0);
return;
}
}
}
// ---- 正常路径:TAA 后 HDR + bloom 叠加 → tonemap ----
vec4 hdrTex = texture(taaImage, fragUV);
vec3 hdr = hdrTex.rgb + texture(bloomImage, fragUV).rgb * pc.bloomStrength;
outColor = vec4(tonemap(hdr) * hdrTex.a, hdrTex.a);
}