本文转自:http://blog.csdn.net/xiaosaret/article/details/6698305

需要两个知识点:

1: 线性方程组和行列式(也可理解成矩阵) 克莱姆法则

2: 重心坐标系: P = uV0 + vV1 + wV (u,v,w)为中心坐标,P为焦点,  u≥0 v≥0 w≥0 且 u+v+w = 1

射线和三角形的相交检测是游戏程序设计中一个常见的问题,最典型的应用就是拾取(Picking),本文介绍一个最常见的方法,这个方法也是DirectX中采用的方法,该方法速度快,而且存储空间少。先讲述理论,然后给出对应的代码实现。

关于射线和三角形相交关于射线和三角形相交

理论部分

一个直观的方法

我想大多数人在看到这个问题时,可能都会想到一个简单而直观的方法:首先判断射线是否与三角形所在的平面相交,如果相交,再判断交点是否在三角形内。

判断射线是否与平面相交

判断点是否在三角形内

但是,上面的方法效率并不很高,因为需要一个额外的计算,那就是计算出三角形所在的平面,而下面要介绍的方法则可以省去这个计算。

本文的方法

接下来会涉及到一些数学知识,不过没关系,我会详细解释每一个步骤,不至于太晦涩,只要您不觉得烦就行了,好了开始!

射线的参数方程如下,其中O是射线的起点,D是射线的方向。

关于射线和三角形相交

我们可以这样理解射线,一个点从起点O开始,沿着方向D移动任意长度,得到终点R,根据t值的不同,得到的R值也不同,所有这些不同的R值便构成了整条射线,比如下面的射线,起点是P0,方向是u,p0 + tu也就构成了整条射线。

关于射线和三角形相交

三角形的参数方程如下,其中V0,V1和V2是三角形的三个点,u, v是V1和V2的权重,1-u-v是V0的权重,并且满足u>=0, v >= 0,u+v<=1。

关于射线和三角形相交

确切的说,上面的方程是三角形及其内部所有点的方程,因为三角形内任意一点都可以理解为从顶点V0开始,沿着边V0V1移动一段距离,然后再沿着边V0V2移动一段距离,然后求他们的和向量。至于移动多大距离,就是由参数u和v控制的。

关于射线和三角形相交

于是,求射线与三角形的交点也就变成了解下面这个方程-其中t,u,v是未知数,其他都是已知的

关于射线和三角形相交

移项并整理,将t,u,v提取出来作为未知数,得到下面的线性方程组

关于射线和三角形相交

现在开始解这个方程组,这里要用到两个知识点,一是克莱姆法则,二是向量的混合积。

令E1 = V1 - V0,E2 = V2 - V0,T = O - V0上式可以改写成

关于射线和三角形相交

这个地方写的有点坑爹让哥看了好久才明白

我们把等式 变一下 –Dt + E1u + E2v = T

所以根据克莱姆法则就有:

D = [-D E1 E2]

因为: –D E1 E2都是向量所以这是一个3X3矩阵.

D1 = [T   E1 E2]

D2 = [-D  T  E2]

D3 = [-D  E1  T]

t u v结果就如下:

t =  D1 / D

u = D2 / D

v = D3 / D

所以就是下面的形式

根据克莱姆法则,可得到t,u,v的解分别是

关于射线和三角形相交

将这三个解联合起来写就是

关于射线和三角形相交

根据混合积公式

关于射线和三角形相交

上式可以改写成

关于射线和三角形相交

关于射线和三角形相交

得到最终的公式,这便是下面代码中用到的最终公式了,之所以提炼出P和Q是为了避免重复计算

关于射线和三角形相交

代码部分

理论部分阐述完毕,开始上代码,这份代码来自DirectX SDK中的Demo,名字叫做Picking(拾取),该函数位于文件Pick.cpp的最末尾。这个函数有一个特点,就是判断语句特别多,因为对于一个频繁被调用的函数来说,效率是最重要的,这么多判断就是为了在某个条件不满足时,及时返回,避免后续不必要的计算。

// Determine whether a ray intersect with a triangle
// Parameters
// orig: origin of the ray
// dir: direction of the ray
// v0, v1, v2: vertices of triangle
// t(out): weight of the intersection for the ray
// u(out), v(out): barycentric coordinate of intersection
const Vector3& dir,
   9:                         Vector3& v0, Vector3& v1, Vector3& v2,
float* v)
  11:  {
// E1
  13:      Vector3 E1 = v1 - v0;
  14:  
// E2
  16:      Vector3 E2 = v2 - v0;
  17:  
// P
  19:      Vector3 P = dir.Cross(E2);
  20:  
// determinant
float det = E1.Dot(P);
  23:  
// keep det > 0, modify T accordingly
  25:      Vector3 T;
if( det > 0 )
  27:      {
  28:          T = orig - v0;
  29:      }
else
  31:      {
  32:          T = v0 - orig;
  33:          det = -det;
  34:      }
  35:  
// If determinant is near zero, ray lies in plane of triangle
if( det < 0.0001f )
return false;
  39:  
// Calculate u and make sure u <= 1
  41:      *u = T.Dot(P);
if( *u < 0.0f || *u > det )
return false;
  44:  
// Q
  46:      Vector3 Q = T.Cross(E1);
  47:  
// Calculate v and make sure u + v <= 1
  49:      *v = dir.Dot(Q);
if( *v < 0.0f || *u + *v > det )
return false;
  52:  
// Calculate t, scale parameters, ray intersects triangle
  54:     *t = E2.Dot(Q);
  55:  
float fInvDet = 1.0f / det;
  57:      *t *= fInvDet;
  58:      *u *= fInvDet;
  59:      *v *= fInvDet;
  60:  
return true;
  62:  }

相关文章: