前言

传统的PCF通常需要在比较高的采样次数才能得到一个比较好的效果,但因为每个uv都需要执行PCF,开销是比较高的

实时渲染中,经常需要用很低的采样达到一个看起来不错的效果,为了达到这个效果一般需要用到随机+TAA多帧融合,比如HBAO在半球上随机方向时,就会用到

但并不是任何随机算法都可以,好的随机算法产生的噪点十分少且结合了人眼特性几乎察觉不到这些噪点,差的就十分多且特别明显

一种低差异的随机算法

  • White Noise

    这种噪声的效果非常随机,可能会扎堆也可能离得非常远,导致渲染效果不均匀,非常不适合于渲染

  • Poisson Disk


    这种噪声保证每个点之间的距离都大于一个特定值r,从上图不难看出,生成的点十分均匀

    但这种噪声需要比较高的采样次数,每次循环都要用到sin、cos这种高开销函数,且由于随机生成的旋转角度是白噪声的形式,不能配合TAA

  • Sobol低差异序列


    这种噪声符合蒙特卡洛,不管样本多少个,都可以快速、均匀的填满空间

    与Poisson Disk相比,这种噪声收敛非常快,以极低的采样数覆盖采样区域,这正是实时渲染需要的

Sobol Shadow

但直接将sobol用于采样,会导致严重的band,这里笔者用到了blue noise来实现高频雪花,并结合TAA降噪

  • 时域黄金分割率随机
    float2 temporalShift = frac(frameIndex * float2(0.61803398875f, 0.75487766624f));
    
  • 空域蓝噪声随机
    float2 spatialShift = LoadTexture2D(BlueNoiseTexIndex, pixelCoord % blueNoiseDimension.xy).xy;
    
  • sobol采样shadow
    uint sobolX = g_Sobol_256spp_256d[sampleIdx * 256 + 0];
    uint sobolY = g_Sobol_256spp_256d[sampleIdx * 256 + 1];
    float2 baseSobol = float2(sobolX, sobolY) * (1.0f / 256.0f);
    
    float2 samplePoint = frac(baseSobol + shift);
    float2 offset = WarpToDisk(samplePoint);
    float2 sampleUV = shadowPos + offset * shadowMapSize.zw * shadowRadius;
    

3次采样可以得到如下效果:

仅仅100us的耗时

优化

时间上已经到极限了,目前只有从空间上进行优化。Eric Heitz提出了一种算法,通过在屏幕空间将蒙特卡洛误差转化为蓝噪声分布,消除了随机采样的杂乱感,从而提升了渲染质量

他在sobol的基础上,引入了排序、扰乱消除了随机采样的杂乱感,且算法没有任何函数,只用到了乘加位运算,性能非常好
这里采样1spp效果如下:

可以看到效果是能接受的

Reference

https://github.com/unity-grenoble/sampling_bluenoise_sig21


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