--- title: "TAA(temporal antialiasing)" date: 2022-05-15T17:55:10+08:00 ------ 知识沉淀有限,后续会补充内容 当前内容来源: [ [SIGGRAPH 2016] Temporal Antialiasing in Uncharted 4 ](https://github.com/QianMo/Real-Time-Rendering-4th-Bibliography-Collection/blob/main/Chapter%201-24/%5B1938%5D%C2%A0%5BSIGGRAPH%202016%5D%20Temporal%20Antialiasing%20in%C2%A0Uncharted%204.pptx) 十分感谢浅墨大佬的整理 ---------------- ###### 基本思想 与超采样类似且在静态图片下实现方式相同 对同一像素点内采用多个采样点的方式来减少走样但将采样点分散到了一段时间内的多个帧上,在每帧采样时对采样点进行偏移,即抖动(jitter)来实现MSAA中放置多个次采样点的效果 采样序列也使用Halto sequence ![](../../images/Halton_sequence.png) ###### jitter的实现 采样点的位置会在初始化的时候确定,之后需要在与像素中心距离$[0,1]$的范围里发生偏移 实现这个效果只需要对投影矩阵中的值进行改动 ![](../../images/offset_projection_matrix.png) 图片中标红的值便是在归一化后的坐标空间里偏移值的替换位置 当然,只完成jitter只会让实时渲染的图像发生严重的抖动,接下来我们只需要让这些抖动的帧收敛(converge, PS:实在找不到合适的词来翻译) ###### 在渲染管线中的位置 TAA Shader 输入: 当前帧的HDR缓存
上一帧的TAA计算结果或者历史结果缓存 输出: 当前帧的TAA结果,用于输出到渲染管线的下一处理阶段 和 作为下一帧TAA计算的历史结果缓存 由此可以推断出TAA Shader在渲染管线的位置 Depth only pass (main view scene pass)
GBuffer pass (main view scene pass)
Deferred lighting shader $\leftarrow$ HDR
Temporal AA
Post processing (tone mapping, motion blur, etc.) $\leftarrow$ LDR ###### 静态场景 在静态场景下应用TAA 设置好输入和输出
启用全屏着色
在历史值和现在值之间做插值计算 ```S float3 currColor = currBuffer.Load(pos); float3 historyColor = historyBuffer.Load(pos); return lerp(historyColor,currColor,0.05f); ``` 插值时,不同的权重会带来不同的影响
当前帧的颜色的权重越高,抖动越明显,但收敛速度较快
历史帧的颜色的权重越高,抖动越少,走样越明显,但是收敛速度慢 0.05是一个合适的权重值来平衡反走样质量与收敛速度,这意味着每一个新渲染的帧都只对最终的静态场景生成占比5% ------------------------- ###### 动态场景 在动态情况下,我们需要使用全局运动向量来计算当前帧的某一个像素在上一帧的位置坐标 所以在GBuffer里我们通常作如下计算
$ pos_{proj}\times mat_{wvp}$
$ posLast_{proj}=posLast_{obj}\times matLast_{wvp}$ 由此我们可以总结出计算运动向量(Motion Vector)所需的值 1. 上一帧的相机信息 2. 每一个物体上一帧的$ mat_{o2w}$ 3. 每一个蒙皮的物体在上一帧时的骨骼位置,并在每一帧计算两次蒙皮和输出上一帧和目前帧的顶点位置 --------------- 当然,在GBuffer里我们可以直接获得目前帧的ndc(normalized device coordinate)坐标,出于不将jitter视为运动的目的,我们也要在ndc里去除jitter带来的偏移 $ pos_{ndc} -= g_{projOffset} $
$ posLast_{ndc} -= g_{projOffsetLast} $
$ float2 \quad motionvector = (posLast_{ndc}- pos_{ndc} ) * float2(0.5f,-0.5f) $ ------------------------- 对于滚动的贴图(比如流动的单层贴图水)
我们需要计算贴图在贴图UV里的变化量(deltaU,deltaV)在屏幕空间里造成了多少变化量(deltaX,deltaY)
deltaU = ddx(U) * deltaX + ddy(U) * deltaY
deltaV = ddx(V) * deltaX + ddy(V) * deltaY ----------------------------- 理论上所有物体都应有motion vector,但依旧有某些物体不支持motion vector 1. 有复杂贴图的动画物体,如粒子烟雾,水流,云的移动 2. 半透明物体,因为motion vector仅有一层而无法写入 当然,我们可以选择将它们的绘制顺序调换至TAA之后,但并非所有物体都允许这么做,任何物体在未经TAA处理的情况下都会发生抖动
纠正抖动并非只是简单地去除,还要用抖动后的深度情况进行检测 -------------------- 在对多种情况进行讨论后,我们便可以开始进行motion vector的混合计算,计算前必须去除当前采样的抖动偏移值 ```s //去除抖动偏移值,得到像素中心值 uv -= _jitter //计算上一帧的投影坐标 float2 uvLast = uv +motionVectorBuffer.Sample(point,uv); //双线性模式采样 float3 historyColor = historyBuffer.Sample(linear,uvLast); ``` 镜头移动的时候,很多物体之间的遮挡关系会发生变化,例如 1. 原本不出现的物体在下一帧出现,原来出现的物体被遮挡而消失 2. 光线发生了改变,阴影的位置改变或高光位置改变 这会导致采样motion偏移到的位置在上一帧并没有渲染数据,这时为了数据的平滑过渡,可以在像素点位置周围判断深度,取距离最近的点位来采样获取motion vector的值,减弱遮挡错误的影响 ```s //通过遍历采样点周围9个像素的方式计算neighborMin和neighborMax float3 neighborMin,neighborMax; historyColor = clamp(historyColor,neighborMin,neighborMax); ``` 这种方式也存在一些弊端
对于未发生改变的物体,会截断(clamp out)太多不同的历史颜色值
$3\times3$的像素点周围采样无法对边缘的反走样生效 ----------------------------------- 这种计算方式有时会因 neighborhood min/max的值过大使clamp失效而产生鬼影(ghosting)现象 解决方法: 1. 使用模板位( stencil bits , PS:这个我也不好翻译)的方式将物体分割为两个部份 2. 将目前帧和上一帧的stencil 输入到TAA shader中处理 ```s uint currStencil = stencilBuffer.Sample(point,uv); uint lastStencil = lastStencilBuffer.Sample(point,uvLast); blendFactor = (lastStencil & 0x18) == (currStencil & 0x18) ? blendFactor : 1.f; //0x18表示有两个ghosting bits (不好翻译+1) ``` 在经过处理后,鬼影现象消失,但当相机从向右旋转时,人物的左侧边缘像素表现效果较差 ![](../../images/revealed_piels_appear_bad.png) 为了减少这种情况的产生,我们在blendFactor为1时,返回一个经过高斯模糊的上一帧的颜色值 ```s blendFactor = (lastStencil & 0x18) == (currStencil & 0x18) ? blendFactor :1.f; float3 blurredCurrColor //Gaussian blur currColor with 3x3 neighborhood if (blendFactor == 1.f) return blurredCurrColor; ``` Gaussian blur使用的卷积核与$3\times3$相邻像素采样一致
$ \begin{bmatrix} \frac{1}{16} & \frac{1}{8} &\frac{1}{16} \\\\ \frac{1}{8} &\frac{1}{4} &\frac{1}{8} \\\\ \frac{1}{16} &\frac{1}{8} &\frac{1}{16} \end{bmatrix} $ 处理后依旧有1像素厚的鬼影存在 ![](../../images/one_pixel_thick_ghosting.png) 产生原因:
Color history 是线性采样,Stencil history 是点采样,两者在边缘并不相容 解决方法
在stencil buffer里让物体向外扩大1像素 1. 创建输入值为历史帧和stencil buffers的全屏shader 2. 对每一个像素,将它的深度与周围4个相邻像素进行对比 3. 输出深度接近的像素的模板(stencil of pixel) 4. 将扩张的stencil buffer输入TAA 5. 上一帧的stencil应该来自于扩张后 6. 对扩张后的stencil做模板测试(stencil test) 7. 边缘的检测则使用未扩张的版本 ---------------------- 在计算完成后我们需要将目前帧的和历史帧的结果混合到一起 ```s return lerp(historyColor,currColor,blendFactor) ``` 出于平衡模糊和抖动的目的,blendFactor必须是一个动态的值
由UE4在[siggraph 2014]提出的方法可知
当局部对比度低的时候增加
当历史帧在截断值附近或像素的偏移接近子像素时减少
但在这样处理后依旧有模糊的情况残余 ------------------ 为了修复残余的模糊情况,我们可以再加入一个全屏的锐化处理
对一个$3\times3$的相邻像素序列,采取$\begin{bmatrix} 0 &-1 &0 \\\\ -1 &4 &-1 \\\\ 0 &-1 &0 \end{bmatrix}$的权重比例,即 ```s return saturate(center + 4 * center-up-down-left-right) ``` ------------ ###### TAA的性能 主体shader在PS4的GPU上渲染1080p分辨率需要0.8ms 后续处理也存在开销
Motion vector calculation 运动向量计算
Sharpen shader (0.15ms) 锐化效果
Expand stencil shader(0.4ms) 模板扩张 ------------------ [WVP和MVP矩阵的区别](https://gamedev.stackexchange.com/questions/57607/what-is-the-difference-between-a-modelview-projection-matrix-and-world-view-proj) [$ matrix_{objToWorld} $](https://computergraphics.stackexchange.com/questions/1976/how-to-determine-the-object-to-world-matrix#:~:text=The%20Object-To-World%20matrix%20is%20often%20called%20%22model-matrix%22.%20This,it%20and%20the%20math%20works%20out%20pretty%20well.)