Qt3D中如何设置部分Entity不可拾取(已绑定RootEntity的QObjectPicker)
解决Qt3D中部分Entity不可拾取的问题
看起来你在Qt3D的拾取控制上踩了个常见的坑——混淆了Entity树的事件传递和FrameGraph的拾取渲染控制。我来帮你梳理清楚问题,并给出两种可行的解决方法。
问题根源分析
你在RootEntity(或父Entity)上添加了QObjectPicker,默认情况下,点击任何子Entity都会触发父Picker的事件(事件冒泡机制)。而你尝试用QNoPicking和QLayerFilter的方式没生效,是因为FrameGraph的配置逻辑错误:你给不可拾取层添加了QNoPicking分支,这相当于对这些Entity做了一次无拾取的重复渲染,而非阻止它们被拾取系统检测。
方法一:在拾取事件中直接过滤(简单快捷)
这种方法不需要修改复杂的FrameGraph,只需要在Picker的点击事件里判断目标Entity是否属于不可拾取层,直接忽略即可。
步骤:
- 给所有不可拾取的Entity添加
noPickingLayer组件; - 在
clicked信号的槽函数中,检查点击的Entity是否包含该层,若包含则跳过处理。
修正后的代码片段:
在loadScene函数的连接部分修改:
connect(picker, &Qt3DRender::QObjectPicker::clicked, this, [this, noPickingLayer](Qt3DRender::QPickEvent* event) { // 检查点击的Entity是否属于不可拾取层 if (event->entity()->componentsOfType<Qt3DRender::QLayer>().contains(noPickingLayer)) { return; // 忽略不可拾取的Entity } qDebug() << "点击了可拾取的Entity:" << event->entity(); });
方法二:通过FrameGraph控制拾取范围(符合Qt3D设计,性能更优)
这种方法从根源上阻止不可拾取的Entity参与拾取渲染,避免生成不必要的拾取事件,性能更好。
步骤:
- 创建两个Layer:
pickableLayer(可拾取)和nonPickableLayer(不可拾取); - 给需要拾取的Entity添加
pickableLayer,不需要的添加nonPickableLayer; - 修改FrameGraph,添加一个仅包含
pickableLayer的QLayerFilter,让拾取系统只处理这些Entity。
修正后的完整代码关键部分:
构造函数中的FrameGraph修改:
// 创建两个控制层 Qt3DRender::QLayer* pickableLayer = new Qt3DRender::QLayer(); Qt3DRender::QLayer* nonPickableLayer = new Qt3DRender::QLayer(); // ... 其他相机、控制器、渲染状态代码保持不变 ... // 添加拾取控制的LayerFilter:仅让可拾取层参与拾取 Qt3DRender::QLayerFilter* pickableFilter = new Qt3DRender::QLayerFilter(cameraSelector); pickableFilter->addLayer(pickableLayer); viewer3D->setActiveFrameGraph(surfaceSelector); // 传递两个层到场景加载函数 loadScene(rootEntity, pickableLayer, nonPickableLayer);
loadScene函数修改:
void QtPicking::loadScene(Qt3DCore::QEntity* rootEntity, Qt3DRender::QLayer* pickableLayer, Qt3DRender::QLayer* nonPickableLayer) { Qt3DExtras::QPhongMaterial* material = new Qt3DExtras::QPhongMaterial(rootEntity); material->setAmbient(Qt::black); Qt3DCore::QEntity* parentEntity = new Qt3DCore::QEntity(rootEntity); // 如果父Entity本身需要可拾取,记得也添加pickableLayer // parentEntity->addComponent(pickableLayer); // 可拾取的子Entity Qt3DCore::QEntity* child1 = new Qt3DCore::QEntity(parentEntity); Qt3DExtras::QSphereMesh* child1Mesh = new Qt3DExtras::QSphereMesh; child1Mesh->setRadius(1); Qt3DCore::QTransform* child1Transform = new Qt3DCore::QTransform(); child1Transform->setTranslation(QVector3D(2, 2, 2)); child1->addComponent(child1Mesh); child1->addComponent(child1Transform); child1->addComponent(material); child1->addComponent(pickableLayer); // 标记为可拾取 // 不可拾取的子Entity Qt3DCore::QEntity* child2 = new Qt3DCore::QEntity(parentEntity); Qt3DExtras::QSphereMesh* child2Mesh = new Qt3DExtras::QSphereMesh; child2Mesh->setRadius(2); child2->addComponent(child2Mesh); child2->addComponent(material); child2->addComponent(nonPickableLayer); // 标记为不可拾取 Qt3DRender::QObjectPicker* picker = new Qt3DRender::QObjectPicker(); picker->setEnabled(true); parentEntity->addComponent(picker); connect(picker, &Qt3DRender::QObjectPicker::clicked, this, [this](Qt3DRender::QPickEvent* event) { qDebug() << "点击了可拾取的Entity:" << event->entity(); }); }
关键注意点
- 如果父Entity本身需要被拾取,记得给它也添加
pickableLayer; QNoPicking是FrameGraph的渲染状态节点,用来禁用某个分支的拾取,而非直接给Entity添加的组件;- 方法二更适合大规模场景,能减少不必要的拾取计算;方法一则适合快速迭代小场景。
内容的提问来源于stack exchange,提问作者Romain
相关产品推荐
相关产品推荐

