游戏中的2d数学-判断一个点是否在三角形内部

资料参考

https://www.scratchapixel.com/lessons/3d-basic-rendering/rasterization-practical-implementation/rasterization-stage

题外话
scratchapixel的文章都很不错,缺点是稍啰嗦,啃起来比较慢。
老外的教程都是怕别人看不懂,一个概念用2种方法说2遍的那种。

下面是总结:

首先我们先来判断一个点在直线的左边还是右边

设直线起点和终点分别是p0和p1,点为p

我们用p1-p0 得到直线的向量 \(\vec l\)
用p-p0得到关于p点的向量 \(\vec p\)

然后对2个向量使用叉乘:

\(\vec l \times \vec p\)

利用2d向量的叉乘性质,在右手坐标系中
如果点p在直线的右边,叉乘结果>0
如果点p在直线的左边,叉乘结果<0
如果点p在直线上,叉乘结果=0

对三角形来说,利用这个原理,对三角形的三条边分别叉乘
如果三个结果都>0或者都<0,则点在三角形内部

以下是对scratchapixel关于点在三角形内部判断的节选翻译:

边界函数

之前已经提到过,得到点是否和三角形重合有多重方法。列出更古老的方法确实是个不错的主意,不过这个课程中,我们只说明当今最普遍的方法。这个方法被Juan Pineda 在1988年的论文《"A Parallel Algorithm for Polygon Rasterization》中被提出。

在我们开始细究Pineda的技术之前,我们先描述一下算法的原理。我们可以说,一个三角形的边可以看做是一条把一个2d平面分成2个部分的直线。Pineda的算法的原理是找到一个函数,他称之为边界函数,这样我们可以测试给定一个点在直线的哪一边(图二的点p)。点在直线的左边,则这个函数返回负数,右边返回正数,恰好在直线上则返回0。

原文的图2

在图2中,我们用这个函数对三角形的第一条边(用顶点v0和v1定义。注意2者的先后顺序非常重要)进行检测。如果我们对另外2条边也用相同的方法,我们可以清晰的看到一个区域(白色部分),这个区域内的所有点都是正值(图3)

原文的图3

如果我们取这个区域内的任意一点,我们就能发现,这个点位均为于三条边的右侧。如果P是在一个像素中心的一个点,我们就可以使用这个方法判断像素是否和三角形重叠。假设对于这个点,我们发现三角形的所有三条边均返回正值,那么该像素被三角形包含(或者与某条边重合)。Pinada 的这个方法正巧是线性的,这就意味着这个方法可以被增量计算,这一点我们待会再讲。

既然我们已经理解了原理,让我们揭示下这个函数是什么。边界函数被定义为(边界用顶点v0和v1来定义):

\[E01(P)=(P.x−v0.x)∗(V1.y−V0.y)−(P.y−V0.y)∗(V1.x−V0.x) \]

就像论文中说的,这个函数有用的属性是这个值反映了点(x,y)相对于V0和V1定义的边的位置:

E(P) > 0,P在直线的右侧
E(P) = 0,P恰好在直线上
E(P) < 0,P在直线的左侧

实际上这个函数等价于数学上向量(v1-v0)和向量(P-V0)之间叉乘的结果。我们也可以把这2个向量用矩阵的形式写出(用矩阵描述不涉及其他方面,仅仅是为了书写起来更简洁)。

\[\left[ \begin{matrix} (P.x−V0.x)&(V1.x−V0.x)\\ (P.y−V0.y)&(V1.y−V0.y) \end{matrix} \right] \]

如果我们令\(A=(P−V0)\),$ B=(V1−V0)$,那么我们可以把上面的形式写成关于A和B的2X2矩阵

\[\left[ \begin{matrix} A.x&A.y\\ B.x&B.y \end{matrix} \right] \]

计算这个矩阵的行列式:

\(A.x∗B.y−A.y∗B.x\)

如果你把A和B用向量 (P-V0)和 (V1-V0) 替换,得到:

\((P.x−V0.x)∗(V1.y−V0.y)−(P.y−V0.y)∗(V1.x−V0.x)\)

你发现,这个和上面定义的边界函数是一样的。换句话说,这个边界函数也可以被看做是有2D向量(P-v0)和 (v1-v0) 构成的2X2矩阵的行列式,或者是 (P-V0) 和 (V1-V0)这2个向量之间的叉积。事实上2个向量的行列式和叉乘的大小都有相同的集合解释。

当我们观察3d向量之间(图4)的叉乘的时候,我们可以更容易理解到底发生了什么。在3D空间中,叉乘结果是返回另一个向量,该向量和另外2个源向量都垂直(或者说正交)。但就像你在图4上看到的那样,这个正交向量的大小随着2个向量之间的朝向而发生改变。当向量A(红色)和向量B(蓝色)同向或者反向时,叉乘结果向量C(绿色)的值是0。向量A在坐标(1,0,0)并且固定不动。B的坐标是(0,0,-1),那么绿色向量C的坐标是(0,-1,0)。假设我们想要找出C的“带正负的”大小,我们会发现他等于-1。另一个例子,当B的坐标等于(0,0,1),那么C的坐标是(0,1,0)并且“带正负的”大小等于1。第一个例子中,符号大小是负数,第二个例子是正数。

未完待续...

posted @ 2020-07-02 18:25  jeoyao  阅读(443)  评论(0编辑  收藏  举报