1)如果你只是想知道直线是否与三角形相交(而不需要实际的交点):
设p1,p2,p3
为你的三角形。
在直线上选取两个点q1,q2
,这两个点要在两个方向上离得很远。
令SignedVolume(a,b,c,d)
表示四面体a,b,c,d的带符号体积。
如果SignedVolume(q1,p1,p2,p3)
和SignedVolume(q2,p1,p2,p3)
有不同的符号,并且SignedVolume(q1,q2,p1,p2)
、SignedVolume(q1,q2,p2,p3)
和SignedVolume(q1,q2,p3,p1)
都有相同的符号,则存在交点。
SignedVolume(a,b,c,d) = (1.0/6.0)*dot(cross(b-a,c-a),d-a)
2) 如果您想要交集,当第1步测试通过时
以参数形式编写线的方程:p(t) = q1 + t*(q2-q1)
编写平面方程:dot(p-p1,N) = 0
其中
N = cross(p2-p1, p3-p1)
将p(t)
注入平面方程中:dot(q1 + t*(q2-q1) - p1, N) = 0
展开得:dot(q1-p1,N) + t dot(q2-q1,N) = 0
推导得:t = -dot(q1-p1,N)/dot(q2-q1,N)
交点为:q1 + t*(q2-q1)
3)一种更加高效的算法
我们现在研究的是以下算法:
Möller和Trumbore,“快速,最小存储光线-三角形相交”,Journal of Graphics Tools,vol. 2,1997,p. 21–28
(也可参见:https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm)
该算法最终比1)和2)中的算法更简单(指令较少),但稍微复杂一些。让我们一步步推导。
符号:
O
= 光线起点,
D
= 光线方向向量,
A,B,C
= 三角形的顶点
光线上的任意点P可以表示为:P = O + tD
三角形上的任意点P可以表示为:P = A + uE1 + vE2
其中 E1 = B-A
,E2 = C-A,u≥0,v≥0
且 (u+v)≤1
将P的两个表达式代入得:
O + tD = A + uE1 + vE2
或者:
uE1 + vE2 -tD = O-A
矩阵形式:
[u]
[E1|E2|-D] [v] = O-A
[t]
(其中[E1 | E2 | -D]是以E1,E2和-D作为其列的3x3矩阵)
使用Cramer公式解决:
[a11 a12 a13][x1] [b1]
[a12 a22 a23][x2] = [b2]
[a31 a32 a33][x3] [b3]
提供:
|b1 a12 a13| |a11 a12 a13|
x1 = |b2 a22 a23| / |a21 a22 a23|
|b3 a32 a33| |a31 a32 a33|
|a11 b1 a13| |a11 a12 a13|
x2 = |a21 b2 a23| / |a21 a22 a23|
|a31 b3 a33| |a31 a32 a33|
|a11 a12 b1| |a11 a12 a13|
x3 = |a21 a22 b2| / |a21 a22 a23|
|a31 a32 b3| |a31 a32 a33|
现在我们得到:
u = (O-A,E2,-D) / (E1,E2,-D)
v = (E1,O-A,-D) / (E1,E2,-D)
t = (E1,E2,O-A) / (E1,E2,-D)
其中(A,B,C)表示以A,B,C为列向量的3x3矩阵的行列式。
现在我们使用以下恒等式:
(A,B,C) = dot(A,cross(B,C)) (develop the determinant w.r.t. first column)
(B,A,C) = -(A,B,C) (swapping two vectors changes the sign)
(B,C,A) = (A,B,C) (circular permutation does not change the sign)
现在我们得到:
u = -(E2,O-A,D) / (D,E1,E2)
v = (E1,O-A,D) / (D,E1,E2)
t = -(O-A,E1,E2) / (D,E1,E2)
使用:
N=cross(E1,E2);
AO = O-A;
DAO = cross(D,AO)
我们最终得到以下代码(这里使用GLSL编写,易于转换为其他语言):
bool intersect_triangle(
in Ray R, in vec3 A, in vec3 B, in vec3 C, out float t,
out float u, out float v, out vec3 N
) {
vec3 E1 = B-A;
vec3 E2 = C-A;
N = cross(E1,E2);
float det = -dot(R.Dir, N);
float invdet = 1.0/det;
vec3 AO = R.Origin - A;
vec3 DAO = cross(AO, R.Dir);
u = dot(E2,DAO) * invdet;
v = -dot(E1,DAO) * invdet;
t = dot(AO,N) * invdet;
return (det >= 1e-6 && t >= 0.0 && u >= 0.0 && v >= 0.0 && (u+v) <= 1.0);
}
当函数返回
true
时,交点由
R.Origin + t * R.Dir
给出。在三角形中,交点的重心坐标为
u
、
v
和
1-u-v
(用于Gouraud着色或纹理映射)。好消息是,你可以免费获得它们!
请注意,该代码是无分支的。它被一些ShaderToy上的我的着色器使用。
链接如下:
inside()
函数是做什么用的? - Fureeish