射线与三角形相交算法(源于DirectX SDK)
算法来自:DirectX SDK, Picking程序示例, Pick.cpp文件。
此算法速度快、精度也高。
算法原理¶
射线参数方程¶
射线的参数方程如下,其中,R为射线终点,O是射线的起点,D是射线的方向:
\[
R = O + Dt
\]
三角形参数方程¶
三角形的参数方程如下,其中
- \(P\)表示三角形上的任意一点
- \(V_0\),\(V_1\)和\(V_2\)是三角形的三个点
- \(u\),\(v\)是\(V_1\)和\(V_2\)的权重,\(1-u-v\)是\(V_0\)的权重
- 满足\(u>=0,v>=0,v+v<=1\)
\(P = (1-u-v)V_0+uV_1+vV_2\)
确切的说,上面的方程是三角形及其内部所有点的方程
- 因为三角形内任意一点都可以理解为从顶点\(V_0\)开始
- 沿着边\(V_1V_2\)移动一段距离
- 然后再沿着边\(V_0V_2\)移动一段距离
- 然后求他们的和向量
- 至于移动多大距离,就是由参数\(u\)和\(v\)控制的
联立方程组¶
于是,求射线与三角形的交点也就变成了解下面这个方程
- 其中\(t\),\(u\),\(v\)是未知数,其他都是已知的
\(O+Dt=(1-u-v)V_0+uV_1+vV_2\)
移项并整理,将t,u,v提取出来作为未知数,得到下面的线性方程组
\[\begin{bmatrix} -D&V_1-V_0&V_2-V_0 \end{bmatrix}
\begin{bmatrix} t\\u\\v \end{bmatrix}
=
O-V_0
\]
解方程组¶
现在开始解这个方程组,这里要用到两个知识点,一是克莱姆法则,二是向量的混合积。
令E1 = V1 - V0,E2 = V2 - V0,T = O - V0上式可以改写成
\[
\begin{bmatrix} -D&E_1&E_2 \end{bmatrix}
\begin{bmatrix} t\\u\\v \end{bmatrix}
=
T
\]
克莱姆法则¶
根据克莱姆法则,可得到t,u,v的解分别是
\[t=
\cfrac{1}{\begin{vmatrix}-D&E_1&E_2\end{vmatrix}}
\begin{vmatrix}
T&E_1&E_2
\end{vmatrix}
\]
\[u=
\cfrac{1}{\begin{vmatrix} -D&E_1&E_2 \end{vmatrix}}
\begin{vmatrix}
-D&T&E_2
\end{vmatrix}
\]
\[v=
\cfrac{1}{\begin{vmatrix} -D&E_1&E_2 \end{vmatrix}}
\begin{vmatrix}
-D&E_1&T
\end{vmatrix}
\]
将这三个解联合起来写就是
\[\begin{bmatrix} t\\u\\v \end{bmatrix} =
\cfrac{1}{\begin{vmatrix} -D&E_1&E_2 \end{vmatrix}}
\begin{vmatrix}
T &E_1 &E_2 \\
-D &T &E_2 \\
-D &E_1 &T \\
\end{vmatrix}
\]
向量混合积¶
根据混合积公式
\[\begin{vmatrix}
a&b&c
\end{vmatrix}
=a \times b \bullet c
=-a \times c \bullet b
\]
上式可以改写成
\[\begin{bmatrix} t\\u\\v \end{bmatrix} =
\cfrac{1}{\begin{bmatrix} D \times E_2 \bullet E_1 \end{bmatrix}}
\begin{bmatrix}
T \times E_1 \bullet E_2 \\
D \times E_2 \bullet T \\
T \times E_1 \bullet D \\
\end{bmatrix}
\]
令
\[
P=D \times E_2 \\
Q=T \times E_1
\]
最终的计算公式¶
得到最终的公式,这便是下面代码中用到的最终公式了,之所以提炼出P和Q是为了避免重复计算
\[\begin{bmatrix} t\\u\\v \end{bmatrix} =
\cfrac{1}{\begin{vmatrix} P \bullet E_1 \end{vmatrix}}
\begin{vmatrix}
Q \bullet E_2 \\
P \bullet T \\
Q \bullet D \\
\end{vmatrix}
\]
代码¶
bool intersects(const Ray& ray, const Triangle& triangle,
Vector3D* intersectionPnt = nullptr)
{
const auto& O = ray.getStart();
const auto& dir = ray.getDirection();
const auto& A = triangle.A();
const auto& B = triangle.B();
const auto& C = triangle.C();
// E1
auto E1_AB = B - A;
// E2
auto E2_AC = C - A;
// P
auto P = dir ^ E2_AC;
// determinant
auto det = E1_AB * P;
// keep det > 0, modify T accordingly
Vector3D T;
if (det > 0)
{
T = O - A;
}
else
{
T = A - O;
det = -det;
}
// If determinant is near zero, ray lies in plane of triangle
//射线与三角形所在平面平行
if (det < 0.0001f)
{
return false;
}
// Calculate u and make sure u <= 1
auto u = T * P; // barycentric coordinate of intersection
if (u < 0.0 || u > det)
{
return false;
}
// Q
auto Q = T ^ E1_AB;
// Calculate v and make sure u + v <= 1
auto v = dir * Q; // barycentric coordinate of intersection
if (v < 0.0 || u + v > det)
{
return false;
}
// Calculate t, scale parameters, ray intersects triangle
auto t = E2_AC * Q; // weight of the intersection for the ray
double inv_det = 1.0f / det;
t *= inv_det;
u *= inv_det;
v *= inv_det;
if (t < 0)
{
//射线反方向上的交点
return false;
}
if (intersectionPnt)
{
*intersectionPnt = O + dir * t;
}
return true;
}