在OpenGL中,指定三角形图元的顶点顺序很重要。譬如,我们可以使用glFrontFace指定顺时针还是逆时针为正面,然后通过glCullFace不绘制正面或者背面.
但你有没有想过,OpenGL是怎么判断三角形的顶点顺序是顺时针还是逆时针呢?
二维还是三维
首先我们先明确一下,culling这个阶段发生在viewport transformation之后,rasterization之前. 三维顶点经过MVP变换->clipping->透视除法->NDC->viewport transformation,得到的是屏幕坐标系下的二维顶点.
这样事情变得稍微简单了一些,我们只需要考虑三个二维顶点的顺序.
根据面积判断顶点顺序
多边形的面积公式为:\(a = \frac{1}{2}\sum_{i=0}^{n-1}x_{i}y_{i\oplus 1} - x_{i\oplus 1}y_{i}\)
其中 \(i\oplus 1\) 表示 \((i+1)\) mod \(n\),用C语言描述:(i+1)%n
.
将此公式应用到三角形,得:\(a = \frac{1}{2}(x_{0}y_{1} - x_{1}y_{0} + x_{1}y_{2} - x_{2}y_{1} + x_{2}y_{0} - x_{0}y_{2})\),作图来解释一下这个公式.

如图所示,\(\Delta ABC\)的面积,可以看作\(\Delta AOC\)的面积加上\(\Delta BOC\)的面积,然后减掉\(\Delta AOB\)的面积. 即:
\(S_{\Delta ABC} = -S_{\Delta AOB} + S_{\Delta BOC} + S_{\Delta AOC}\)
而三角形的面积恰恰是两条邻边向量叉乘的一半(带有正负号). 故有:
\(-S_{\Delta AOB} = \frac{1}{2}(\vec{OA} \times \vec{OB}) = \frac{1}{2}(x_{0}y_{1} - x_{1}y_{0})\)
\(S_{\Delta BOC} = \frac{1}{2}(\vec{OB} \times \vec{OC}) = \frac{1}{2}(x_{1}y_{2} - x_{2}y_{1})\)
\(S_{\Delta AOC} = \frac{1}{2}(\vec{OC} \times \vec{OA}) = \frac{1}{2}(x_{2}y_{0} - x_{0}y_{2})\)
将三者相加,就相当于四边形\(OBCA\)的面积减去\(\Delta AOB\)的面积,恰好就是\(\Delta ABC\)的面积,且这个面积是正的. 图示的三角形是顶点顺序是逆时针顺序. 同学们可以自行验证一下. 把顺序变成顺时针时,得到的面积是逆时针面积的相反数.
所以通过三角形面积的正负号判断其顶点顺序是可行的.
多边形面积
凸多边形应用上述公式也是可行的,在此不再赘述. 下面我们主要看一下凹多边形的面积是否也能满足此公式.

我们求的是凹多边形\(ABCD\)的面积. 纯色的三角形面积总能被公式叉乘运算得到的两个三角形面积抵消掉. 例如\(S_{\Delta FOB}\)可以通过\(S_{\Delta AOB}\)和\(S_{\Delta OBC}\)抵消掉,\(S_{\Delta CGD}\)可以通过\(S_{\Delta ODA}\)和\(S_{\Delta ODC}\)抵消掉. 所有的纯色三角形面积抵消之后,得到的就是凹多边形的面积.
小结
- 通过\(a = \frac{1}{2}\sum_{i=0}^{n-1}x_{i}y_{i\oplus 1} - x_{i\oplus 1}y_{i}\)来求多边形的面积,此面积有正负号
- 通过面积的正负号来判断多边形顶点的顺序是顺时针还是逆时针
- 上述公式也可以应用到凹多边形