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
相关产品推荐
相关产品推荐

