GPU Gems 3 · Chapter 28. Practical Post-Process Depth of Field

从薄透镜与圆形弥散圈出发,解释如何用低分辨率 CoC、高斯平滑和可变宽度合成实现可接入现有引擎的后处理景深。

学习目标

  • 能解释焦点平面、光圈和前后景清晰范围之间的关系
  • 能修改 Practical Depth of Field Lab 的焦点、光圈、CoC 算法与工作分辨率,比较成本和伪影
  • 能回答:为什么先平滑近景 CoC,再用可变宽度模糊合成,比每个像素独立随机取样更稳定

先问:为什么镜头只让一段距离清楚

想象一张纸上有一排小人,镜头前后各放一个透明圆环。只有落在纸面上的光线会聚成一个小点,离开纸面的光线会在纸上摊成一圈;圈越大,轮廓越软。

本章要解决的问题是:已有一帧画面和每个位置的远近记录时,怎样让一段距离保持清楚,让前后景逐渐变软,同时把运算量控制在实时游戏可以接受的范围内。

没有这套安排,最直接的做法要么把场景重复渲染很多次,要么对每个像素做昂贵的随机邻域搜索;前者难接入旧引擎,后者会产生响铃边缘、前景轮廓仍然锐利等明显破绽。

1. 薄透镜把“清楚”变成一个半径问题

把镜头的焦距记作 f,光圈半径记作 d,物体距离记作 u。薄透镜几何告诉我们:物体在成像面前后偏离时,不再落成一点,而是形成直径为 c 的圆。实时实现不必精确模拟每条光线,但必须保留“离焦越远,圆越大”的单调关系。

焦平面上的 c 接近零,前景的 CoC 通常增长得更快,远景则有一个较明显的上界。这解释了为什么一个统一的模糊半径既不自然,也无法同时照顾近处物体和远处背景。

focus is a plane; blur is a circle with a radiusforegroundlarge CoCfocal planetiny CoC = sharpbackgroundbounded far CoCaperture controls how quickly the circle grows away from the focal plane

2. 先选择后处理路径,再决定要保留多少物理细节

景深可以用光线追踪、累积多次相机、分层合成、向前散射或向后聚合等方式实现。对于已经有颜色和深度输出的游戏引擎,基于 z-buffer 的图像处理更容易接入:它把场景绘制与景深处理分开,且深度图还能复用于软粒子和雾。

本章的实现选择“向后聚合”思路:每个目标像素从邻域取颜色,而不是把源像素散射到未知的目标位置。这样访问模式更适合纹理采样,也能在最后一个全屏 pass 里统一决定清晰、小模糊还是大模糊。

对于不允许把深度缓冲当纹理读取的旧硬件,可以在深度预通过程把世界空间距离写入 32 位浮点颜色目标。透明像素用 clip() 丢弃,后续阶段仍然读取同一份深度信息;这改变的是数据通道,不改变 CoC 和合成逻辑。

3. 近景 CoC 先下采样,再用平滑消掉硬边

完整的前半段可以拆成三个动作:先为前景像素计算 CoC,焦点和背景像素的近景 CoC 设为零;再同时缩小颜色与近景 CoC;最后在缩小的 CoC 图上做大范围平滑。按面积看,四分之一宽高意味着只处理十六分之一的像素。

高斯模糊的重点不是制造一个更大的颜色污渍,而是让 CoC 在边缘附近连续。实际实现可以把大半径拆成水平、垂直两个方向,并利用双线性过滤一次拿到相邻样本,从而把纹理读取成本压下来。

spend blur work where the foreground CoC needs itfull framecolor + depthexpensive resolutionquarter resolution4×4 source regionfewer target pixelsblur near CoCGaussian blursmooth neighboring CoCsdownsampled work makes a large blur radius affordable

4. 用邻域最大值保护前景轮廓的连续性

近景物体和清晰背景交界处最容易露馅:如果只看当前像素,前景边缘可能只模糊一半,背景颜色还会穿过它。算法不必搜索所有邻居,而是用已经平滑的 CoC 估计附近半径,并在两侧选择更大的直径,再做一次小模糊。

make CoC edges continuous before the final compositeraw edgehard stepmax neighborchoose largerfinal small blursoft transitionthe final blur removes ringing without searching every neighbor

这个修正是在质量和成本之间取舍。它不保证每个遮挡关系都像多层几何一样正确,却能把最刺眼的硬边、响铃和 90 度角不连续压下去。对实时第一人称游戏来说,连续的视觉感受往往比严格的物理可逆更重要。

动手走一遍:四个阶段组成可插拔的景深管线

分步1 / 4

先从深度计算前后景 CoC

用焦点距离、光圈和深度得到近景与远景的弥散圈,焦平面附近接近零。

focus is a plane; blur is a circle with a radiusforegroundlarge CoCfocal planetiny CoC = sharpbackgroundbounded far CoCaperture controls how quickly the circle grows away from the focal plane
practical depth of field in four passes1 · CoCdepth → radiusnear + far2 · shrinkquarter targetcolor + CoC3 · smoothGaussian CoCsoft edges4mixoptimize the largest blur where the image is already quarter-sized

第 1 / 4 步 · 从深度计算近景与远景的弥散圈

逐步观察深度如何变成弥散圈,再变成平滑且可控的最终合成。

5. 可变宽度模糊把成本集中在需要的像素

最终 pass 可以准备三档结果:原始或清晰图、小半径模糊图、大半径模糊图。对每个像素读取深度得到远景 CoC,同时读取经过修正的近景 CoC,取两者较大值,再把这个值映射到三档之间的权重。权重从一档平滑过渡到下一档,避免半径变化时出现亮度跳变。

half3 sharp = GetSmallBlurSample(texCoords);
half3 medium = tex2D(mediumSampler, texCoords).rgb;
half3 large = tex2D(largeSampler, texCoords).rgb;
half depth = tex2D(depthSampler, texCoords).r;
half nearCoc = tex2D(nearCocSampler, texCoords).r;
half farCoc = saturate(dofEqFar.x * depth + dofEqFar.y);
half coc = max(nearCoc, farCoc * dofEqFar.z);
 
half3 color = lerp(sharp, medium, smoothstep(0.0, smallRange, coc));
color = lerp(color, large, smoothstep(mediumRange, largeRange, coc));
return half4(color, coc);

这里的关键不是某一组常数,而是把近景和远景都纳入同一个连续的 CoC,且每次插值都保持颜色能量稳定。大半径越大,画面越像把整张屏幕涂开;所以最大半径应由目标物体尺寸、细节频率和平台预算共同决定。

6. 透明物体与大半径是必须显式接受的边界

透明表面通常会继承其后方第一个不透明物体的 CoC,玻璃、粒子和窗口边缘因此可能出现不一致。常见工程选择是先对不透明几何完成景深,再在后面单独画透明对象,或接受少量边界伪影换取更低的管线侵入。

GPU Gems 3 · Chapter 28

Practical Depth of Field Lab

可交互

调整焦点、光圈、场景距离和 CoC 算法,比较前景、焦平面与背景的模糊半径及伪影风险。

depth → circle of confusion → variable blurfocal planeforeground CoC 11background CoC 13focus 50 · blur radius 13px · 5 passescontinuous CoC smoothing · controlled
前景 CoC11
背景 CoC13
最大模糊半径13px
估算 pass5

先猜一猜:把“平滑 CoC”切换成“随机取样”,再把前景场景的焦点推远,哪个指标会先暴露问题?在 Lab 中一次改一个参数,观察前景 CoC、背景 CoC、最大模糊半径与伪影风险。

小结

  • 焦平面附近 CoC 接近零,前景和背景离焦程度不同。
  • 深度图让景深成为可插入现有引擎的后处理。
  • 四分之一分辨率与高斯模糊承担大半径近景工作。
  • 平滑并取邻域较大 CoC,能减少前景硬边和响铃。
  • 最后一遍可变宽度合成,在质量、成本和透明边界之间取舍。

练习

练习

问题 1|修改 Demo 代码。 把最终合成中的 smallRangemediumRangelargeRange 改成三组更近的阈值,比较焦点过渡是否变硬,以及颜色是否出现跳变。

问题 2|诊断前景轮廓。 远景效果自然,但近处角色的轮廓像剪纸一样清楚。请说明为什么只按当前像素 CoC 的随机取样会失败,以及应加入哪一步。

问题 3|场景选型。 一个普通第一人称场景、一个需要极大前景模糊的离线镜头、一个包含大量透明粒子的场景,分别说明是否适合本章的纯后处理路径。

名词解释

本章出现的专业名词,用大白话再讲一遍。

circle of confusion

离焦点在成像面上形成的圆;圆越大,物体看起来越模糊。

focal plane

镜头把光线聚成清晰点的一层空间,焦点距离决定它的位置。

depth buffer

记录每个屏幕位置远近的图,景深用它计算 CoC。

quarter-resolution downsample

把宽高都缩小到四分之一,让大半径模糊只处理少量目标像素。

Gaussian blur

用邻域加权平均平滑颜色或 CoC 的滤波,常拆成水平和垂直两次。

variable-width blur

根据每个像素的 CoC,在不同模糊级别之间连续混合的方式。

资料与写作方式声明

本章以GPU Gems 3 · Chapter 28. Practical Post-Process Depth of Field权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…