问题:为什么做光线步进要学习包围盒
答案:因为全局搜索3DTexture费时,需要指定的位置进行步进,减少不必要遍历
射线和AABB包围盒的交点
AABB包围盒的简介可以网上找,很多的,这里不做介绍.
先给出射线和AABB盒的交点怎么求,出自于一篇知乎添加链接描述
先处理YZ的两个平面,可知射线和YZ两个面的左右交点在X轴的值分别是 和 ,因为是AABB包围盒,所以可以知道 和 的值,所以 和 也是可知的.则有
计算可得
其中
写到这一步了,可以想下, 作为光线方向在x轴上的分量
如果 为0,那就说明射线和YZ两个平面平行,但是不知道射线的起点是在包围盒里面还是外面;所以需要进一步判断,若 ,说明点是在包围盒外面的,则射线和包围盒肯定不相交;就算 ,也不代表起点在包围盒内,只能说明起点是在YZ无限延伸的平面内,需要进一步利用包围盒的YZ轴判断,即 才能真正确定P点在包围盒内.
如果 不为0,则有
在论文上的伪代码来看,由于是AABB盒,设中心点为
则知 ,其中 是包围盒三个轴的长度,可知都大于0
进一步知
其中
是包围盒三个轴的长度,可知都大于0
进一步知
判断AABB和OBB包围盒和射线是否有交点的原理是两个区间范围是否重复
先看下对应的链接
添加链接描述
添加链接描述
即max(start) >min(end),则两个区间不相交
再看下射线和AABB包围盒是否有交点的两个方法
1:依次判断和面是否相交 链接https://zhuanlan.zhihu.com/p/610258258
2:Slab方法 链接添加链接描述
第一种方法(直接口述)
判断射线起点是否在3D内部,如果在,则必相交
若射线起点不在3D内部,遍历每一个轴向,求出和近面的交点,判断交点是否在包围盒的内部
重点: 怎么求出近面的交点;如果射线方向和某一个轴向的方向的夹角是钝角(也就是点乘<0),那么近面就是该轴对应值Max的面;如果是锐角,那么近面就是该轴对应值Min的面,有demo演示
以遍历到X轴正方向为例
对于射线AB,可知是锐角,则近面就是X轴对应值小的Xmin面,也就是左边红色的面;
对于射线CD,可知是钝角,则近面就是X轴对应值大的Xmax面,也就是右边绿色的面
换句话说,需要提前计算好射线和轴的方向,这样就能算出和近面的交点,且根据射线的参数方程求出的t值,如果t值小于0,则射线和平面必不相交;如果t值大于0,则需要通过t值算出的交点位置判断是否在包围盒里面
第二种方法
原理就是:求出射线和6个面的交点(以参数的形式代替),也就是说有3个区间;确定好区间的左值和右值(需要进行两个t之间的判断,这就是论文上有对t0和t1做大小判断的原因,因为无法确定大小,所以强制判断);然后对三个区间的左值做max得到left,对三个区间的右值做min得到right,若left>right,则不相交;left<=right,相交.论文的伪代码即
现在以demo做演示
由于在Word上已经写好了公式,所以在CSDN上不想再写了,直接截图了
这就是为什么论文上要有交换最大最小值的原因,以确定 始终是最小值
现给出C++的基本实现代码 参考链接添加链接描述
/// @brief 判断射线和AABB盒是否有交点
/// @param pStartPos 射线起点
/// @param pEndPos 射线终点
/// @param pMinPlane 包围盒最小点 数组 里面有三个元素
/// @param pMaxPlane 包围盒最大点 数组 里面有三个元素
/// @param fMinValue 射线和近面交点的t值
/// @param fMaxValue 射线和远面交点的t值
/// @return 是否相交
bool JudgeAABBIntersection(const float* pStartPos, const float* pEndPos, const float* pMinPlane, const float* pMaxPlane, float& fMinValue, float& fMaxValue)
{
static const float EPS = 1e-6f;
float fRayDir[3];
fRayDir[0] = pEndPos[0] - pStartPos[0];
fRayDir[1] = pEndPos[1] - pStartPos[1];
fRayDir[2] = pEndPos[2] - pStartPos[2];
fMinValue = 0.0;
fMaxValue = 1.0f;
// 遍历三个面
for (int i = 0; i < 3; i++)
{
// 射线方向和当前轴平行
if (std::fabs(fRayDir[i]) < EPS)
{
if (pStartPos[i] < pMinPlane[i] || pStartPos[i] > pMaxPlane[i])
{
return false;
}
}
else
{
//通过上面的求t值公式,求出AABB盒的每个轴上的两个面分别与射线的交点的t值,求出一个大值为远面交点t值,小值为近面交点t值,此时各个面的法向量n在该轴值可取为1,其它两个轴上分值为0
const float ood = 1.0f / fRayDir[i];
float t1 = (pMinPlane[i] - pStartPos[i]) * ood;
float t2 = (pMaxPlane[i] - pStartPos[i]) * ood;
if (t1 > t2) { float tmp = t1; t1 = t2; t2 = tmp; }
if (t1 > fMinValue) fMinValue = t1;
if (t2 < fMaxValue) fMaxValue = t2;
if (fMinValue > fMaxValue) return false;
}
}
return true;
}
射线和OBB包围盒的交点
OBB盒相对于AABB盒的一个特点就是轴向不是(1,0,0), (0,1,0), (0,0,1),的类似形式,但是求相交的思想是一样的.
唯一不同的就是需要根据包围盒的长宽高计算.
如图所示,该图也是来自于链接添加链接描述
可知向量
则剩下的操作就是和AABB盒是一样的.
现给出C++的基本实现代码 参考实现
添加链接描述
添加链接描述
float GetDot3D(const float* pV1, const float* pV2)
{
if (pV1 == nullptr || pV2 == nullptr)
{
return;
}
return pV1[0] * pV2[0] + pV1[1] * pV2[1] + pV1[2] * pV2[2];
}
/// @brief 射线和OBB盒是否相交 可能有些特殊场景没有考虑到 但思想没有问题
/// @param pStartPos 射线起点
/// @param pEndPos 射线终点
/// @param pOBBCenter OBB盒中心点
/// @param vecAxis OBB盒对应的轴方向
/// @param pOBBXYZ OBB盒对应的长宽高
/// @param fMinValue 射线和近面交点的t值
/// @param fMaxValue 射线和远面交点的t值
/// @return 是否相交
bool JudgeOBBIntersection(const float* pStartPos, const float* pEndPos, const float* pOBBCenter,
const std::vector<float*> vecAxis, const float* pOBBXYZ,
float& fMinValue, float& fMaxValue)
{
// 判空 其它输入参数类似 仅作示例
if (pStartPos == nullptr || vecAxis.size() != 3)
{
return;
}
static const float EPS = 1e-6f;
float fRayDir[3];
fRayDir[0] = pEndPos[0] - pStartPos[0];
fRayDir[1] = pEndPos[1] - pStartPos[1];
fRayDir[2] = pEndPos[2] - pStartPos[2];
fMinValue = 0.0;
fMaxValue = 1.0f;
float fCMinusP[3];
fCMinusP[0] = pOBBCenter[0] - pStartPos[0];
fCMinusP[1] = pOBBCenter[1] - pStartPos[1];
fCMinusP[2] = pOBBCenter[2] - pStartPos[2];
// 遍历三个面
for (int i = 0; i < 3; i++)
{
// 射线方向和当前轴平行
float fRayDirDotAxis = GetDot3D(fRayDir, vecAxis[i]);
auto fCMinusPDotAxis = GetDot3D(fCMinusP, vecAxis[i]);
auto fHalf = pOBBXYZ[i] / 2;
if (std::fabs(fRayDirDotAxis) < EPS)
{
// 射线在OBB外 则必不相交
if (std::fabs(fCMinusPDotAxis) > fHalf)
{
return false;
}
}
else
{
const float ood = 1.0f / fRayDir[i];
float t1 = (fCMinusPDotAxis - fHalf) * ood;
float t2 = (fCMinusPDotAxis + fHalf) * ood;
if (t1 > t2) { float tmp = t1; t1 = t2; t2 = tmp; }
if (t1 > fMinValue) fMinValue = t1;
if (t2 < fMaxValue) fMaxValue = t2;
if (fMinValue > fMaxValue) return false;
}
}
return true;
}
射线和AABB包围盒的交点在光线步进中的应用
讲了那么多,现在说下AABB的实际应用,在光线步进中的Shader中,就是利用这个思想实现射线和3DTexture(AABB)的噪声问题确定交点的,如图所示
glsl中min函数的解释,max函数也是类似的
说明下为什么Shader代码里的有些写法和上面的C++有些不一样呢.
1:在Shader要尽量避免if等逻辑判断,尽可能的使用Shader中自带的函数
2:图中97,98行,求出射线和三个轴的交点,这就是glsl语法的神奇之处,不用for循环,可以直接求出;
图中100,101行,相当于求出射线和三个轴的交点的最大最小值;
图中103-106行,利用两次min和max函数求出上述论文(两个区间的start和end)中描述的t0和t1,再利用两者的大小关系就可以判断射线和AABB盒是否相交;
图中115-116行,即求出射线交点和近远平面交点的t值;
其实还有优化空间,在hit=false的时候直接返回就可以
简易喷泉特效
20240129_032556