6.4 后处理阶段:色调映射、Bloom 与抗锯齿收尾


6.4 后处理阶段:色调映射、Bloom 与抗锯齿收尾

本节摘要:后处理是管线的最后一站:整帧渲染完成后,再用全屏图像 Pass 加工一遍——HDR 值经色调映射压进显示范围,亮部经 Bloom 洇出光晕,几何锯齿由 FXAA/TAA 擦除,运动模糊与景深补上镜头的"不完美"。本节讲这几个经典 Pass 的原理与实现要点,并说明全屏 Pass 的成本结构与"后处理链不能无限叠加"的带宽账。像素在这里完成出厂前的最后精修。

本节能力目标

阅读完本节,你应当能够:

  1. 说明 HDR 帧为什么必须色调映射,比较 Reinhard 与 ACES 曲线的行为差异
  2. 实现 Bloom 的三步流程(提取亮部、降采样模糊、叠加回主帧)
  3. 解释 FXAA 靠对比度找边、TAA 靠时间积累的两种抗锯齿思路
  4. 算一笔后处理链的带宽账,说出"Pass 越多越卡"的原因

为什么需要色调映射

5 章光照在线性 HDR 空间计算,太阳可能算出 50.0、烛火 2.0,而显示器只认 0 到 1。直接截断:大量中间亮度被压成一团白。色调映射(tone mapping)的任务是把巨大的亮度范围"优雅地"折进 0 到 1,保留层次与观感:

def reinhard(c): # Reinhard 曲线:简单收敛,高亮整体偏灰 return c / (1 + c) def aces_approx(c): # ACES 近似:胶片感的 S 曲线,高光柔和滚降、色相更稳 a, b, c_, d = 2.51, 0.03, 2.43, 0.59 return min(max((c*(a*c+b)) / (c*(c_*c+d)+0.14), 0.0), 1.0) for v in (0.2, 1.0, 5.0, 50.0): print(f"输入 {v:5}: Reinhard {reinhard(v):.3f} ACES {aces_approx(v):.3f}") # 输出: # 输入 0.2: Reinhard 0.167 ACES 0.175 # 输入 1.0: Reinhard 0.500 ACES 0.805 # 输入 5.0: Reinhard 0.833 ACES 0.973 # 输入 50.0: Reinhard 0.980 ACES 1.000 # Reinhard 中亮部全挤在 0.8~1 区间;ACES 让 1.0 就到 0.8,层次拉开

先曝光(整体乘一个系数,模拟相机曝光量)再映射,最后按 2.2 的幂编码回 sRGB(2.2 节的出口流程)——三步合称"最终颜色分级",现代游戏的美术风格一半在此调出。

Bloom:亮部洇出光晕

人眼与相机镜头里的强光源会"晕开"。Bloom 的实现是三步流水:

import numpy as np def bloom(bright_pass, threshold=1.0): # 第一步:提取超过阈值的亮部 lit = np.clip(bright_pass - threshold, 0, None) # 第二步:降采样金字塔模糊(示意:逐层减半后高斯混合) small = lit[::4, ::4] # 降采样本身就是低通 # 第三步:放大叠加回主帧 up = np.kron(small, np.ones((4,4,1)))[:lit.shape[0], :lit.shape[1]] return bright_pass + 0.5 * up frame = np.zeros((8, 8, 1)); frame[4,4] = 12.0 # 黑帧上一个强亮点 print(bloom(frame)[3:6, 3:6].ravel()) # 输出: [0. 0. 0. 0. 12. 0. 0. 0. 0. ] # 示意版降采样保留了亮点本身;真实实现里模糊核会把它周围染出一圈渐变光晕

降采样金字塔(每级减半、逐级模糊再逐级叠回)是带宽友好的模糊法:大范围模糊在低分辨率层做,成本骤降。提取阈值常设 1.0(HDR 空间里超过 1 即"超亮"),发光材质把亮度写到 3~10 就能自然参与 Bloom。

抗锯齿的收尾方案

3.1 节的超采样抗锯齿(SSAA)成本太高,实时的两大主力各有哲学:

FXAA(快速近似):单个 Pass 分析对比度,找到锯齿边就把邻像素混合 便宜(约 1 毫秒)、不需额外显存;缺点是可能把纹理细节也当边抹糊 TAA(时间性):每帧偏移采样位置,历史帧与当前帧加权累积 质量极佳、几乎免费;缺点是运动时残影(幽灵化)、透明与动画元素难处理

TAA 是当代 3A 的默认选择,它把 3.1 的空间超采样换成时间超采样:每帧只采 1 个样本,但样本位置在像素内逐帧跳动(抖动),多帧累积等效于每像素多样本。代价是历史帧与运动物体的矛盾,需要运动矢量与重投影来"把历史对齐到当前",这正是动画与后处理交叉的地带。

后处理链的带宽账

每个全屏 Pass = 读一整帧纹理 + 写一整帧纹理。1080p 的 RGBA8 一张约 8.3 MB(2.1 节的账本),读写各一次就是 16.6 MB,HDR 浮点纹理翻倍;一个链挂六七个 Pass,仅后处理就可能吃掉几 GB/s 的带宽。优化手段全在减少搬运:能合并的 Pass 合并(一次片元里做完色调映射加 Bloom 混合)、半分辨率做模糊再升采样、计算着色器直接在显存里原地处理。移动端还多一重顾虑:发热与耗电直接随带宽走,后处理链常砍到只剩色调映射加 FXAA。

⚠️ 常见坑:后处理链的顺序有讲究——抗锯齿应在 Bloom 之前(否则光晕边缘被当作锯齿抹掉)、色调映射应在 Bloom 之前(Bloom 在 HDR 空间提取才有物理意义)。顺序颠倒,画面立刻"塑料感"。

症状速查:后处理链排错

  • 白天场景像蒙了雾、高光区死白一片 → 多半没做色调映射直接截断,先加 Reinhard 或 ACES
  • 暗部细节全糊成黑团 → 曝光系数过低或色调映射曲线过陡,抬曝光再看
  • 泛光光晕边缘有锯齿 → 抗锯齿放在了 Bloom 之后,调换 Pass 顺序
  • 画面整体偏灰像褪色照片 → Reinhard 用在强对比场景,换 ACES 类曲线
  • 移动端加上后处理链后掉帧严重 → 逐个 Pass 关闭定位,多半是带宽瓶颈,改半分辨率

本节要点回顾

  • 色调映射是 HDR 的出口:Reinhard 简单、ACES 层次佳,曝光在前、编码在后
  • Bloom 三步走:提取、金字塔模糊、叠加,都在 HDR 空间完成
  • FXAA 空间找边、TAA 时间积累:一个便宜一个优质,各有失真模式
  • Pass 越多带宽越炸:合并、半分辨率、原地处理是三板斧
  • 顺序也是正确性:抗锯齿、色调映射、Bloom 的次序错位即画质事故

第 6 站到站。实时管线全速运转,但有些画面它给不了——第 7 章走出实时约束,看离线渲染、动画与图形学的更远方。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U