详解D3D/OpenGL光栅化(2)

一 前言

上一篇讨论了D3D/OGL如何将Shader输出的图元顶点坐标及属性转化为其所覆盖像素和像素的属性。本篇将继续地讨论光栅化其他步骤相关的细节。包括Face Cull,Fill Mode,Clipping, 透视矫正等。

二 几个概念

Render Target

对应显示存储中的一块数据(Surface),存储着用于画到屏幕上每个像素的颜色、深度等数据。

Viewport

由一个二维矩形区域和一维的z范围构成,表示要显示渲染输出的区域,它是NDC立方体所映射的区域。例如RenderTarget大小为64*64,当设置Viewport左上角为(32,32),大小为32*32时,我们将在Render Target的右下角1/4的区域画图。

详解D3D/OpenGL光栅化(2)_第1张图片

坐标系

D3D和OGL的屏幕坐标系有所区别,D3D将NDC中y的范围[-1,1]映射到Viewport的[Viewport.Top+Viewport.Height, Viewport.Top],例如下图中(假设Viewport区域与Render Target相等)Viewport.Top=0,Viewport.Height=-1,NDC中y=-1映射到屏幕上y=8,y=1映射到屏幕上y=0。

由下图可知,D3D的坐标系原点位于屏幕的左上角,像素的长宽为1,像素中心的坐标小数位均为0.5。

详解D3D/OpenGL光栅化(2)_第2张图片

下图是OGL的坐标系,其坐标原点位于屏幕的左下角(在新的OGL版本中,可通过Clip Control选择坐标原点为左上角或左下角),其余特征与D3D类似。

详解D3D/OpenGL光栅化(2)_第3张图片

透视除法和Viewport变换

Raster从Shader收到的顶点位置坐标属于齐次坐标(或裁剪坐标),在较简单的情况下(可能需要做裁剪,在后文继续做讨论),
透视除法:Raster首先将xyz都除以w,将齐次坐标转化为笛卡尔坐标,这时的坐标系称为正规化设备坐标(NDC, Normalized Device Coordinate)。完整的透视除法还包括将非Const的attribute除以w(在透视矫正部分将解释这样做的原因)
Viewport变换:NDC坐标再经过 Viewport变换得到屏幕坐标,这样NDC立方体将被映射到viewport,这一步实际是通过拉伸偏移,将独立于屏幕的标准化设备坐标映射到实际的屏幕上。以D3D为例:

由于坐标系的差异,OGL为:
详解D3D/OpenGL光栅化(2)_第4张图片

三 Cull, Clipping

1. 视椎体(Viewing Frustum)及Frustum Cull

流水线模拟照相机成像的原理,将三维模型成像到二维屏幕上。一台选好焦距的照相机,将具有一定范围的其视野区域,同样流水线中具有“视椎体”,它由上,下,左,右,近,远6个平面围成,这些平面分别对应该方向所能观察到的范围边界。Raster首先确定一个图元是否完全落于视椎体外面,如果是则直接丢弃该图元(Frustum Cull),因为落在视椎体外面的图元经投影后不会覆盖屏幕上任何像素。

详解D3D/OpenGL光栅化(2)_第5张图片

2. Face Cull&Fill Mode

即使一些图元落在了视椎体内部,也有可能可以被提前丢弃,例如在一些封闭模型中,背面的三角形是看不见的。


容易知道,当模型被拆分后的三角形都具有相同的顶点绕向(即逆时针或顺时针)时,从固定角度观察时,其正面和背面的三角形绕向是相反的。因此,我们通过Area的符号可确定三角形是逆时针(CCW,Counter ClockWise)或顺时针(Counter ClockWise),再结合应用拆分模型中正面对应的绕向(这种对应关系称为Winding),最终确定三角形是否为Back Face。对应地,有些情况下要做Front Face Cull,例如镜头位于封闭物体的内部。利用Frustum Cull和Face Cull提前避免了不必要的渲染,可以提高硬件的性能。

在OGL中,可分别为Front Face和Back Face指定不同的Fill Mode。因此确定三角形Face后,可进一步确定其Fill Mode,Fill Mode包括三种:(1)Point,表示只渲染三角形的三个顶点(2)Wireframe,表示只渲染三条边(3)Solid,表示渲染整个三角形内部区域。

详解D3D/OpenGL光栅化(2)_第6张图片

3. Clipping

3.1 Clipping的意义

当图元通过了Frustum Cull和Face Cull。图元仍可能部分落在视椎体外。D3D/OGLRaster会通过六个面对图元进行裁剪,构造新的图元进行渲染。


需要注意的是,裁剪是在裁剪空间下(投影变换之前)做的,裁剪空间是四维的齐次空间下做的。上图仅是给出一个直观的几何概念,例如在齐次坐标系下做zNear/zFar clip时,分别是把z裁到z=-w和z=w,经过投影变换和viewport transform后才分别得到z=zNear和z=Far的顶点。

3.2 Clipping的必要性

3.2.1 Z clipping的必要性

对于z而言,进行裁剪可保证准确性的,例如下面的例子,在裁剪坐标系中(在裁剪空间系下,z落在视椎体中相当于z落在z=W和z=-w两面围成的区域中),v0落在镜头后面,即z<0,经投影变换后将落在V’0,V1位于视椎体内,投影后落在V’1,这样我们看到的将是区域V’0 V’1。但事实上从镜头向z方向看时,看到的应该在V’1下方。

详解D3D/OpenGL光栅化(2)_第7张图片

如果在投影变换前,对z做裁剪,

详解D3D/OpenGL光栅化(2)_第8张图片

3.2.2 XY Clipping的必要性

经过viewport transform之后,通常z的范围在[0, 1]或[-1,1]中(前者是D3D的范围,后者是OGL的常用范围)。与之不同,x/y的范围可以是任意大的float数,而考虑到精度上限制以及计算的效率等问题,Raster中计算边的方程使用具有具体范围的顶点数,例如D3D10的x/y使用定点格式s15.8(即一个符号位,15个整数位,8个小数位),这样最大的表示范围大约为[-2^15, 2^15],因此,将XY做裁剪可以保证得到这种范围。 

3.3Clipping的简单实现

伪代码:

4 Clipping: Left, Right, Bottom, Top. Example: Left Clipping

For (each two vertices in BufIn)

{

            if (v0_left_out && v1_ left_out)  //here v0,v1 means first and second vertex of the pair

                      Both are outside, continue for the next vertex pair;

  else if (v0_ left_out && v1_ left_in)

             {

      Swap v0 and v1, i.e. regard original v0 as v1 and regard original v1 as v0

                     Interpolate from v0 to v1 for each attribute: 

                      v’ = v0 + s(v1-v0);

                       Put v’ into BufOut

    }

         else if (v0_ left_in && v1_ left_out)

              {

     Interpolate from v0 to v1 for each attribute:

      v’ = v0 + s(v1-v0);

      Put v0 and v’ into BufOut

     }

            else // (v0_ left_in && v1_ left_in)

              Both are inside, Put v0 into BufOut and continue;

    }

 

 

一个例子:

详解D3D/OpenGL光栅化(2)_第9张图片




详解D3D/OpenGL光栅化(2)_第10张图片


详解D3D/OpenGL光栅化(2)_第11张图片



四 透视矫正

理论上,属性插值时用到的权重系数(三角形的重心坐标和线的插值系数)应该通过Eye Space坐标计算。但光栅化所处理的像素位于屏幕上,所使用的权重系数也是在屏幕坐标系下算出的。投影变换并非线性变换,使用上述两种插值权重系数所插值的结果往往是不同的。例如下面的例子


Eye Space中的两点V0,V1以及其连线上的点V,投影到屏幕上分别为V‘0,V’1和V‘。

在Eye Space下,

详解D3D/OpenGL光栅化(2)_第12张图片

而在Screen Space下:

详解D3D/OpenGL光栅化(2)_第13张图片

这样导致插值结果的失真称为“投影畸变”,当包含纹理坐标属性时其导致的失真更加明显,该那么如何处理(x,y)投影变换所引入的非线性的影响呢?

我们看一下OGL的specification,对于三角形,用(a,b,c)表示像素的重心坐标,则插值公式是:

详解D3D/OpenGL光栅化(2)_第14张图片

可以看到,只有z是按照之前所说的方法直接插值的。下面较为复杂的插值公式是考虑了透视矫正的,但它是如何得来的呢?

我们首先看看,固定管线中投影变换和透视除法联合起来对于ZW做了什么:

详解D3D/OpenGL光栅化(2)_第15张图片

投影变换

详解D3D/OpenGL光栅化(2)_第16张图片

详解D3D/OpenGL光栅化(2)_第17张图片

最终我们得到:


由于屏幕空间上的z是NDC空间的线性变换,这里暂且不区分它们,我们集中在插值的准确性上。

接下来,我们看两个重要结论并给出其证明

(1)1/Ze在NDC坐标系中可按线性插值。

假设裁剪空间和NDC空间下的插值系数分别为t和s,根据投影变换的几何特点可知:

详解D3D/OpenGL光栅化(2)_第18张图片

把最后的等式表示成插值的计算式,并带入前面两式的结果,得

整理上面的等式,经过加减法并约去相同因式,可得到

从而

详解D3D/OpenGL光栅化(2)_第19张图片


结论得证。


(2)P/Ze在NDC坐标系中可按线性插值。

详解D3D/OpenGL光栅化(2)_第20张图片

代入结论1,整理得


因此,P/Ze在NDC坐标系中可按线性插值。

至此,让我们再回头看OGL specification的插值公式

对于三角形

详解D3D/OpenGL光栅化(2)_第21张图片

对于线

详解D3D/OpenGL光栅化(2)_第22张图片

我们知道,Raster中为每个插值出z(即存到depth buffer中的z),其实等于A/Ze+B,由结论1知,1/Ze可在屏幕坐标系下线性插值。

有结论2知,对于普通的属性,可以在屏幕坐标系下插值得到P/w(上文提到,透视除法时会将非Const属性除以w)。但我们最终需要的实际上是P!注意到,1/w也是可以在屏幕坐标下线性插值的,因此可以同时先插值得到每个像素的1/w,然后将得到的P/w除以1/w。

在某种程度上,zw其实是顶点的特殊的属性。

五 结语

本篇继续覆盖光栅化整体经过的步骤,但并未考虑MSAA的情况,后续会继续做总结。

你可能感兴趣的:(详解D3D/OpenGL光栅化(2))