图形学基础算法介绍

本文将着重搜集图形学基础中的一些容易被忽视的细节,从而避免因此导致的效果异常问题。

渲染相关

  • Alpha to coverage(A2C)[3,4,7]

由于MSAA只能处理几何边缘,因此在MSAA中使用Alpha-Test绘制的物体在Alpha渐变的边缘的锯齿就无法被平滑,这个问题有如下三种解决方案:

  1. 通过增加顶点的方式将alpha边缘转换为几何边缘,这种方案会导致几何数据增加,代价比较高
  2. 使用alpha-blend替换alpha-test,这种方案需要对模型按照从后到前进行排序,费时费力且效果可能还会有问题
  3. 通过硬件辅助实现的A2C进行处理,成本低,效果好。

在MSAA的实现中,硬件会为每个fragment下的每个sample各分配一个bit用作mask(比如4x MSAA的话,每个fragment对应的mask就是4bits的),这个mask可以称之为coverage mask(这个mask是在Rasterization阶段输出的,而在开启了A2C功能后,在PS中会根据alpha值对此值进行修正),在后续进行blend的时候会用于判断当前sample是否需要更新的依据。

在开启A2C的时候,硬件会将当前shading pixel的alpha数值转化为coverage,之后根据coverage对应的mask(比如4x MSAA, coverage 87-100% 的mask为 1111, 62-87% 为 0111, 37-62% 为 0011, 12-37% 为 0001,0-12% 为 0000)对当前像素中的sample进行覆盖写入。这里需要注意的是早期硬件的coverage mask是通过固定函数实现的,但当代显卡却是可以通过PS对coverage mask进行手动处理。

这里需要注意的是,A2C应该只用于Alpha-Test物件,Alpha-Blend以及不透明物件都不应该使用,否则徒增消耗。

使用A2C实现的MSAA其表现比FSAA效果更好,因为不会出现由于贴图缩小导致的闪烁(不过贴图放大器效果可能会更模糊,这是alpha-blend方案的通病)

  • Order-Independent Tranparency(OIT)

为了得到正确的渲染效果,半透物件渲染常见的做法是将物件按照从后往前的顺序进行排序,并按顺序进行渲染。

这种做法的缺陷在于排序比较费时,且对于存在深度穿插的物件而言,也不能完美解决问题。为了应对这两个问题,大家纷纷尝试给出一种OIT渲染方案,通过一种顺序无关的权重计算方式来对半透物件进行累加。为了对权重进行归一化,通常需要将权重累加到一个单独的buffer中(也就是需要MRT的支持),之后通过一个单独的pass对半透物件的渲染结果(即需要将半透物件渲染到一个单独的buffer中)进行resolve(其实就是权重归一化)。

OIT有多种实现方式,这里给出一种Weighted Blended Order-Independent Transparency方案作为示例,在这个方案中,权重的计算与当前待写入像素的depth有关(距离相机越近,权重越大,这种做法是可以理解的),在PS计算的时候,根据像素的depth以及opacity计算对应的权重weight,之后将当前待写入的color数据累加到半透color buffer中(float4(color* opacity, opacity) * weight),并将weight写入对应的半透权重buffer中。之后按照OIT的通用逻辑进行resolve之后与不透明buffer结果相叠加得到最终结果。

  • MSAA Sample Pattern

最近读文章看到说,MSAA Sample Pattern基本已经成为业界标准,即不论何种机型,相同倍率的Sample Pattern基本上都是一致的,突然很好奇,在不同的倍率下的sample pattern到底是怎样的,这个问题MJP的DangerZone已经做了解答。

基本实现原理是通过绘制一个高密度的grid,而gird中的顶点数据存放的是这个顶点的相对于grid的起始位置信息,在MSAA作用下,被grid的quad所覆盖的sample会有数据写入,之后将sample数据逐个输出就能够得到每个sample的位置信息了。原理非常简单,虽然结果不是百分百精确,但是大致够用,如果希望得到更高精度的结果,还可以通过增加grid的尺寸做到,这里就直接借用其工具输出各个倍率下的Sample Pattern:

1x
2x
4x
8x

D3D 10.1以后,API规定了各硬件必须遵循的MSAA sample pattern,其结果与上面给出的结果中接近一致:

贴图相关

  • 贴图采样点位置

贴图采样中,将UV坐标转换到贴图空间(即从[0, 1]浮点数转换成[0, TexWidth/TexHeight]的整数)之后,整数UV(或者说ST)对应的实际是每个texel的中心,以D3D为例,[0, 0]对应的实际上是贴图中左上角的texel的中心位置,而非贴图左上角的原点。如果在实践过程中发现贴图采样的结果距离自己的预期存在轻微的偏移,那么就是这个原因导致了,具体修正方法为将ST坐标减去0.5。[1]

  • Mipmap相关[1][2]

    • mip级别选取的原理:物体距离相机越近,mipmap级别越低(贴图精度越高),相邻像素之间的UV差距应该越小,因此可以根据相邻像素对应的UV差距来判定当前像素所对应的mipmap级别,这个操作通常是通过dx/dy来实现的(这一步通常是硬件自动完成)。
    • 使用mipmap虽然会导致所使用的贴图大小有大约1/3尺寸的增幅,但是其优点完全可以覆盖因此导致的损失:不但有助于减轻采样高精度贴图导致的高频颜色跳变(这其实是采样频率不足导致的重建失败而出现的锯齿感),同时还有助于减轻贴图采样所需要的带宽(贴图访问会有一个cache机制,在访问某个像素时,会同步将周边的像素一并加载到显存。采用合适的mipmap,有助于提升cache的效率,避免频繁的cache miss,从而降低带宽需要)。
    • 不是所有的贴图都应该使用mipmap:
      • 不需要进行filtering的贴图(比如索引、深度等非image信息)不应该使用mipmap
      • 不会因为相机远近而缩放的贴图(比如UI所使用的的贴图等)不应该使用mipmap
  • 贴图过滤
    目前常用的贴图过滤(filtering)方法有以下几种,按照顺序,效果与性能消耗逐渐提升:

    • point filtering,点采样,无任何过滤,相当于直接采取贴图上最近的texel作为输出结果(一次贴图采样)
    • bilinear filtering,双线性采样,会根据uv坐标的小数部分对相邻的四个texel进行采样并加权平均(通常由硬件自动完成),效果相对点采样有极大提升,锯齿感得到较好的抑制,但是图像也会相对模糊。(四次贴图采样)
    • trilinear filtering,三线性采样,对计算得到的mipmap层级保留其小数部分,据此增加一层mipmap级别的过滤处理,即同时对相邻两层mipmap应用bilinear filtering,之后根据mipmap小数部分对结果进行加权平均(八次贴图采样)
    • anisotropic filtering,各向异性采样,直接按照x/y方向上的最大梯度来计算mipmap级别会导致一个狭长的物体在屏幕上的效果较为模糊,为了能同时兼顾两个方向上的梯度变化,通常需要通过各向异性采样来解决。各向异性采样算法稍微复杂一点,需要将屏幕空间的像素按照短轴(而非普通mipmap算法中按照长轴)计算的mipmap层级投影到贴图空间,之后得到多个对应的texel sample,在每个sample上应用trilinear filtering并加权平均得到最终的结果,具体可以参考使用最广的FAST(Footprint Area Sampled Texturing)算法实现。

参考文献

1. 图形学底层探秘 - 纹理采样、环绕、过滤与Mipmap的那些事
2. Do Use Mipmapping
3. MSAA and alpha-to-coverage
4. Alpha to coverage - Humus
5. Weighted Blended Order-Independent Transparency
6. 乱弹纪录II:Alpha To Coverage
7. UE426终于实现了Alpha to Coverage

你可能感兴趣的:(图形学基础算法介绍)