← ALL ARTICLES

景深与运动模糊

全库目录221
本文目录11

失焦和运动都会让一个表面点覆盖更多像素,但前者来自光学焦平面,后者来自曝光期间位移。两种模糊都要解决前后景混色,因此先生成CoC或速度,再在深度约束下分层采样。

Depth of Field

景深根据镜头参数和深度估计 Circle of Confusion(CoC)。焦平面附近 CoC 小,前景和背景离焦区域 CoC 大。

实现难点:

  • 前景模糊会覆盖后方清晰像素;
  • 深度边界容易漏色;
  • 半透明没有可靠单层深度;
  • 大 Blur Radius 成本高;
  • Bokeh 形状和能量需要控制。

薄透镜与像素半径

设焦距为 ff、对焦距离为 zf>fz_f>f、物体正深度为 z>0z>0、光圈值为 NN。孔径直径为 f/Nf/N;薄透镜像距为 v(z)=fz/(z−f)v(z)=fz/(z-f)。固定成像平面在 v(zf)v(z_f) 后,用相似三角形比较离焦光束与孔径,可得带符号的弥散圆半径

rsensor=f22N(zf−f)z−zfz.r_{sensor}=\frac{f^2}{2N(z_f-f)}\frac{z-z_f}{z}.

本页规定近景为负、远景为正。z是沿相机光轴的线性正深度,需从深度缓冲按投影约定恢复;不能直接使用设备深度或到相机的欧氏距离。f,z,zff,z,z_f 与传感器宽度使用同一长度单位。若水平分辨率为 WW、传感器宽度为 SwS_w,像素半径为 rpx=rsensorW/Swr_{px}=r_{sensor}W/S_w,不能把直径当半径使用。

hlsl
float CoCRadiusPixels(float depth, float focusDistance, float focalLength,
    float fNumber, float sensorWidth, float imageWidth)
{
    float radius=focalLength*focalLength /
        (2.0*fNumber*(focusDistance-focalLength));
    return radius*(depth-focusDistance)/depth*imageWidth/sensorWidth;
}

这是HLSL机制函数;所有输入必须有限,焦距、光圈值、传感器宽度和图像宽度均大于零,同时满足上述深度和对焦距离条件。50 mm焦距、f/2、对焦2 m、36 mm传感器宽和1920像素时,1 m处半径约为-17.09像素,2 m处为0。扩大光圈值会缩小半径;近裁剪面附近的半径可能极大,实际采样设置上限并记录截断误差。

前后景覆盖

只按中心像素的CoC搜索邻域,清晰背景像素的半径为零,就无法接收前景失焦物体扩散过来的颜色。收集时还需检查邻居自己的半径:邻居 ii 到目标像素的距离小于 ∣ri∣|r_i| 时,它才覆盖目标。圆盘核的理想权重与 1/(πri2)1/(\pi r_i^2) 成正比,因此大光斑应把同一份能量分散到更多像素,不能每个覆盖像素都写完整颜色。

屏幕实现通常将近景和远景分别保存预乘颜色与覆盖,先求远景背景,再将近景覆盖合成在前。收集需要限制最大搜索半径、逐样本检查深度和CoC,并归一化有效核权重。近景覆盖扩张或最大半径分块有助于找到来自邻居的贡献,但会引入过覆盖近似。单层颜色图没有被前景挡住的背景,即使覆盖计算正确,也仍需填补或接受缺失信息。

近景覆盖的合成算例

设清晰背景为蓝(0,0,1),失焦前景为红(1,0,0),某目标像素接收近景核的总覆盖0.25。近景缓冲保存预乘颜色(0.25,0,0)和Alpha0.25;覆盖到已完成的远景后,结果为(0.25,0,0.75)。若把非预乘红色直接作为源、同时使用预乘混合,会得到(1,0,0.75),凭空增亮。

一个前景点的圆盘半径由2像素变4像素时,理想面积变成四倍,每单位面积权重应降到四分之一。收集邻居时必须使用邻居自己的半径判断其是否覆盖目标,中心CoC=0并不意味着可以跳过邻近前景。这个简化覆盖例隔离预乘与能量;单层输入缺失的背景仍无法从混合方程恢复。

Motion Blur

运动模糊使用相机和物体 Motion Vector 沿运动方向采样。只用相机矩阵无法处理骨骼、顶点动画和独立物体运动。

快门时间与速度单位

若运动向量为当前UV到上一帧UV的位移 v=uprev−ucurrentv=u_{prev}-u_{current},帧间隔为 Δt\Delta t、快门曝光时间为 tst_s,则常速假设下的曝光轨迹长度是 v ts/Δtv\,t_s/\Delta t。180°快门意味着 ts=Δt/2t_s=\Delta t/2,因此轨迹只有一帧位移的一半。像素单位速度先除以对应宽高,NDC单位位移则先按纹理轴向和二分之一尺度转换。

尾随快门覆盖当前时刻之前的一段时间。一个表面点当前位于p,过去的对应位置是 p+λvp+\lambda v;要在固定目标u重建过去的颜色,当前图的读取位置因此是 u−λvu-\lambda v。均匀快门的中点采样为

uj=u−j+0.5MvtsΔt,j=0,…,M−1.u_j=u-\frac{j+0.5}{M}v\frac{t_s}{\Delta t},\qquad j=0,\ldots,M-1.

将有效颜色加权求和后再除以权重和。以下HLSL机制函数只生成查询坐标:UV与速度使用相同纹理轴向,速度为prev-current;frameSeconds大于零,shutterSeconds非负,sampleCount为正整数且sampleIndex小于它,所有浮点输入有限。

hlsl
float2 MotionGatherUV(float2 uv, float2 backwardVelocity,
    float shutterSeconds, float frameSeconds, uint sampleIndex, uint sampleCount)
{
    float fraction=(float(sampleIndex)+0.5)/float(sampleCount);
    return uv-backwardVelocity*(fraction*shutterSeconds/frameSeconds);
}

用整幅一维信号的匀速平移隔离方向问题:一个亮点从上一帧u=0.5移动到当前u=0.6,v=-0.1。半帧快门覆盖的亮点位置是[0.55,0.6]。目标u=0.575采用一个中点样本时,应查询当前图u=0.6;公式给出 0.575-(-0.1)*0.5*0.5=0.6。若把减号写成加号,就会读取u=0.55的空处,并使亮点向运动前方扩散。

这仍是利用当前颜色图的收集近似,不能恢复过去真正被遮挡的表面;完整光学积分应考虑曝光期间的可见性。零快门时间直接返回中心颜色。

需要限制最大像素半径,处理深度边界和前后景速度差。当前像素自己的速度不足以表示邻近快速物体扫过它的覆盖范围,实际方法常结合邻域最大速度和深度分类。骨骼与顶点动画需要前后姿态位置,不能仅由相机矩阵推导。TAA投影抖动通常应从几何运动中剔除,否则静止场景也会模糊。

Lens Effect

Vignette、Chromatic Aberration、Film Grain、Lens Distortion 可以塑造镜头感,但它们通常在显示链后段工作。

这些效果不应遮盖曝光、颜色空间或上游渲染错误。Chromatic Aberration 和 Grain 还会降低 TAA/压缩后的细节稳定性。

资源与调度

景深和运动模糊都读取线性颜色、深度及辅助数据;前者的CoC以像素半径保存,后者的速度在本页约定为UV位移。低分辨率缓冲中的半径要按该缓冲分辨率换算,不能沿用全分辨率数值。

先景深后运动模糊或反过来都会改变边界与高亮形状,两种操作一般不交换。宿主需固定资源生产顺序,记录透明物体是否提供深度/速度、UI在哪一阶段合成,以及相机切换时如何重置历史。Lens Effect的排列按同样的颜色与时域契约确定。

验证方法

固定焦距移动焦平面,检查近远CoC符号及前景覆盖。静止相机下移动骨骼角色核对物体速度;设置曝光比例为零时运动模糊消失。镜头颗粒与色差是附加显示效果,不能用来掩盖上游编码错误。

test_post.py 检查对焦处零半径、近远符号、同场景毫米/米换算、光圈比例及非法参数拒绝,并用上述解析平移亮点检查运动采样方向;正文CoC与运动坐标函数通过DXC编译。分层覆盖、运动采样和实际GPU效果尚未运行。

相关主题

参考资料