光栅化


光栅化位于渲染管线的 Primitive Assembly 之后,Fragment Shader 之前。而对于 D3D、OpenGL 这类有 Tessellation 和 Geometry Shader 的 API,光栅化则位于 Geometry Shader 之后。本文讨论的是 OpenGL ES 2.0,没有 Tessellation 和 Geometry Shader,所以光栅化位于 Primitive Assembly 之后。

点的光栅化

对于 point 类型的 primitive,光栅化输出的是正方形的 fragments,正方形的中心点位于 (xw,yw),而该正方形边长由 vertex shader 的输出gl_PointSize控制,该值不能无限大,有一个范围,由具体的实现自己定义。除了gl_PointCoord 之外,生成的所有 fragments 的varying数据都是一样的,内置变量gl_PointCoord定义了每个 fragment 位于该矩形内的坐标空间(s,t),分别从左往右地 0 到 1 和从上往下地从 0 到 1。下面的公式可以用来计算 (s,t) 的值:

公式中的 size 即该点的大小gl_PointSize,(xf,yf) 是该 fragment 的窗口坐标,(xw,yw) 是顶点的窗口坐标,顶点的窗口坐标对于所有的 fragments 都一样。例如 (xw,yw)=(200,100),size=50 时,xf​ 和 yf 的范围分别是 [175,225和 [75,125]。

线段的光栅化

线段由一个 line strip、一个 line loop 或一系列单独的 lines 产生。

如果线段和裁剪体相交,则线段会被裁剪到裁剪体的边界,对应的顶点属性和varying数据也会用线性插值重新生成,然后再进行光栅化。

被光栅化的线段宽度可以用LineWidth来控制,线宽具有一定的范围,但通常只能是 1。

光栅化线段首先需要确定该线段是 x-major 的还是 y-major 的,斜率位于 [−1,1]之间的线段是 x-major 的,否则就是 y-major 的。

GL 使用 “diamond-exit” 规则来确定光栅化线段会产生哪些 fragments。对于 center 位于窗口坐标 (xf,yf) 的 fragment,定义一个菱形区域,它是四个半平面的交集:

起点和终点分别是 pa和 pb 的线段会产生哪些与 Rf 相交的 fragments,但是,如果 pb 位于 Rf​ 内,则不会产生 pb 对应的 fragment。

当 pa和 pb 位于 fragment center 时,这些 fragments 的生成方式就会简化成做了一点修改的 Bresenham 算法:在这种描述下生成的线是“半开的”,即最后的 fragment 不会被绘制。这意味着当光栅化一系列相连的线段时,线段之间的公共端点只会被生成一次,而不是两次(如果使用的是 Bresenham 算法就会生成两次)。

然后是计算每个被光栅化的 fragments 的 associated data。假设生成的 fragment 的 center 是 pr=(xd,yd),而两个端点是 pa=(xa,ya) 和 pb=(xb,yb)\mathbf{p}_b = (x_b, y_b)。则设

那么该 fragment 的 associate data f 是

其中的 fa​ 和 fb​ 分别是该线段端点的 associated data,wa​ 和 wb​ 分别是该线段端点的 clip w coordinates。但是,深度值(window z)必须使用线性插值:

多边形的光栅化

多边形由 triangle strip、triangle fan 或一系列单独的 triangles 生成。像点和线段一样,多边形的光栅化也由一些变量控制。

第一部是先确定该多边形是正面的还是背面的,这由该多边形在窗口坐标下的面积的符号决定:

其中的 xwi 和 ywi是 n 顶点多边形的第 i个顶点的窗口坐标,而 i⊕1对 (i+1) 取模。如果顶点顺序是逆时针的,则面积的符号的正,如果是顺时针的,则符号是负的。而如果FrontFace设为CCW,则可以直接使用该符号,如果是CW,则表示该面积的符号应该先取反,然后再使用 OpenGL ES 2.0 规范的 3.5.1 小节对这里是否取反的描述刚好反了,ES 3.0 规范的 3.6.1 小节改回来了。 。完成可能的取反之后,如果符号是正的,则表示该多边形是正面的,否则表示是背面的。例如如果顶点顺序是逆时针,则计算的符号是正的,然后FrontFace设为CCW,则不用取反,直接使用,如果是CW,则要取反,逆时针的符号变成负的,表示逆时针顺序对应的是背面。

可以使用CullFace决定是否对正面或背面进行光栅化。

用于光栅化多边形生成 fragments 的规则被称为 point sampling

生成一个 triangle 内的 fragments 之后,这些 fragments 的 associated data 由 triangle 的重心坐标(barycentric coordinates)得到。重心坐标是三个数字 a,b,ca, b, ca,b,c 的集合,它们的范围都是 [0,1]],且 a+b+c=1。得到重心坐标后,就可以计算该三角形内或边界上的任意点的坐标:

其中 pa​、pb​ 和 pc​ 是该三角形的顶点,而 a,b,c 是:

其中的 A(lmn) 表示的是顶点为 l,m,n 的三角形在窗口坐标下的面积。

将位于 pa,pb,pc​ 的一个 datum(即顶点属性,例如顶点坐标,顶点纹理坐标,顶点法线)分别记为 fa,fb,fc,则位于一个 fragment 的 datum 值 f 为:

其中的 wa,wb,wc​ 分别是 pa,pb,pc 的裁剪坐标的 w分量。a,b,c 必须对应该 fragment center 的精确坐标。

就像线段的光栅化那样,深度值(window z)必须使用线性插值得到:

拖动三角形的顶点,当三角形变得很细时,你会发现光栅化的 fragment 会变得“断断续续”的,这种锯齿被称为 sub-pixel aliasing。可以通过增加光栅化时的采样数来解决,例如 SSAA 或 MSAA。Diving into Anti-Aliasing 是一篇不错的文章,其介绍了锯齿的类型以及对应的抗锯齿办法。

深度值偏移

光栅化多边形时,还可以通过设置额外的状态,计算一个值,对所有 fragments 的深度值进行一定的偏移。调用PolygonOffset(float factor, float units)可以设置这些状态。要使用这个深度值偏移的功能还需要调用Enable来开启POLYGON_OFFSET_FILL

这个功能可以用在渲染两个叠加的面时,对另一个面的深度值进行一定的偏移,避免它们之间的 z-fighting,还可以用在 Shadow Map 中,作为深度值的 bias。

还需要注意的是,即使对深度值做了偏移,但 fragments 的深度值总是被限制在 [0,1] 之间。

无论是点、线段或多边形,光栅化时只对 fragment 的 center 采样了一次,所以都会产生锯齿!有些图形 API 允许在光栅化阶段进行多次


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注