前言

TAA的核心在于如何抗锯齿,而TAAU的核心是性能提升

TAA

TAA这里只会提及实现时的几个重点,重点在后续TAAU部分

Jitter Velocity

  • 为了能和前一帧混合,需要对每一帧的uv进行偏移,偏移范围是当前pixel块范围内——即1

    得到jitter后的uv offset(范围[-0.5, 0.5]),再把offset从uv空间转到NDC空间(范围[-1,1]),转换公式如下:

    offset_{NDC} = \frac{offset_{uv} * 2}{ScreenSize}

    又因为ProjMatrix的第一行第三列、第二行第三列,分别控制x的偏移、y的偏移,因此将offset_{NDC}赋值给ProjMatrix的第一行第三列、第二行第三列即可

  • 当前帧与上一帧的Proj Matrix都需要jitter吗?

    答案是否定的,因为TAA的思想是混合每帧不同的偏移后的渲染图,达到时间换空间的目的。因此,每帧Proj Matrix 都需要jitter以得到offset后的gbuffer,但计算velocity时需要用到非jitter的Pre Proj Matrix、非jitter的Curr Proj Matrix

  • 如何计算动态物体的velocity

    记录动态物体非jitter的curr world matrix、pre world matrix

TAA

  • 消除动态物体边缘的严重撕裂和残影

    • 为什么

    假设,一个人物在运动,他的背后是墙壁且静止。在人物身体边缘部分,因为jitter的原因,某些pixel上一帧可能采样到了静止的墙壁,这一帧采样到了人物,这会导致前景与背景混合,导致背景的颜色渗入前景的边缘,最终导致边缘的残影

    • 如何解决

    为了防止背景混合到前景,可以计算临近的最近depth的uv,用这个uv作为当前Pass的uv

  • 运动或者光影剧烈变化时,颜色闪烁

    • 为什么

    如果是RGB空间作clamp,假设上一帧pixel是极其鲜艳的红色,因为光照变化的原因这一帧pixel颜色变为非常暗淡的蓝色,如果进行clamp,会导致红色的分量都被限制在蓝色范围内,导致颜色闪烁

    • 如何解决

    因为RGB space与亮度有关,而我们只想clamp影响色度(人眼对色度迟钝,对亮度敏感),而非亮度,所以可以将color space变换到YCoCg space

  • Fireflies

    • 为什么

    假设当前history buffer pixel颜色为(1, 1, 1),当前帧因为jitter pixel采样到旁边非常亮的pixel(1000, 1000, 1000),根据混合公式当前帧pixel结果为(1,1,1) * 0.95 + (1000, 1000, 1000) * 0.05 = 50.95,导致Fireflies的出现

    • 如何解决

    执行tone map将亮度压回[0,1],为了性能执行简单的reinhard——\frac{Color}{1.f - Luminance(Color)}。随后混合,混合完后再执行inverse tone map——\frac{Color_{Mapped}}{1 - Luminance(Color_{Mapped})}

  • 鬼影

    • 为什么

    由于TAA混合时,历史帧是占绝大部分的,那么就不能无条件的信息历史帧,动态物体尤其明显,出现明显的残影

    • 如何解决

    一个性能高效的方法是,统计3x3或十字内的pixel color,将历史帧的color限制在统计的color内。虽然这不是物理正确的,但根据图形学第一定律,只要看起来是对的,那它就是对的

    限制的方法有三种,从最早的简单的clamp,到后来的ClipBox、加入统计学的ClipBox

    • clamp

      如下图,统计九个pixel,计算mincolor、maxcolor,将历史帧color clamp在mincolor与maxcolor之间

      这种方法比较省,但效果不算好,因为无脑clamp会产生色偏

    • ClipBox

      根据min Color、max Color,建立一个AABB,mid Color为AABB中心,toEdgeVec为中心点到max color的向量,curr Color为上图AABB外的深蓝色点,计算curr Color到mid Color的向量mid Color,再计算curr Color是否在AABB外部unitVec = abs(toSrcVec / max(toEdgeVec, FLT_EPS)),如果unitVec其中任一分量小于等于1,说明curr Color在AABB内部,大于1在外部

      找出color三个分量中,离AABB最远的记为unit,计算toSrcVec在AABB边缘的交点clipped color,若curr Color在AABB内,则返回mid Color,若curr Color在ABABB外,返回clipped color

      float3 midColor = (minColor + maxColor) * 0.5;
      float3 toEdgeVec = (maxColor - minColor) * 0.5;
      
      float3 toSrcVec = currColor - midColor;
      float3 unitVec = abs(toSrcVec / max(toEdgeVec, FLT_EPS));
      float unit = max(unitVec.x, max(unitVec.y, max(unitVec.z, FLT_EPS)));
      float3 res = lerp(currColor, midColor + toSrcVec * rcp(unit), step(1.0, unit));
      
    • VarianceClip

      VarianceClip在ClipBox的基础上加入了统计学,它统计根据3x3范围内的pixel color,基于9个color计算均值、标准差,若标准差越小,说明范围内的pixel color互相颜色差距越小,AABB自适应变小;若标准差越大,说明范围内的pixel color互相颜色差距越大,AABB自适应变大

      • 标准差公式

      一般的标准差公式:\sqrt{\frac{1}{N} \sum(x_i - u)^2}x_i代表pixel color,u代表平均值

      这种标准差公式可以用但性能不太好会消耗寄存器,需要记录9个pixel color

      这里使用另一个标准差公式,\sqrt{E[x^2] - (E[x])^2}即平方的期望减期望的平方,E[x]代表平均值

      float3 VarianceClipBox(float3 m1, float3 m2, float gamma, float3 preColor)
      {
        float3 mu = m1 / 9;
        float3 sigma = sqrt(abs(m2 / 9 - mu * mu));
        float3 colorMin = mu - gamma * sigma;
        float3 colorMax = mu + gamma * sigma;
      
        float3 p_clip = 0.5 * (colorMax + colorMin);
        float3 e_clip = 0.5 * (colorMax - colorMin) + FLT_EPS;
      
        float3 v_clip = preColor - p_clip;
        float3 v_unit = v_clip.xyz / e_clip;
        float3 a_unit = abs(v_unit);
        float ma_unit = max(a_unit.x, max(a_unit.y, a_unit.z));
      
        float factor = rcp(max(1.0, ma_unit));
        return p_clip + v_clip * factor;
      }
      
  • 动静结合
    • 为什么

    一个固定的weight是没法同时满足动态物体、静态物体的,将历史帧权重设到0.95对于静态物体效果很好,但动态物体会有明显的残影

    • 如何解决

    鸣潮提到的方案是利用motion vector的大小对历史帧的权重控制,当速度越快(motion vector越大),当前帧的比例越高;当速度越慢,历史帧的比例越高

  • TAA带来的模糊

    • 为什么TAA会变模糊

    因为采样历史帧,uv不可能恰好落在pixel中央,GPU会自动执行双线性滤波,随着帧数递增,历史帧不断混合,从而导致模糊

    • 如何解决

    锐化即可,可以采用两种方案,一种消耗质量好但消耗略高——Catmull-Rom(双三次插值),另一种质量略低但消耗低FidelityFX CAS

    FidelityFX CAS需要置于LDR空间——tonemap pass后

性能







2,688 * 1,296分辨率下,耗时0.22ms,ALU、显存都没有瓶颈,L2命中率很高,活跃的warp较多,极少因为分支等待,SM 执行指令的吞吐量适中延迟很低是比较健康的

TAAU

为什么需要TAAU

开胃小菜上齐,重头戏开始!

随着GPU的发展,越来越多的用户开始追求高分辨率、好的渲染效果,但众所周知,好的渲染效果包含很多项,这些基本都与屏幕分辨率相关,效果越好,开销越大,但GPU的发展不太更得上用户的需求,如果用低分辨率的效果能做到高分辨率的效果,就能满足的需求。TAAU为了解决这个问题,提出一种思想——降分辨率渲染光照、阴影,在TAA阶段对低分辨率执行上采样,这样至少可以节省75%的开销,非常不错

什么是TAAU

TAAU的jitter思想与TAA不同,TAAU的每个pixel通过计算低分辨率下它原本的位置(这个过程再去jitter),再在降分辨率的光照阴影渲染图的对应位置去采样,最后安放后原生分辨率的TAAU RT

降采样

对于TAA前非postprocess的rt,如gbuffer、lighting rt,都需要降采样,这里就降采样1/2,随后TAAU后会升采样到原生分辨率

Mipmap bias

  • 问题

    由于gbuffer降采样,在gbuffer中采样贴图时,sample level会选择更高的mipmap level(更模糊),导致贴图变模糊,最终光照渲染也会变模糊

  • 如何解决

    根据down sample size / display size的比值,为sample level计算bias。公式:log_2(\frac{Down Sample Width }{Display Width})

Jitter

  • 问题

    由于gbuffer降采样,jitter以降采样的duv作为单位,会导致画面抖动异常明显

  • 如何解决

    以原生的duv作为单位

核心算法

  • 问题

    由于降采样后,TAAU 3x3 sample不能像非降采样那样,11对应采样

  • 如何解决

    为了弥补这个缺陷,TAA对这部分采用了加权平均+时空滤波组合拳,类似DDGI

    通过还原每次低分辨率下的jiiter uv,基于此uv采样计算当前pixel的中心点,随后基于此中心点计算临近的3x3pixel,以采样位置到jiiter uv的差值作为权重,统计9个权重,执行加权平均

算法流程

  • 通过还原每次低分辨率下的jiiter uv
  • 基于此uv采样临近的3x3pixel
  • 以采样位置到jiiter uv的差值作为权重,统计9个权重,执行加权平均

权重计算

  • 问题

    由于每帧uv都需要还原jitter后的,那么每帧uv位置都不相同,每帧计算3x3pixel离jitter uv的距离也不一样,因此需要根据距离来计算当前pixel的权重

  • 怎么解决

    需要一个公式来自动根据距离来计算权重,UE提出了一个五次多项式拟合曲线

    //UpscaleFactor:原生分辨率 / 降分辨率
    //PixelDelta:采样点到中心点的距离
    float ComputeSampleWeigth(float UpscaleFactor, float2 PixelDelta)
    {
      float u2 = UpscaleFactor * UpscaleFactor;
    
      // 1 - 1.9 * x^2 + 0.9 * x^4
      float x2 = saturate(u2 * dot(PixelDelta, PixelDelta));
      return (0.905 * x2 - 1.9) * x2 + 1;
    }
    

    这个公式将距离限制在[0,1],超过1的清零,先还原高分辨率下采样点到中心点的距离,再计算权重

    我们可视化这个式子,不难发现距离为0权重越高,距离越大,权重越低:

历史帧混合

与上述问题一样,每帧jitter uv都不同且速度可能快、可能慢,离中心采样点可能远可能近,在混合时也需要根据距离来计算历史帧的权重,距离越远应该越倾向当前帧以避免鬼影

效果

这是宽高降采样一半的效果

颜色相近的问题

  • 问题

    VarianceClipBox会更加信任与当前帧颜色相近的历史帧pixel,这就带来一个问题,如果前景和后景的颜色过于相似,在运动时很可能导致ghost

  • 如何解决

    计算两个pixel的深度差,判断深度差是否大于阈值,大于则降低或舍弃历史帧

物体太细的问题

  • 问题

    若某个物体特别细。横向只占一个pixel,在TAA下3x3采样closet uv没有问题,但TAAU是有问题的。由于TAAU会降分辨率,极有可能采样不到

  • 如何解决

    类似AMD CACAO的处理方法,后处理velocity buffer,将离相机最近的velocity膨胀

半透明物体

  • 问题

    由于半透明物体不写depth、velocity,TAAU没法处理半透明物体

  • 如何解决

    将半透明物体记录在一张mask图上,TAAU会跳过这部分,TAAU后用空间滤波处理半透明

性能

可以看到gbuffer、AO等耗时下降了许多,但TAApass也就增加了0.03ms



但SM快达到瓶颈,Warp Can't Launch增加了50%,说明内部计算压力太大,需要瘦身,这就需要细细优化了


他们曾如此骄傲的活过,贯彻始终