7.2 高斯模糊与色调映射


7.2 高斯模糊与色调映射

本节摘要:模糊是后处理里复用率最高的原语,可分离性让它从平方开销降到线性开销;色调映射是线性渲染管线的规定收尾动作,把无上界的亮度压回屏幕能显示的范围。本节给出可分离高斯模糊的实现与两条经典映射曲线,并厘清它们在管线里的先后位置。

学习目标

读完本节,你应当能够:解释可分离性带来的计算量节省并写出两趟一维模糊;区分 Reinhard 与 ACES 的观感差异;说清色调映射与 gamma 校正的先后顺序。

可分离性:模糊提速的关键

二维高斯核有个漂亮的数学性质:二维卷积可以拆成先横向、再纵向的两趟一维卷积。这笔账值得算给直觉看——9 乘 9 的二维核每像素要采样 81 次,拆成横向 9 次加纵向 9 次只需 18 次,核越大差距越悬殊:33 乘 33 的核是 1089 次对 66 次。视觉结果几乎无差,性能差出一个数量级,这就是所有引擎的模糊都跑两趟的原因。

一维高斯模糊 pass 的实现(配合 7.1 的骨架,横竖各跑一趟):

// 高效的一维模糊:5 次采样近似 9 点高斯(采样点落在双线性插值最优点上) #version 330 core in vec2 vUv; out vec4 FragColor; uniform sampler2D uTex; uniform vec2 uDirection; // 横向传 (1/宽, 0),纵向传 (0, 1/高) void main() { vec4 sum = texture(uTex, vUv) * 0.227027; sum += (texture(uTex, vUv + uDirection * 1.3846154) + texture(uTex, vUv - uDirection * 1.3846154)) * 0.3162162; sum += (texture(uTex, vUv + uDirection * 3.2307692) + texture(uTex, vUv - uDirection * 3.2307692)) * 0.0702703; FragColor = sum; }

权重加起来恰好约等于 1——模糊不能凭空造亮。这版实现还藏了一个优化彩蛋:采样偏移量取在双线性插值的"合并点"上,一次 texture 调用等于免费混合两个相邻像素,5 次采样等效 9 点核。方向由 uniform 传入而不是写死,同一份着色器横竖两用,也方便做任意角度的运动模糊。

想扩模糊范围,不必加大核:把结果再跑一遍,或先降采样再模糊(7.1 的老朋友)。"降采样加多趟"是引擎级模糊的标准配方,每一步都在用分辨率换半径。

色调映射:把无限压进屏幕

线性管线里,亮度没有上界:太阳、灯泡、高光点的值可以是 10 也可以是 100,而屏幕最亮只能显示 1.0。直接截断(clamp)会把所有过亮区域糊成一片惨白的大色块,层次全无。色调映射(tone mapping)的任务是找一条平滑的压缩曲线:低亮度近乎线性保留,高亮度渐近压缩——亮,但亮得有层次。

两条经典曲线:

// Reinhard:简单优雅,整体偏灰偏软 vec3 reinhard(vec3 c) { return c / (1.0 + c); } // ACES 近似:暗部扎实、高光带滚动过渡,电影感的主流选择 vec3 aces(vec3 x) { return clamp((x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0); }

图:三种输出策略对高亮度的响应曲线

图:三种输出策略对高亮度的响应曲线

选型经验:小项目与风格化渲染,Reinhard 一行搞定;写实场景与有强光源的画面,ACES 近似的观感明显占优——暗部不发灰、高光从亮到白有"滚动"过程而非生硬压扁。曲线还可整体乘一个曝光参数(进曲线前先乘 exposure),这就是相机曝光滑杆背后的全部数学。

顺序问题:色调映射、gamma 与管线位置

输出前的最后一段路有严格的顺序,错一步画面对比就会怪异:线性空间计算 → 色调映射 → gamma 校正 → 上屏

回顾 6.3 的代码,结尾两行正是这个顺序:先 color / (color + 1) 压亮度,再 pow(color, 1/2.2) 做 gamma。为什么不能反过来?gamma 校正是对"显示设备响应"的补偿,作用对象是最终的 0 到 1 显示值;色调映射处理的是"超范围亮度"的压缩,作用对象是线性物理量。先 gamma 再压缩,等于给还没归一的数据做了非线扭曲,高光与暗部全走样。

完整的线性工作流可以概括成三句话:贴图以 sRGB 格式加载,采样时硬件自动转线性;全部光照与混合在线性空间完成;输出前色调映射加 gamma 一次到位。第 5 章的贴图、第 6 章的光照、本章的收尾,至此串成一条完整的色彩管线。

💡 关键直觉:画面"发灰没对比"多半是 gamma 被做了两次或零次;高光"惨白一片"多半是缺色调映射。色彩管线的故障有个特点——参数怎么调都救不回来,必须回到顺序上找。

曝光:曲线前的最后一个旋钮

正式的色调映射代码里总有一个 exposure(曝光)参数,位置在进曲线之前:

vec3 mapped = aces(color * uExposure); // 先曝光后压缩

它的意义是模拟相机的光圈快门:同一份线性亮度,乘上不同曝光得到不同明暗基调——白天场景低曝光保高光层次,夜戏高曝光把暗部细节捞出来。动态曝光(随场景亮度自动调整)就是游戏里"从洞穴走进雪原眼睛适应"的效果,机制无非是逐帧缓慢插值这个 uniform。

把曝光、映射、伽马连起来看,输出链的完整公式是三行:color *= exposure; color = aces(color); color = pow(color, 1/2.2);。调画面时按此顺序排查——曝光管整体明暗,曲线管高光形态,伽马管对比基调,各管一段互不越权。参数失控时按这个顺序归位,比乱调快得多。

本节要点回顾

  • 可分离性:二维模糊拆横竖两趟,1024 核变 66 次采样,所有引擎模糊的提速根基;
  • 半径换分辨率:多趟叠加或先降采样再模糊,比加大核便宜得多;
  • 两条曲线:Reinhard 简单偏软,ACES 暗实高光滚,进曲线前乘曝光即得曝光控制;
  • 顺序铁律:线性计算、色调映射、gamma 校正,不可颠倒,错序的症状是全画面发灰或过曝;
  • 管线合龙:sRGB 贴图、线性光照、色调映射收尾,色彩管线自此完整。

跨平台的最后一公里在眼前。下一章把本册所有代码翻译成 HLSL 的表达,并审视两套语言的差异与兼容之道。


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