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

Qt3D中QLayerFilter与QObjectPicker失效,求多图层筛选下的替代拾取方案

Qt3D多QLayerFilter场景下替代QObjectPicker的物体拾取方案

Qt3D存在编号为QTBUG-128299的潜在BUG:当帧图包含两个及以上QLayerFilter节点时,不同QLayer实例化的网格无法与QObjectPicker正常协作。而实现透明效果通常需要拆分多个QLayerFilter节点分别渲染不透明和透明物体,这就导致了拾取问题——只有帧图顶层QLayerFilter对应图层的物体能被拾取,调换节点顺序则仅另一物体可被拾取,且因为要保留透明渲染效果,无法退回使用QSortPolicy。

下面提供两种无需依赖QObjectPicker的拾取方案:

方案一:手动实现射线与包围盒/几何体相交检测

核心逻辑是通过鼠标位置生成世界空间射线,遍历场景中标记为可拾取的物体,依次检测射线是否与物体的包围盒或几何体相交。

1. 转换鼠标坐标为世界射线

在窗口的鼠标点击事件中,将屏幕坐标转换为Qt3D相机空间的射线:

void MainWindow::mousePressEvent(QMouseEvent *event)
{
    QVector2D mousePos(event->position().x(), event->position().y());
    QCamera* camera = view->camera();
    // 射线起点为相机位置
    QVector3D rayOrigin = camera->position();
    // 计算射线方向:将屏幕远平面点反投影到世界空间,与相机位置的向量
    QVector3D rayEnd = camera->unproject(QVector3D(mousePos.x(), mousePos.y(), 1.0f));
    QVector3D rayDirection = rayEnd - rayOrigin;
    rayDirection.normalize();
    
    // 执行射线检测
    checkRayIntersection(rayOrigin, rayDirection);
}

2. 遍历物体进行相交检测

先递归收集场景中所有可拾取的实体,然后逐个检测射线与物体包围盒的交集:

// 递归查找所有标记为可拾取的实体
QList<QEntity*> getPickableEntities(QNode* parentNode)
{
    QList<QEntity*> entities;
    for (QNode* child : parentNode->children()) {
        if (QEntity* entity = qobject_cast<QEntity*>(child)) {
            // 这里通过objectName前缀标记可拾取,也可以用自定义组件
            if (entity->objectName().startsWith("Pickable_")) {
                entities.append(entity);
            }
        }
        // 递归遍历子节点
        entities += getPickableEntities(child);
    }
    return entities;
}

// 射线与AABB包围盒相交检测
bool rayHitsBoundingBox(const QVector3D& origin, const QVector3D& dir, const QBoundingBox& bb)
{
    QVector3D min = bb.minimum();
    QVector3D max = bb.maximum();
    
    float tMin = (min.x() - origin.x()) / dir.x();
    float tMax = (max.x() - origin.x()) / dir.x();
    if (tMin > tMax) std::swap(tMin, tMax);
    
    float yMin = (min.y() - origin.y()) / dir.y();
    float yMax = (max.y() - origin.y()) / dir.y();
    if (yMin > yMax) std::swap(yMin, yMax);
    
    if ((tMin > yMax) || (yMin > tMax)) return false;
    tMin = qMax(tMin, yMin);
    tMax = qMin(tMax, yMax);
    
    float zMin = (min.z() - origin.z()) / dir.z();
    float zMax = (max.z() - origin.z()) / dir.z();
    if (zMin > zMax) std::swap(zMin, zMax);
    
    return !(tMin > zMax || zMin > tMax);
}

// 执行射线检测逻辑
void checkRayIntersection(const QVector3D& origin, const QVector3D& dir)
{
    QList<QEntity*> pickables = getPickableEntities(sceneRoot);
    for (QEntity* entity : pickables) {
        if (QMesh* mesh = entity->components().filterByType<QMesh>().value(0)) {
            if (rayHitsBoundingBox(origin, dir, mesh->boundingBox())) {
                // 这里可以触发自定义的拾取回调,比如高亮、触发事件
                qDebug() << "Picked entity:" << entity->objectName();
                // 如果只需要第一个相交的物体,直接break
                break;
            }
        }
    }
}

优化点

  • 提前缓存可拾取实体列表,避免每次检测都递归遍历场景
  • 对于高精度需求,在包围盒相交后,进一步检测射线与几何体的三角形面相交

方案二:使用Qt3D的QRayCaster类

Qt3D提供了QRayCaster类,可以直接在场景中执行射线检测,无需依赖QObjectPicker,且支持批量检测:

void performRaycastWithCaster(const QVector3D& origin, const QVector3D& dir)
{
    QRayCaster* rayCaster = new QRayCaster(sceneRoot);
    rayCaster->setOrigin(origin);
    rayCaster->setDirection(dir);
    // 设置为单次触发模式
    rayCaster->setRunMode(QAbstractRayCaster::SingleShot);
    
    // 监听检测结果
    connect(rayCaster, &QRayCaster::hitsChanged, this, [this](const QList<QRayCasterHit>& hits) {
        if (!hits.isEmpty()) {
            // 获取第一个被击中的实体(按距离排序)
            QEntity* picked = hits.first().entity();
            qDebug() << "Picked via QRayCaster:" << picked->objectName();
            // 处理拾取逻辑
        }
        // 检测完成后释放资源
        rayCaster->deleteLater();
    });
    
    // 触发检测
    rayCaster->trigger();
}

这个方案更简洁,Qt3D内部已经优化了检测逻辑,适合大多数场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 02:37:08