三角形1:
Ps:三角形可拓展至任意网格
推导过程:
射线: (O原点,D方向,t距离)
三角形:(V123 顶点 uvw 重心坐标)
求相交点
等价于矩阵运算
设V1-V0=E1 V2-V0=E2 O-V0=T
根据克莱姆法则,三个值分别为
整合在一起就是
根据混合积公式
设P=DxE2 Q=TxE1
代码(hlsl):
//三角形的相交性检测 双面 uvw 重心坐标 w=1-u-v t=距离
static const float EPSILON = 1e-8;
bool IntersectTriangle_01(Ray ray, float3 vert0, float3 vert1, float3 vert2,
inout float t, inout float u, inout float v,inout float toward)
{
//E1 E2
float3 edge1 = vert1 - vert0;
float3 edge2 = vert2 - vert0;
//P=DxE2
float3 pvec = cross(ray.direction, edge2);
float det = dot(edge1, pvec);
//背面det<0 正面>0 在三角形内=0
if (det < EPSILON&&det >-EPSILON) return false;
toward=sign(det);
// 1/(DxE2)·E1
float inv_det = 1.0f / det;
//T
float3 tvec = ray.origin - vert0;
//u=invdet *(P·T)
u = dot(tvec, pvec) * inv_det;
//如果三角形内的点,重心坐标一定在[0,1]
if (u < 0.0 || u > 1.0f) return false;
//Q=TxE1
float3 qvec = cross(tvec, edge1);
//v=invdet*(Q·D)
v = dot(ray.direction, qvec) * inv_det;
if (v < 0.0 || u + v > 1.0f) return false;
//t=Q·E2
t = dot(edge2, qvec) * inv_det;
return true;
}
关于正反面的判断
= D为射线方向,E1xE2为三角形法线方向
对于:
>0 =>cos<0=>夹角>90°=>正面相交=0 =>cos=0=>夹角=90°=>平行
<0 =>cos>0=>夹角<90°=>反面相交
三角形2:
推导过程:
射线: (O原点,D方向,t距离)
三角形:(V123 顶点 uvw 重心坐标)
① 确定射线与三角形所在平面相交,并求出交点。
平面: (N为三角形法线)
表示交点在平面内,带入射线
DL≠0则表示有交点,从而得到交点P
② 计算交点相对于三角形的重心坐标,从而判断是否在三角形内。
交点P带入三角形
设 Ep=P-V0 E1=v1-v0 E2=v2-v0
两边乘E1 E2得到二元一次方程
矩阵形式
求解
bool IntersectTriangle_02(Ray ray, float3 vert0, float3 vert1, float3 vert2,
inout float t, inout float u, inout float v,inout float toward)
{
//E1 E2
float3 edge1 = vert1 - vert0;
float3 edge2 = vert2 - vert0;
float3 normal=cross(edge1,edge2);
//1、检测平面求交点
float4 plane=float4(normal.xyz,-dot(normal,vert0));
float ld=dot(plane,float4(ray.direction,0.0f));
if(ld<EPSILON&&ld>-EPSILON) return false;
t=-dot(plane,float4(ray.origin,1.0f))/ld;
//正反检测
toward=sign(-dot(normal,ray.direction));
//Ep
float3 edgep=ray.origin+ray.direction*t-vert0;
//2、计算重心坐标
float e1e2=dot(edge1,edge2);
float e12=dot(edge1,edge1);
float e22=dot(edge2,edge2);
float e1p=dot(edge1,edgep);
float e2p=dot(edge2,edgep);
float invDet=1.0f/(e12*e22-e1e2*e1e2);
u=invDet*(e1p*e22-e2p*e1e2);
if (u < 0.0 || u > 1.0f) return false;
v=invDet*(-e1p*e1e2+e2p*e12);
if (v < 0.0 || u + v > 1.0f) return false;
return true;
}
立方盒:
默认在对象空间计算,需要将光线转至立方盒的对象空间
射线: (O原点,D方向,t距离)
立方体:(由六个面组成)
推导过程:
① 寻找可能正面相交的平面(最多三个)
通过光线方向的三个分量判断各自两个面的相交情况,以x轴为例:
:光线与平面相交
:光线与平面相交
:两个面都不相交
② 计算与平面的焦点,得到最近的相交点(在矩形内部)
将带入各个面,以为例
可以确定焦点的x值为,带入R(t)推出t值
然后根据t得到交点的另外两个分量
如果该交点另外两个分量满足下面两个条件,则该点就是最近的相交点,不用再考虑其他平面了。
代码:
#define ISPOINT(name,c1,c2) name.c1>=0&&name.c1<=cubeSize.c1&& name.c2>=0&&name.c2<=cubeSize.c2
//立方体 对象空间
bool IntersectCube(Ray ray,float3 cubeSize,inout float t,inout float3 normal){
//1.用矩阵将射线转为立方体的对象空间,略 后面的法线也应该转回世界空间
float3 rayOri=ray.origin;
float3 rayDir=ray.direction;
float3 pos;
//x轴两个面
if(rayDir.x<0){
t=(cubeSize.x-rayOri.x)/rayDir.x;
pos=rayOri+t*rayDir;
if(ISPOINT(pos,y,z)){
normal=float3(1,0,0);
return true;
}
}
else if(rayDir.x>0){
t=-rayOri.x/rayDir.x;
pos=rayOri+t*rayDir;
if(ISPOINT(pos,y,z)){
normal=float3(-1,0,0);
return true;
}
}
//y轴两个面
if(rayDir.y<0){
t=(cubeSize.y-rayOri.y)/rayDir.y;
pos=rayOri+t*rayDir;
if(ISPOINT(pos,x,z)){
normal=float3(0,1,0);
return true;
}
}
else if(rayDir.y>0){
t=-rayOri.y/rayDir.y;
pos=rayOri+t*rayDir;
if(ISPOINT(pos,x,z)){
normal=float3(0,-1,0);
return true;
}
}
//z轴两个面
if(rayDir.z<0){
t=(cubeSize.z-rayOri.z)/rayDir.z;
pos=rayOri+t*rayDir;
if(ISPOINT(pos,x,y)){
normal=float3(0,0,1);
return true;
}
}
else if(rayDir.z>0){
t=-rayOri.z/rayDir.z;
pos=rayOri+t*rayDir;
if(ISPOINT(pos,x,y)){
normal=float3(0,0,-1);
return true;
}
}
return false;
}
球:
射线:
圆:pos+radius
得到向量d
点乘单位向量得到投影P1
两次勾股定律得到p2平方
<0无解 =0 一解 >0 两解
如果有两个解,就选距离最短的那个解,看图可以知道最短的就是进入圆的点,圆的是退出圆。
代码:
bool IntersectSphere(Ray ray, float3 position,float radius,inout float t)
{
float3 d = ray.origin - position;
float p1 = -dot(ray.direction, d);
float p2sqr = p1 * p1 - dot(d, d) +radius*radius;
if (p2sqr < 0)//不存在解也就是不相交
return false;
float p2 = sqrt(p2sqr);
t = p1 - p2 > 0 ? p1 - p2 : p1 + p2;//优先选近的那个点,除非近的点在后头
return true;
}
椭球:
这个也是对象空间,这里省略了矩阵变换
推导过程:
射线: (O原点,D方向,t距离)
椭球: (m是x半轴和y半轴长度比值,n是x半轴和z半轴长度比值 ,两者等于1就等价于球)
将射线公式带入椭球公式,整理后如下
判别式:
交点应该是最近的点:
计算法线:
转为隐函数,值为0,同时切线和法线的向量积也是0
椭球隐函数:
代码:
bool IntersectEllipsoid(Ray ray,float radius,float m,float n,inout float t)
{
//此处应该有个世界->对象
float3 rayDir=ray.direction;
float3 rayOri=ray.origin;
float3 newD=rayDir*float3(1,m,n);
float3 newO=rayOri*float3(1,m,n);
float a=dot(newD,newD);
float b=2*dot(newD,newO);
float c=dot(newO,newO)-radius*radius;
float det=b*b-4*a*c;
if(det<0.0f) return false;
t=(-b-sqrt(det))/2*a;
return true;
}