You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何判断射线与人体仿真模型(Phantom)是否相交?

如何判断射线与人体仿真模型(Phantom)是否相交?

首先,你的模型是基于三角网格的(从glDrawElements(GL_TRIANGLES)可以看出来),所以射线与模型相交的核心就是检测射线是否与模型中的任意一个三角形相交。下面一步步给你讲怎么实现,以及如何优化性能(毕竟你的模型有200万顶点,三角数量肯定不少)。

1. 先明确射线的完整表示

你当前的calculateMouseRay函数只返回了射线的方向向量,但射线还需要一个起点——也就是你的FPS相机的位置。所以在做相交检测时,你需要同时用到:

  • 射线起点:fpsCamera->getPosition()
  • 射线方向:你生成的worldRay(已经做了归一化,这很好)

2. 实现射线-三角形相交算法(Möller-Trumbore)

这是目前最常用的快速射线三角相交算法,它能高效判断射线是否与三角形相交,还能返回交点距离。下面是适配GLM的实现代码:

#include <glm/glm.hpp>
#include <glm/gtc/constants.hpp>
#include <float.h>

// 射线与三角形相交检测:返回是否相交,t是射线起点到交点的距离
bool rayTriangleIntersect(const glm::vec3& rayOrigin, const glm::vec3& rayDir,
                          const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2,
                          float& t) {
    const float EPSILON = 0.0000001f;
    glm::vec3 edge1 = v1 - v0;
    glm::vec3 edge2 = v2 - v0;
    glm::vec3 h = glm::cross(rayDir, edge2);
    float a = glm::dot(edge1, h);

    // 如果a接近0,说明射线与三角形平行
    if (a > -EPSILON && a < EPSILON)
        return false;

    float f = 1.0f / a;
    glm::vec3 s = rayOrigin - v0;
    float u = f * glm::dot(s, h);

    // u不在[0,1]范围内,交点不在三角形内部
    if (u < 0.0f || u > 1.0f)
        return false;

    glm::vec3 q = glm::cross(s, edge1);
    float v = f * glm::dot(rayDir, q);

    // v不在[0,1]或者u+v>1,交点不在三角形内部
    if (v < 0.0f || u + v > 1.0f)
        return false;

    // 计算交点距离t
    t = f * glm::dot(edge2, q);
    // t>0说明交点在射线正方向上(不是相机背后)
    return t > EPSILON;
}

3. 在你的Mesh类中添加相交检测方法

你的Mesh类已经存储了vertices和indices(每3个索引对应一个三角形),所以可以添加一个方法遍历所有三角形,逐个检测:

// 在Mesh类的头文件中声明
bool rayIntersect(const glm::vec3& rayOrigin, const glm::vec3& rayDir, float& closestT);

// 实现
bool Mesh::rayIntersect(const glm::vec3& rayOrigin, const glm::vec3& rayDir, float& closestT) {
    if (!loaded) return false;

    closestT = FLT_MAX;
    bool hasIntersection = false;

    // 遍历所有三角形:indices每3个一组是一个三角的顶点索引
    for (size_t i = 0; i < indicies.size(); i += 3) {
        GLuint idx0 = indicies[i];
        GLuint idx1 = indicies[i+1];
        GLuint idx2 = indicies[i+2];

        const glm::vec3& v0 = vertices[idx0];
        const glm::vec3& v1 = vertices[idx1];
        const glm::vec3& v2 = vertices[idx2];

        float t;
        if (rayTriangleIntersect(rayOrigin, rayDir, v0, v1, v2, t)) {
            // 记录最近的交点
            if (t < closestT) {
                closestT = t;
                hasIntersection = true;
            }
        }
    }

    return hasIntersection;
}

4. 性能优化:先检测模型包围盒

直接遍历所有三角形对于你的大模型来说肯定会卡顿——200万顶点对应的三角数量可能有几百万个。所以第一步要给模型计算一个轴对齐包围盒(AABB),先判断射线是否和这个包围盒相交,如果不相交,直接跳过所有三角检测。

4.1 给Mesh类添加AABB存储

在Mesh类中添加两个成员变量:

glm::vec3 aabbMin;
glm::vec3 aabbMax;

4.2 加载模型后计算AABB

在loadOBJ函数的最后添加计算AABB的逻辑:

bool Mesh::loadOBJ() {
    loadVertices("Phantom Data/FA_vertices.txt");
    loadIndices("Phantom Data/FA_indices.txt");
    
    // 计算AABB
    if (!vertices.empty()) {
        aabbMin = vertices[0];
        aabbMax = vertices[0];
        for (const auto& v : vertices) {
            aabbMin = glm::min(aabbMin, v);
            aabbMax = glm::max(aabbMax, v);
        }
    }
    
    initBuffers();
    return (loaded = true);
}

4.3 实现射线与AABB的相交检测

bool rayAABBIntersect(const glm::vec3& rayOrigin, const glm::vec3& rayDir,
                      const glm::vec3& aabbMin, const glm::vec3& aabbMax) {
    float tMin = (aabbMin.x - rayOrigin.x) / rayDir.x;
    float tMax = (aabbMax.x - rayOrigin.x) / rayDir.x;

    if (tMin > tMax) std::swap(tMin, tMax);

    float tYMin = (aabbMin.y - rayOrigin.y) / rayDir.y;
    float tYMax = (aabbMax.y - rayOrigin.y) / rayDir.y;

    if (tYMin > tYMax) std::swap(tYMin, tYMax);

    if ((tMin > tYMax) || (tYMin > tMax))
        return false;

    if (tYMin > tMin)
        tMin = tYMin;

    if (tYMax < tMax)
        tMax = tYMax;

    float tZMin = (aabbMin.z - rayOrigin.z) / rayDir.z;
    float tZMax = (aabbMax.z - rayOrigin.z) / rayDir.z;

    if (tZMin > tZMax) std::swap(tZMin, tZMax);

    if ((tMin > tZMax) || (tZMin > tMax))
        return false;

    if (tZMin > tMin)
        tMin = tZMin;

    if (tZMax < tMax)
        tMax = tZMax;

    // 只要交点在射线正方向(tMin>0)就算相交
    return tMin > 0.0f;
}

4.4 修改Mesh的rayIntersect方法,先检测AABB

bool Mesh::rayIntersect(const glm::vec3& rayOrigin, const glm::vec3& rayDir, float& closestT) {
    if (!loaded) return false;

    // 先检测AABB,如果不相交直接返回
    if (!rayAABBIntersect(rayOrigin, rayDir, aabbMin, aabbMax)) {
        return false;
    }

    // 剩下的遍历三角逻辑和之前一样
    closestT = FLT_MAX;
    bool hasIntersection = false;

    for (size_t i = 0; i < indicies.size(); i += 3) {
        GLuint idx0 = indicies[i];
        GLuint idx1 = indicies[i+1];
        GLuint idx2 = indicies[i+2];

        const glm::vec3& v0 = vertices[idx0];
        const glm::vec3& v1 = vertices[idx1];
        const glm::vec3& v2 = vertices[idx2];

        float t;
        if (rayTriangleIntersect(rayOrigin, rayDir, v0, v1, v2, t)) {
            if (t < closestT) {
                closestT = t;
                hasIntersection = true;
            }
        }
    }

    return hasIntersection;
}

5. 更进一步的性能优化:BVH树

如果AABB还不够快(比如模型局部复杂,射线经常穿过AABB但只和少数三角相交),可以构建层次包围盒(BVH),把模型分成多个层级的小包围盒,递归检测射线是否与这些小包围盒相交,只检测那些可能相交的子节点内的三角。这会大幅减少需要检测的三角数量,但实现起来稍微复杂一些,你可以等AABB优化满足需求后再考虑。

6. 调用示例

在你的交互逻辑中,这样使用:

// 获取射线起点和方向
glm::vec3 rayOrigin = fpsCamera->getPosition();
glm::vec3 rayDir = calculateMouseRay(projectionMatrix);

float closestT;
if (mesh->rayIntersect(rayOrigin, rayDir, closestT)) {
    // 射线与模型相交了,可以计算交点位置
    glm::vec3 intersectionPoint = rayOrigin + rayDir * closestT;
    // 这里处理模型选择、变换等逻辑
}

内容的提问来源于stack exchange,提问作者Elnur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 07:27:30