通过检测图像中的像素是否处在投影后的三角形中,来赋予像素相应的颜色的属性,是光栅化算法的主要思路。
光栅化
为了实现一个简易的光栅化效果(在屏幕上画出一个三角形),我们需要解决两个问题:
- 首先要找到所有落在三角形投影范围内的像素。
- 为以上位置的像素赋予相应的颜色。
Edge Function可以很好的解决第一个问题,快速判断一个像素是否在三角投影区域内。第二个问题也被成为shading(着色)。
The Edge Function
Edge Function 由 Juan Pineda 在 1988 年发表的一篇名为 A parallel Algorithm for Polygon Rasterization 的论文中提出。
三角形的一边可以看作是分割二维平面的一条线(Figure 1),Pineda 的方法的主要思路是找到一个方程,用它来测试任一给定点 $P(x, y)$ 处于分割线的哪一侧:
- 当点 $P$ 位于分割线的左侧时,方程返回一个负值。
- 当点 $P$ 位于分割线的右侧时,方程返回一个正值。
- 当点 $P$ 正好位于分割线之上时,方程返回0。
于是我们可以发现分别以三角形的三条边作为分割线的话,若某一点 $P(x, y)$ 经过Edge Function测试全为正,即点 $P(x, y)$ 全部位于三角形三边的右侧,那么就可以确定这一点位于三角形内部。
需要注意三角形三边的方向,按照顺时针定义,即三边分别为 v0->v1,v1->v2,v2->v0。
了解了Edge Function的设计思路,下面给出它的公式(关于边$V0->V1$):
$$
E_{01}(P)=(P.x-V0.x)*(V1.y-V0.y)-(P.y-V0.y)*(V1.x-V0.x)
$$
事实上这也是向量 $(P-v0)$ 和 $(V1-V0)$ 叉积的大小值,可以用行列式矩阵表示:
$$
\left[
\begin{matrix}
P.x-V0.x & V1.x-V0.x \\
P.y-V0.y & V1.y-V0.y
\end{matrix}
\right]
$$
不难理解,Edge Function的结果的正负性和向量叉积值的正负性是一致的。
所以判断某一像素是否位于投影三角形内,只需检测由像素中心坐标 $P(x, y)$ 和三角形三边所定义的三个Edge Function的正负性。
$$
E_{01}(P)=(P.x-V0.x)*(V1.y-V0.y)-(P.y-V0.y)*(V1.x-V0.x) \\
E_{12}(P)=(P.x-V1.x)*(V2.y-V1.y)-(P.y-V1.y)*(V2.x-V1.x) \\
E_{20}(P)=(P.x-V2.x)*(V0.y-V2.y)-(P.y-V2.y)*(V0.x-V2.x)
$$
重心坐标
通常我们只能对三角形的三个顶点定义颜色等属性,那么如何确定三角形内部任意一点的颜色?(Figure 4)
假设 $P=\lambda_{0} * V0 + \lambda_{1} * V1 + \lambda_{2} * V2$,且满足 $\lambda_{0} + \lambda_{1} + \lambda_{2} = 1$ 那么 $(\lambda_{0},\lambda_{1},\lambda_{2})$ 就是 $P$ 的重心坐标。它可以表示位于三角形内部(及边界)上的任意一点。
于是我们可以借助重心坐标来对三角形内任意一点进行插值,以获得其颜色 $C_P$ 等其它由顶点定义的属性:
$$
C_P=\lambda_{0} * C_{V0} + \lambda_{1} * C_{V1} + \lambda_{2} * C_{V2}
$$
如何获得任意一点的重心坐标
在三角形情形中,重心坐标也叫面积坐标,因为 $P$ 点关于 $\Delta ABC$ 的重心坐标和 $\Delta PBC, \Delta PCA, \Delta PAB$ 的面积成比例。证明如下。
如图(Figure 4),设 $\Delta ABC$ 三个顶点和原点构成的向量分别为 $pmb{\vec{a}}, \pmb{\vec{b}}, \pmb{\vec{c}}$,$P$ 点和原点构成的向量为 $\pmb{\vec{p}}$。$\Delta PBC, \Delta PCA, \Delta PAB$ 面积之比为 $\lambda_{0}:\lambda_{1}:\lambda_{2}$,且 $\lambda_{0} + \lambda_{1} + \lambda_{2}=1$,设射线 $AP$ 与 $BC$ 交于点 $D$,点 $D$ 和原点构成的向量为 $\pmb{\vec{d}}$,
则:
$$
BD:DC=\lambda_{2}:\lambda_{1}, 从而\quad \pmb{\vec{d}}=\cfrac{\lambda_{1}\pmb{\vec{b}}+\lambda_{2}\pmb{\vec{c}}}{\lambda_{1}+\lambda_{2}} \\
AP:PD=(\lambda_{1}+\lambda_{2}):\lambda_{0}, 故\quad \pmb{\vec{p}}=\cfrac{(\lambda_{1}+\lambda_{2})\pmb{\vec{d}}+\lambda_{0}\pmb{\vec{a}}}{\lambda_{0}+\lambda_{1}+\lambda_{2}} \\
\pmb{\vec{p}}=\lambda_{0}\pmb{\vec{a}}+\lambda_{1}\pmb{\vec{b}}+\lambda_{2}\pmb{\vec{c}}
$$
而 $\Delta PBC, \Delta PCA, \Delta PAB$ 的面积又正好等于 $P$ 点和 $\Delta ABC$ 顶点构成的向量与 $\Delta ABC$ 三边构成的向量的叉积的值的一半。
叉积值已通过 Edge Function 得到,所以重心坐标:
$$
\lambda_{0}=\cfrac{Area(V1,V2,P)}{Area(V0,V1,V2)}=\cfrac{E_{12}(P)/2}{E_{12}(V0)/2}=\cfrac{E_{12}(P)}{E_{12}(V0)} \\
\lambda_{1}=\cfrac{Area(V2,V0,P)}{Area(V0,V1,V2)}=\cfrac{E_{20}(P)/2}{E_{20}(V1)/2}=\cfrac{E_{20}(P)}{E_{20}(V1)} \\
\lambda_{2}=\cfrac{Area(V0,V1,P)}{Area(V0,V1,V2)}=\cfrac{E_{01}(P)/2}{E_{01}(V2)/2}=\cfrac{E_{01}(P)}{E_{01}(V2)}
$$
计算优化
$$
lambda_{0} + lambda_{1} + lambda_{2} = 1 \\
P = lambda_{0} * V0 + lambda_{1} * V1 + lambda_{2} * V2
$$
两公式联立消去 $\lambda_{0}$ 得:
$$
P=V0 + lambda_{1} * (V1-V0) + lambda_{2} * (V2-V0)
$$
$V1-V0$ 和 $V2-V0$ 可以提前计算出来,这样就把计算由三次乘法和两次加法简化到了两次乘法和两次加法。GPU采用了这种优化策略。
拓展
在某些特殊情况下,一个像素可能同时落在两个投影三角形区域内,Figure 6 中的红色像素点显示了这种情况。
top-left rule (Figure 7) 可以用来避免这种重复光栅化三角形边界像素的情况。
由图可知:
- top edge 指的是满足向量 $V[(x+1)%3] - V[x], x=0,1,2$ 的 $y$ 坐标等于 $0$,且 $x$ 坐标大于 $0$ 的边。
- left edge 指的是满足向量 $V[(x+1)%3]-V[x], x=0,1,2$ 的 $y$ 坐标大于 $0$ 的边。
需要特别注意的是,本节都是建立在三角形三边按顺时针方向定义的前提下讨论的。
实例
给定光栅化坐标系下的三角形的三个顶点以及顶点的颜色,渲染出这个三角形。
代码
1 |
|