基于C++和glm的物理引擎RayCast三角相交检测错误求助
射线与三角形相交检测RayCast函数错误排查
以下是针对你的RayCast函数及相关代码的错误点排查和修正方案:
1. 索引遍历逻辑错误
这是导致检测结果错误的核心问题之一:你遍历三角形索引时,错误地使用了连续的i+0/i+1/i+2索引,而非每个三角形对应的三个独立索引(每个三角形占indices数组的3个元素)。这种写法会导致取错三角形顶点,直接引发相交检测结果错误。
错误代码片段:
for (size_t i = 0; i < mesh.indices.size() / 3; ++i) { auto normal = calculateNormal(mesh.hitBox[mesh.indices[i + 0]], mesh.hitBox[mesh.indices[i + 1]], mesh.hitBox[mesh.indices[i + 2]]); // ...后续代码 }
修正代码:
// 按步长3遍历索引数组,每个步长对应一个三角形的三个顶点索引 for (size_t i = 0; i < mesh.indices.size(); i += 3) { uint32_t idx0 = mesh.indices[i]; uint32_t idx1 = mesh.indices[i + 1]; uint32_t idx2 = mesh.indices[i + 2]; auto normal = calculateNormal(mesh.hitBox[idx0], mesh.hitBox[idx1], mesh.hitBox[idx2]); // ...后续代码引用idx0/idx1/idx2访问顶点 }
2. 模型空间变换方向完全错误
你当前的代码试图用模型矩阵将世界空间射线转换到模型空间,但正确逻辑应该是使用模型矩阵的逆矩阵——模型矩阵是将模型空间坐标转换到世界空间的矩阵,逆矩阵才能完成反向转换(世界→模型)。同时,方向向量的齐次分量w应设为0(不受平移变换影响),点的齐次分量w设为1。
错误代码片段:
glm::dvec3 vModel = glm::dvec4(v, 1.0) * this->mesh.model; glm::dvec3 fromModel = glm::dvec4((from - mesh.position), 1.0) * this->mesh.model; glm::dvec3 toModel = glm::dvec4((to - mesh.position), 1.0) * this->mesh.model;
修正代码:
// 计算模型矩阵的逆矩阵,用于世界空间→模型空间的转换 glm::dmat4 modelInv = glm::inverse(this->mesh.model); // 转换射线起点(点,w=1) glm::dvec3 fromModel = glm::dvec3(modelInv * glm::dvec4(from, 1.0)); // 转换射线终点(点,w=1) glm::dvec3 toModel = glm::dvec3(modelInv * glm::dvec4(to, 1.0)); // 转换射线方向向量(向量,w=0,不受平移影响) glm::dvec3 vModel = glm::dvec3(modelInv * glm::dvec4(glm::normalize(to - from), 0.0));
3. 平面相交计算存在精度与逻辑漏洞
原planeIntersection函数未处理射线与平面平行的情况(分母为0),且判断条件仅检查k>0,未考虑浮点精度容差,同时公式推导可以更清晰直观。
错误代码片段:
std::pair<glm::dvec3, double> planeIntersection(const glm::dvec3 &start, const glm::dvec3 &end, const glm::dvec3 &point, const glm::dvec3 &normal) const { double sDot = glm::dot(start, normal); double k = (sDot - glm::dot(point, normal)) / (sDot - glm::dot(end, normal)); glm::dvec3 res = start + (end - start) * k; return std::make_pair(res, k); }
修正代码:
std::pair<glm::dvec3, double> planeIntersection(const glm::dvec3 &start, const glm::dvec3 &end, const glm::dvec3 &point, const glm::dvec3 &normal) const { glm::dvec3 dir = end - start; double denom = glm::dot(normal, dir); // 射线与平面平行,无有效交点 if (std::fabs(denom) < EPS) return {glm::dvec3(0.0), -1.0}; // 计算射线参数t:t>=0表示交点在射线起点前方 double t = (glm::dot(normal, point) - glm::dot(normal, start)) / denom; glm::dvec3 res = start + dir * t; return {res, t}; }
4. 点在三角形内的检测存在精度损失
原代码对叉乘结果做了normalize操作,这会放大浮点精度误差,实际上我们只需要判断叉乘结果与法线的点积符号是否一致,无需归一化。同时,判断条件需要加入浮点容差,避免因精度问题导致误判。
错误代码片段:
bool isPointInsideTriangle(const glm::dvec3 &point, const glm::dvec3 &triangleNorm, const glm::dvec3 &point1, const glm::dvec3 &point2, const glm::dvec3 &point3) const { double dot1 = glm::dot(glm::normalize(glm::cross(point2 - point1, point - point1)), triangleNorm); double dot2 = glm::dot(glm::normalize(glm::cross(point3 - point2, point - point2)), triangleNorm); double dot3 = glm::dot(glm::normalize(glm::cross(point1 - point3, point - point3)), triangleNorm); if ((dot1 >= 0.0 && dot2 >= 0.0 && dot3 >= 0.0) || (dot1 <= 0.0 && dot2 <= 0.0 && dot3 <= 0.0)) return true; return false; }
修正代码:
bool isPointInsideTriangle(const glm::dvec3 &point, const glm::dvec3 &triangleNorm, const glm::dvec3 &point1, const glm::dvec3 &point2, const glm::dvec3 &point3) const { // 计算三条边与点的叉乘,再点乘法线获取符号 glm::dvec3 cross1 = glm::cross(point2 - point1, point - point1); double dot1 = glm::dot(cross1, triangleNorm); glm::dvec3 cross2 = glm::cross(point3 - point2, point - point2); double dot2 = glm::dot(cross2, triangleNorm); glm::dvec3 cross3 = glm::cross(point1 - point3, point - point3); double dot3 = glm::dot(cross3, triangleNorm); // 加入浮点容差,判断所有点积符号一致(全非负或全非正) bool allNonNegative = (dot1 >= -EPS) && (dot2 >= -EPS) && (dot3 >= -EPS); bool allNonPositive = (dot1 <= EPS) && (dot2 <= EPS) && (dot3 <= EPS); return allNonNegative || allNonPositive; }
5. 法线计算中的未定义函数问题
原代码使用了未定义的sqrAbs函数,应该替换为glm内置的glm::length2函数(返回向量长度的平方,避免开方运算,效率更高)。
错误代码片段:
if (sqrAbs(crossProduct) > EPS)
修正代码:
if (glm::length2(crossProduct) > EPS)
6. RayCast返回结果错误
原代码在检测到相交时返回mesh.position(物体位置),而非实际的交点坐标。正确做法是将模型空间的交点转换回世界空间后返回。
错误代码片段:
return std::make_pair(true, glm::dvec3(mesh.position));
修正代码:
// 将模型空间交点转换为世界空间 glm::dvec3 worldHit = glm::dvec3(this->mesh.model * glm::dvec4(intersection.first, 1.0)); return std::make_pair(true, worldHit);
内容的提问来源于stack exchange,提问作者Dimas
相关产品推荐
相关产品推荐

