「Custom SRP」:后处理栈
系列第 7 篇。前六篇笔记把场景从几何提交一路走到 GI 接入——到这一步,相机的 color attachment 中存储的是一张线性空间的 HDR 图像:物理光度学单位下的真实辐射度,可能远超 [0, 1],包含了所有直接光、间接光、阴影信息。本篇关注的是从这张 HDR 图像到最终显示器输出的完整加工链——Bloom 模拟相机镜头光晕、Tone Mapping 把 HDR 映射到 LDR、Color Grading 进行风格化调色、Render Scale 处理动态分辨率、FXAA 收尾抗锯齿。
TL;DR
- 后处理是 Render Graph 中的三个独立 Pass:BloomPass(光晕金字塔)→ ColorLUTPass(生成 3D 调色查找表)→ PostFXPass(最终合成 + FXAA + 输出)。Render Graph 自动复用格式相同、生命周期不重叠的中间 RT,显著减少分配。
- HDR 的核心 RT 格式是 R11G11B10F:32 位/像素、近似 RGBA16F 的视觉质量、显著节省带宽。这是后处理性能与质量的甜区。
- Bloom 用 Dual Filter Pyramid:下采样 + 上采样的双向金字塔,每级用低成本 Box Filter 模拟高斯模糊。Threshold 用 soft knee 曲线避免硬截断造成的 Mach Band。
- Tone Mapping 三模式各有适用场景:Reinhard 简单稳定、Neutral 中性自然、ACES 电影级胶片质感(计算量稍高)。所有模式都在 Log-C 空间外完成。
- Color LUT 是性能优化的灵魂:把所有逐像素调色(白平衡、对比度、饱和度、Channel Mixer、SMH)烘焙到一张 32³ 的 3D 纹理,全屏只需一次三线性查找——把每像素几十次浮点计算压缩为一次纹理采样。
- 6.2.0 起使用真 3D Texture + Compute Shader 生成 LUT:替代旧版 2D 模拟方案,提升采样精度与编程清晰度。
1. 后处理栈架构
1.1 整体数据流
flowchart TD
A[Camera Color Attachment
HDR Linear · R11G11B10F] --> B[BloomPass]
B --> C[Bloom Pyramid
多级 RT]
C --> D[Bloom Result
HDR · 与原图同尺寸]
E[PostFXSettings · 调色参数] --> F[ColorLUTPass · Compute Shader]
F --> G[Color LUT 3D Texture
32³ · Log-C 空间]
A --> H[PostFXPass]
D --> H
G --> H
H --> I[最终输出
LDR sRGB · Camera Target]
style B fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style F fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style H fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
三个 Pass 各司其职:
- BloomPass — 提取 HDR 高亮部分、生成模糊金字塔、合成回原图
- ColorLUTPass — 用 Compute Shader 生成 3D 颜色查找表(不依赖输入图像、只依赖参数)
- PostFXPass — 最终合成阶段:从原图 + Bloom 结果 + LUT 一次性产出 LDR 输出,期间完成 Tone Mapping、Color Grading 查表、FXAA、Render Scale 缩放
1.2 Render Graph 资源复用
后处理的中间 RT 数量很多——Bloom 金字塔可能有 5-7 级、加上 Color Copy、Color Grading Result 等中间副本。如果每个都独立分配,是显著的显存负担。
Render Graph 会自动检测格式相同 + 生命周期不重叠的资源,在底层复用同一张物理 RT。Frame Debugger 中会显示”Color Copy”和”Color Grading Result”使用了同名的物理 RT——这就是复用生效的标志。
1 | |
这是 Render Graph 相对手动 RT 管理的核心收益之一——资源声明的颗粒度可以做得很细,物理分配由引擎统一优化。
1.3 PostFX 启用判断
不是每个相机都需要后处理。Custom SRP 在 CameraRenderer 中根据相机配置与 PostFX 设置决定走完整 PostFXPass 还是直接 FinalPass:
1 | |
camera.cameraType <= CameraType.SceneView 跳过预览相机和反射 Probe 相机——对它们做后处理通常无意义且会拖慢编辑器响应。
2. HDR 与中间 RT 格式
2.1 为什么需要 HDR
直接用 LDR(每通道 8 位 [0, 1])做后处理会撞上两个问题:
- 过曝信息丢失:场景中超过 1.0 的辐射度(直接光、霓虹灯、阳光反射)在 LDR 中被截断为纯白——后续 Bloom 无法区分”白纸”和”灯泡”
- 精度链式损失:每次 Blit 都会产生 8 位量化误差,链式后处理多次累积后出现可见 banding
HDR 用浮点格式存储辐射度,保留 [0, ∞) 全范围。Bloom 提取时能精确区分高光程度,Tone Mapping 阶段才把 HDR 映射到 LDR。
2.2 R11G11B10F:性能甜区
HDR 中间 RT 的格式选择直接影响带宽与质量。三个候选:
| 格式 | 位深/像素 | 范围 | 精度 | 适用 |
|---|---|---|---|---|
| RGBA16F | 64 bit | [-65504, 65504] | 11 位尾数 | 桌面端、对精度极敏感的场景 |
| R11G11B10F | 32 bit | [0, 65000] | 6-7 位尾数 | 后处理标准 |
| RGBA8 (LDR) | 32 bit | [0, 1] | 8 位定点 | 最终输出 |
R11G11B10F 的精度安排:R 与 G 通道 11 位(5 位指数 + 6 位尾数)、B 通道 10 位(5 位指数 + 5 位尾数)——B 通道精度略低是因为人眼对蓝色亮度变化最不敏感。
视觉对比 RGBA16F 时几乎无差,但带宽减半——这在移动端 TBR 架构下意味着片上内存占用减半、Pass 间 store-load 也减半。这是 R11G11B10F 成为 URP / HDRP / Custom SRP 后处理标准格式的根本原因。
2.3 Pre-Exposure:避免精度浪费
R11G11B10F 的 6-7 位尾数虽然够用,但当场景辐射度数值范围跨度大(暗部 0.001、阳光 1000)时仍会出现暗部精度不足。
Pre-Exposure 是 HDR 管线的前置技巧:在 Tone Mapping 之前,把整个 HDR buffer 乘以一个全局曝光值,让”中等亮度”的辐射度落到 1.0 附近——浮点精度在 1.0 附近最高。
1 | |
postExposure 暴露给美术的单位是 EV(Exposure Value,stops)——每 +1 EV 等同曝光时间翻倍或光圈大一档。这与摄影师的工作语言一致,在场景照明调整时直观。
2.4 KeepAlpha:透明度保留判断
PostFX 链路是否需要保留 alpha 通道是个常被忽视的选项。Custom SRP 暴露 keepAlpha 配置:
- keepAlpha = false(默认):Alpha 通道在 PostFX 阶段被覆盖。此时 Bloom 可以借用 Alpha 通道存储中间数据
- keepAlpha = true:Alpha 通道严格保留——用于需要把渲染结果合成到外部图层的情况(比如视频会议背景虚化、AR 应用、UI 层叠加)
这个开关影响 Bloom 实现细节——如果允许使用 Alpha,可以把”Bloom 强度”打包到 alpha 中省一张 RT。
3. Bloom:HDR 高光泛光
3.1 物理原理与设计目标
真实相机镜头不是完美的——亮光源在感光元件上产生散射光晕,强度越大、扩散越广。Bloom 模拟这个效应,让 HDR 高光部分自然”溢出”到周围区域。
Bloom 的关键参数:
- Threshold — 高光提取阈值。低于此值的像素不参与 Bloom
- Knee — Threshold 的软过渡范围。避免硬截断造成的 Mach Band
- Intensity — Bloom 总强度
- Scatter — 散射程度。决定金字塔上采样的混合比例
- Iterations — 金字塔层级数。越多越柔和但也越糊
3.2 Threshold 的 Soft Knee 曲线
朴素 threshold 用 max(color - threshold, 0) 截断——但这会让略高于阈值的像素突然出现,亮度与阈值之差很小时容易出现可见的边界。
Soft Knee 曲线让 threshold 周围有一段平滑过渡:
其中
1 | |
_BloomThreshold.xyzw 在 CPU 端预计算好四个系数,Shader 端只用 4 次乘法 + 几次比较即可完成 soft threshold——比展开公式直接计算快得多。
3.3 Dual Filter Pyramid
Bloom 的核心算法是多级模糊金字塔。直接对全屏做大半径高斯模糊成本极高(半径 R 的高斯需要 R² 次采样),分层金字塔把这个成本压到对数级:
1 | |
下采样阶段每像素只需要 4 次采样(box filter),总成本是 O(N × tap),远低于单级大核高斯。
更精妙的是上采样阶段反向走金字塔,在每级把当前模糊结果与上一级原始结果按 Scatter 权重混合:
1 | |
这种 dual filter(下采样 + 上采样的两阶段对称结构)的视觉效果接近高质量高斯模糊,但成本只有单次大核模糊的几分之一——Kawase 在 CryEngine 时代提出的优化思路,至今仍是现代游戏引擎 Bloom 的事实标准。
3.4 BloomPass 的 Render Graph 资源声明
Bloom 金字塔的每一级都是独立 RT。BloomPass 需要在录制阶段批量声明:
1 | |
每级金字塔都是独立 attachment——Render Graph 会自动判断这些 RT 中能否复用物理资源。注意 Bloom 各级 attachment 的尺寸不同,无法跨级合并为同一原生 render pass——这是 Bloom 必然打断 attachment 合并链的原因,也是为什么 Note 1 §7 强调”PostFX 必然打断合并链”。
4. Tone Mapping
4.1 设计动机
人眼的动态范围(暗适应到亮适应)跨越约 14 个数量级,单次场景中可感知约 3-4 个数量级;显示器只能显示约 2 个数量级(典型 250 nits 标准显示器);HDR 渲染输出可能跨越 6-8 个数量级。
Tone Mapping 的核心任务是把 HDR 辐射度智能地压缩到 LDR 范围,保留视觉重要的细节、舍弃眼睛不敏感的部分。这不是简单的 min(color, 1.0) 截断——而是模拟人眼或胶片对亮度的非线性响应。
4.2 三种 Tone Mapping 模式
Custom SRP 实现三种典型 Tone Mapper:
Reinhard
最简单的 Tone Mapper,公式:
1 | |
特点:对 HDR 任意值都收敛到 [0, 1]、没有截断、计算极廉。但视觉上偏淡——所有高亮区域都被均匀压缩,缺少电影感。
适合:debug 用、性能极致受限的场景、追求中性风格的项目。
Neutral
Unity 内置的 Neutral Tone Mapper 采用 Hable / Lottes 风格的多项式拟合,特点是中等亮度区域几乎线性(保持准确色彩)、高光部分柔和压缩:
1 | |
NeutralTonemap 是 RP Core Library 提供的标准实现,参数已经调好(white point、shoulder、toe 等)。视觉上比 Reinhard 自然许多——高光不那么平淡、暗部也保持细节。
适合:写实游戏、户外场景、对色彩准确性要求高的项目。
ACES
ACES(Academy Color Encoding System)是电影工业的色彩管线标准,包含完整的 Input → Reference Rendering Transform → Output 链路。游戏中常用的是简化版的 ACES Filmic Tone Mapping 曲线:
1 | |
unity_to_ACES 把 Unity 的工作色彩空间(线性 sRGB)转换到 ACES2065-1 / ACEScg;AcesTonemap 应用 RRT + ODT 简化曲线后输出。
ACES 的特点:胶片质感的高光卷曲(roll-off)、暗部的丰富深度、整体色调偏暖。这是当代 3A 游戏的事实标准(《最后生还者》《荒野大镖客 2》《赛博朋克 2077》等都使用 ACES 路径)。
代价:计算量稍高(多几次矩阵乘法),但移动端高端机型完全能承担。
4.3 模式选择决策
flowchart TD
A[项目风格] --> B{追求什么?}
B -->|纯净中性| C[Reinhard / Neutral]
B -->|电影质感| D[ACES]
B -->|风格化| E[Reinhard + Color Grading]
C --> F[移动端低端: Reinhard
桌面端: Neutral]
D --> G[全平台 ACES
性能预算允许]
E --> H[依赖 Color LUT 风格化]
style D fill:#e8f5e9,stroke:#388e3c
style G fill:#e8f5e9,stroke:#388e3c
实践中 ACES 是默认选择——除非性能极度紧张或风格上明确不要电影感。
5. Color Grading
Color Grading 是后处理中最复杂的子系统——涉及多个独立的调色阶段串联,每阶段都有自己的参数。但 Custom SRP 通过 LUT 烘焙巧妙地把整个链路成本固化为”一次 3D 纹理采样”。
5.1 调色管线
调色阶段按顺序串联:
1 | |
每个阶段都有独立的参数集,对应美术界面上的多个分区。完整链路如果逐像素计算,每像素需要执行整套数学操作——典型 50-100 ALU。
5.2 LUT 烘焙的核心思想
观察一下整个调色链路的特性:
- 它是一个纯函数
f(rgb_in) = rgb_out——输入 RGB 决定输出 RGB,不依赖屏幕位置、不依赖时间、不依赖其他像素 - 参数变化频率低——美术调好一组参数后,每一帧都用同一组参数
既然是纯函数且参数稳定,可以预计算所有可能的输入对应的输出,存在 3D 纹理里——这就是 Color LUT(Lookup Table)。
LUT 的尺寸常见 16³ / 32³ / 64³。32³ 是甜区——压缩 4096 像素长的 1D LUT 为可三线性插值的 3D 纹理,覆盖整个 RGB 空间且精度足够。
每像素的成本从”50-100 ALU 整套调色计算”压缩为”一次 3D 纹理三线性查找”——后处理的核心性能优化。
5.3 ColorLUTPass 的 Compute Shader 实现
Custom SRP 6.2.0 起使用真正的 3D 纹理 + Compute Shader 生成 LUT。Compute Shader 比 fragment shader 直接写入更适合 3D 纹理(fragment 无法直接渲染到 3D RT):
1 | |
numthreads(4, 4, 4) 是个看似保守的选择——64 thread/group 在所有支持 Compute Shader 的硬件上都能运行(Mali、Adreno、Apple、Nvidia、AMD)。32³ LUT 用 8×8×8 = 512 个 thread group 完成填充——一次 dispatch 即可。
💡 64 线程的硬件哲学:Wavefront 与 Warp 的对齐:
4 × 4 × 4 = 64不是随意的魔法数字,而是对 GPU 硬件执行单元(SIMD)的精确对齐。64 个线程恰好填满 AMD GPU 的一个完整 Wavefront(Wave64),同时完美等分为 NVIDIA GPU 的两个 Warp(Warp32)——也对得上 Apple GPU 的 32 SIMD-group、Mali 的 16/8 量子。这种”最大公约对齐”保证了:(1) 寄存器分配在硬件层最优化,没有任何线程组内的部分填充浪费;(2) 完全规避 Divergence——同一 dispatch 内所有线程走相同代码路径(都是 LUT 填充计算),没有 if-else 分歧导致的空转;(3) 跨厂商兼容——大于 64 的 thread group 可能在某些移动 GPU 上被强制拆分降低效率,小于 32 的会让 NVIDIA Warp 没填满。64 线程是 Compute Shader 跨平台的兼容性甜区,也是性能调优的基石。当你看到现代引擎的 Compute Shader 反复出现numthreads(8, 8, 1)(64)、numthreads(64, 1, 1)、numthreads(4, 4, 4)(64)时,背后是同一条硬件对齐准则。
1 | |
SetGlobalTexture 让 LUT 对所有后续 PostFXPass 可见——这是 Compute → Raster Pass 之间最常见的资源共享方式。
5.4 Log-C 空间的工作意义
调色操作在不同色彩空间中表现差异巨大。一些操作(contrast、white balance)在 Log-C 空间 中比在线性 RGB 中更接近人眼感知:
- 线性空间下的对比度调整:高光区域被剧烈拉开(数值大、变化大)、阴影区域被压扁(数值小、变化小)。视觉上不均匀
- Log-C 空间下的对比度调整:所有亮度区间均匀缩放——这正是胶片摄影师调对比度时的预期效果
Log-C 是 ARRI Log-C 的简称,类似的还有 Sony S-Log、RED Log3G10 等——所有都是为电影级调色优化的对数曲线。
ColorLUTPass 的工作流程:
- 将 LUT 输入坐标
id视作 LDR RGB([0, 1]) - 如果
inLogC标志启用:用LogCToLinear(rgb)把它解读为 Log-C 空间的坐标,转换为线性 RGB(这样输入范围扩展到 HDR 的等效区域) - 应用所有调色阶段
- 应用 Tone Mapping
- 写入 LUT
PostFXPass 采样时反向使用:把每像素的 HDR 颜色 LinearToLogC() 转换为 Log-C 编码后查找 LUT——这样一张 32³ LUT 能覆盖 HDR 全范围。
5.5 LUT 采样
PostFXPass 中应用 LUT:
1 | |
scale 与 offset 是 32³ LUT 与 [0, 1] 输入坐标空间的转换系数(避免边缘 voxel 的精度损失):scale = (resolution - 1) / resolution、offset = 1 / (2 × resolution)。
整个采样只占 1 次 ALU 运算 + 1 次 3D 纹理三线性插值(GPU 硬件原生支持)。
5.6 LUT 调试
Color LUT 的内容是个 3D 纹理,无法直接观察。Custom SRP 6.1.0 起提供 LUT 调试可视化——把 LUT 作为渐变色块覆盖在屏幕角落,让美术能直观确认调色参数是否产生预期效果。这对调试”为什么调色看起来不对”极有帮助——美术可以对照标准的渐变色块判断当前 LUT 把哪些颜色区域怎么变换了。
6. Render Scale 与中间分辨率
6.1 设计目标
Render Scale 让渲染分辨率与显示分辨率解耦:
- Render Scale = 0.5:以一半分辨率渲染,最后放大到屏幕——大幅省 GPU
- Render Scale = 1.5:超采样渲染,缩小到屏幕——抗锯齿质量极高(SSAA 等效)
- Render Scale = 1.0:原生分辨率(默认)
这是动态分辨率(DRS)的基础——在性能压力大时降低 Render Scale 维持帧率,性能宽裕时回升保持画质。移动端项目中是必备特性。
6.2 中间 RT 与最终 Blit
启用 Render Scale 时,整个渲染流程都在中间 RT 上完成:
1 | |
中间 RT 的尺寸:
1 | |
整个 PostFX 链路(Bloom 金字塔、Color Grading 输出)都在 bufferSize 尺度下进行——这意味着 Bloom pyramid 的级数、PCF 的相对像素尺寸等都自适应缩放。
6.3 Bicubic Filtering:上采样质量
最终 Blit 把中间 RT 拉伸到屏幕尺寸时,简单 bilinear 会造成可见模糊。Bicubic 上采样使用 16 个邻域 texel(4×4)做高阶插值,效果显著优于 bilinear:
1 | |
Bicubic 的代价是约 5-7 倍的采样开销(16 tap vs bilinear 的 4 tap 实质成本)。Custom SRP 通过 _CopyBicubic 关键字让美术按需启用——大多数场景下 Render Scale > 0.7 时 bilinear 已经足够,更激进的下采样才需要 bicubic。
6.4 与后处理的协同
Render Scale 与 Bloom 的协同需要注意:Bloom pyramid 的级数应该基于中间 RT 尺寸计算,而不是屏幕尺寸。否则在 Render Scale = 0.5 时 pyramid 会少一级,Bloom 范围意外变小。
Catlike 实现已经处理这个细节——bufferSize 是所有 PostFX 阶段的统一尺寸基准。
7. FXAA 抗锯齿
7.1 抗锯齿方案对比
主流抗锯齿技术:
| 方案 | 原理 | 成本 | 质量 |
|---|---|---|---|
| MSAA | 多采样几何边缘 | 高(带宽 × 4) | 最高 |
| SSAA | 全屏超采样 | 极高 | 最高 |
| TAA | 时间累积抗锯齿 | 中 | 高(动态画面有 ghosting) |
| FXAA | 单帧屏幕空间分析 | 极低 | 中 |
| SMAA | 形态学 + 多采样混合 | 中低 | 高 |
FXAA(Fast Approximate Anti-Aliasing)由 Nvidia 的 Timothy Lottes 在 2009 年提出,核心是在 LDR 图像上通过 luma 检测边缘并对边缘做单方向模糊。计算量极低、不依赖 MSAA、不依赖时间累积——是移动端项目的现代默认选择。
7.2 FXAA 算法概要
简化的 FXAA 流程:
- 采样中心像素与四个邻居(上下左右)的 luma
- 检测对比度:max - min 是否超过 threshold?低于 → 不是边缘、跳过
- 判定边缘方向:水平边缘(左右 luma 差大)还是垂直边缘?
- 沿边缘方向迭代:步进探测边缘端点位置
- 混合样本:根据探测结果在边缘垂直方向 lerp 模糊
Custom SRP 集成 RP Core Library 的 FXAA 实现,分三个质量等级:
1 | |
| 等级 | EXTRA_EDGE_STEPS | 边缘探测能力 |
|---|---|---|
| Low | 3 | 中等长度边缘 |
| Medium | 8 | 大多数边缘 |
| High | 12 (默认) | 极长边缘 |
EXTRA_EDGE_STEPS 越多越能处理倾斜的长边(屋顶天际线、远处地平线),代价是每像素几次额外采样。移动端通常用 Medium,桌面端用 High。
7.3 Luma 计算
FXAA 需要 luma(亮度)输入。两种获取路径:
- 从 RGB 现算:
luma = sqrt(dot(rgb, float3(0.299, 0.587, 0.114)))——最常见 - 使用 alpha 通道:在最终 Blit 时把 luma 写入 alpha——节省 FXAA Pass 内的运算
第二种是 Custom SRP 的现代实现选择——前一个 Pass 写入 alpha = sqrt(luma),FXAA Pass 直接读 alpha。这是 keepAlpha = false 路径下的优化。
1 | |
注意:BT.709(HD)的 luma 系数是 (0.2126, 0.7152, 0.0722),比 BT.601(SD)的 (0.299, 0.587, 0.114) 略有不同。现代项目应使用 BT.709。
8. PostFXPass 总集成
最终的 PostFXPass 是所有上述阶段的集成器。它的任务是从中间 HDR RT、Bloom 结果、Color LUT 三个输入,一次产出最终 LDR 输出:
1 | |
后续 FXAA Pass 在此输出上应用边缘检测与模糊。最终输出到 Camera Target——也就是屏幕本身或外部 RT。
8.1 屏幕空间抖动(Dithering):消除 8-bit 色带的最后一道工序
即使中间 RT 全程使用 R11G11B10F、LUT 是 RGBA16F、计算全在浮点空间——最终输出给显示器的依然是 8-bit RGBA8 图像。Tone Mapping 的输出曲线在暗部(接近 0)和高光(接近 1)的斜率较小,意味着 HDR 输入的相邻浮点值映射到 LDR 后落在同一个 256 级量化档位上。
这种量化截断在以下场景产生明显的可见色带(Color Banding):
- 暗室墙壁的渐变阴影
- 夜晚天空盒的颜色过渡
- 平缓的雾效和大气散射
- 渐变 UI 背景
屏幕空间抖动通过给每像素引入微小的、空间分布合理的随机扰动,让”相邻的两档量化值在屏幕上随机分布”——人眼的视觉暂留效应会把这种分布感知为更平滑的中间色,色带被彻底打散。
1 | |
InterleavedGradientNoise(Jorge Jimenez 提出)是 Custom SRP 中已经使用的稳定屏幕空间噪声函数(Note 5 §2.4 LOD Cross-fade 和 Note 2 也用过它)。它的特点是同一像素位置每帧给出相同噪声值——如果用 frame-varying 的随机数会产生闪烁,反而比色带更糟糕。
更高质量的实现使用预计算的 蓝噪声(Blue Noise) 贴图——蓝噪声的频谱特性在视觉上比白噪声更不可见,是当代 3A 项目的标准选择:
1 | |
sampler_PointRepeat 让蓝噪声在屏幕上周期性平铺,64×64 或 128×128 的蓝噪声贴图就足够全屏使用。
💡 抖动是 3A 后处理栈的隐形收尾工序:成本极低(一次纹理采样 + 一次 lerp),收益显著(彻底消除 8-bit banding)。不加抖动的 PostFX 链路在中等画质需求下就能撞墙——美术经常发现”暗部就是有色带,调什么都调不掉”的根源就是这一步缺失。Custom SRP 教程没有展开这一步,但 production 项目应该把它视为 PostFXPass 的必备最后一公里。HDRP 的
_DitherTexture和 UE5 的DitherFinalOutput都对应这个工作。
需要注意:抖动应用在输出 sRGB 之前——即在线性空间内施加 1/255 的扰动。如果在 sRGB 编码后施加,扰动量就需要根据 sRGB gamma 曲线非线性调整,复杂得多。
9. TA Takeaway
9.1 后处理的总成本图谱
1 | |
后处理通常占整个 GPU 帧时间的 20-30%——这是移动端帧率最容易撞墙的阶段。
📱 Bloom 在 TBR 架构下的带宽真相:上述成本图谱的 ALU 数字是桌面级 GPU 的视角。在移动端 TBR 架构下,Bloom 的真实成本根本不在 ALU 上——而在带宽。Dual Filter Pyramid 在算法层面虽然是
的优雅设计,但每一次 Downsample 与 Upsample 都意味着切换 RT——从 Note 1 §7 已经确立的 TBR 行为推论:每次 RT 切换都触发一次完整的 attachment store-load 往返。一个 6 级 Bloom Pyramid 包含 6 次下采样 + 6 次上采样 = 12 次 store-load 往返。1080p × R11G11B10F 单次往返约 8MB,6 级 Pyramid 加上原图采样约 30-50MB 带宽——这在移动端 25-50 GB/s 的总带宽预算下是个惊人的数字,足以让单帧温度上升一个台阶。这也是为什么在重度移动端项目优化中,宁愿用更复杂的单 Pass 算法(Compute Shader 实现的合并下/上采样)减少 RT 切换次数,也要极力压缩 Bloom 的迭代层级—— maxBloomIterations在移动端通常配置为 4 而非默认的 5-6。同样的逻辑适用于所有多级 Pass 后处理:DOF、SSAO、Volumetric 等。在 TBR 架构上,带宽是比 ALU 更紧迫的预算,这是后处理优化的核心硬件认知。
9.2 LUT 烘焙是性能优化的范式案例
Color Grading 的 LUT 化是一个值得反复学习的优化范式:
问题特征:
- 计算成本高(50-100 ALU)
- 是纯函数(无状态依赖)
- 输入是有限的(RGB 三维空间)
- 参数变化频率低(美术调好后稳定)
优化思路:
把”逐像素计算”换成”一次性预计算 + 全屏查找表”。当问题满足上述四个特征时,LUT 化几乎总是巨大胜利。
类似可以 LUT 化的场景:
- BRDF 预积分(Karis BRDF LUT,HDRP/UE 标准实现)
- Atmospheric Scattering 预积分
- 复杂的颜色空间转换(如 PQ → SDR / HLG → SDR)
- 噪声模式生成
理解 LUT 化的背后哲学——用空间换时间、用一次预计算换每像素的运行时计算——是 TA 优化思路的核心工具之一。
9.3 中间分辨率与 PostFX 的协同设计
Render Scale 是当代移动端的必备特性,但与 PostFX 协同有几个隐性约束:
- Bloom 阈值是绝对值:Render Scale 改变后场景”亮”的认知不变,Bloom Threshold 不需要随之调整
- FXAA 在中间 RT 上工作:Render Scale = 0.5 时 FXAA 处理的是低分辨率图像,最终 Blit 时小尺度边缘已经被 bicubic 平滑——可以考虑跳过 FXAA 或用 Low 等级
- LUT 与 Render Scale 完全解耦:LUT 是颜色空间映射,与分辨率无关,无需任何调整
实践推论:移动端项目推荐 Render Scale 在 0.7-1.0 之间动态调整,配合 ACES + Color LUT + FXAA Medium——这是当前移动端 PBR 项目的画质/性能甜区组合。
9.4 实践原则
- HDR 中间 RT 用 R11G11B10F:除非碰到精度问题再上 RGBA16F
- Pre-Exposure 先于 Tone Mapping:让暗部精度集中在 1.0 附近
- Bloom 用 Soft Knee 而不是硬截断:Mach Band 是常被忽视的视觉瑕疵
- Bloom 移动端层级 ≤ 4:每多一级 = 多一次 store-load 往返,带宽收益负优化点比 ALU 来得早
- Color Grading 必走 LUT 路径:逐像素调色在中等画质需求下就能撞墙
- 3D LUT > 2D 模拟:Compute Shader 路径的精度与可读性都更好
- ACES Tone Mapping 是默认选择:除非有明确的非电影感需求
- Dithering 是 PostFX 必备最后一公里:1/255 量级的蓝噪声扰动,彻底消除 8-bit 色带
- FXAA 优于不抗锯齿:成本 < 1ms,效果显著
- Render Scale 而不是降低 PostFX 质量:先降分辨率再削 PostFX
关键 API 速查
CSHARP
1 | |
1 | |