尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

VulkanSceneGraph学习教程(十九)

VulkanSceneGraph学习教程(十九) 第 19 章 拾取与交互摘要本章系统介绍了 VulkanSceneGraph (VSG) 中实现鼠标拾取与交互的核心机制。主要内容包括使用vsg::LineSegmentIntersector进行射线拾取获取交点世界坐标与节点路径使用vsg::PolytopeIntersector进行体选择如框选触发遍历的方法实现选中高亮效果的思路一个完整的点击打印坐标示例以及常见问题排查与解决方案。本章为后续实现交互式3D应用奠定了基础。本章定位让程序「知道鼠标点到了什么」。VSG 在vsg::utils提供射线/体相交器纯 CPU 遍历场景图做拾取不依赖 GPU。19.1 本章目标用vsg::LineSegmentIntersector做鼠标射线拾取读取交点信息世界坐标、命中的节点路径了解vsg::PolytopeIntersector用于视锥/体选择实现「选中高亮」的基本思路。19.2 前置准备第 6 章节点/遍历与第 11 章相机已读了解鼠标事件PointerEvent来自vsg::ui第 24 章会用到。19.3 射线拾取LineSegmentIntersectorLineSegmentIntersector是一条「线段 vs 几何」的相交器有两个构造方式// 方式 A直接给世界空间线段 vsg::LineSegmentIntersector intersector(start, end); // 方式 B给相机 屏幕坐标自动换算成穿过像素的射线 vsg::LineSegmentIntersector intersector(camera, x, y);结果落在intersector.intersectionsstd::vectorref_ptrIntersection。每个Intersection含worldIntersection/localIntersection世界/局部坐标ratio沿射线的参数0..1nodePath从根到命中节点的路径instanceIndex实例化绘制中的实例编号。19.4 触发遍历Intersector继承自ConstVisitor用accept触发对场景图的遍历并填充intersectionsauto intersector vsg::LineSegmentIntersector(camera, mouseX, mouseY); scene-accept(intersector); // 遍历 scene填充 intersector.intersections if (!intersector.intersections.empty()) { auto hit intersector.intersections.front(); std::cout 命中世界坐标: hit-worldIntersection \n; // hit-nodePath 可回溯到被点中的节点 }注意intersections按ratio排序最近的在最前。取front()即最近交点。19.5 体选择PolytopeIntersectorvsg::PolytopeIntersector用于「视锥/凸多面体」相交适合框选、裁剪体选择// 用一个凸多面体一组平面定义选区 auto polytope vsg::PolytopeIntersector::create(planes); scene-accept(*polytope); // polytope-intersections 收集所有落入选区的几何常见用途编辑器里的框选、视锥裁剪调试看哪些节点落在当前视锥内。19.6 选中高亮完整实现拾取只是「找到节点」。高亮需要完整的实现方案包括创建高亮几何体、管理选中状态以及在拾取回调中调用。以下是完整的C代码示例1. 创建描边高亮几何体首先创建一个函数来生成描边高亮几何体比原几何体略大的线框壳#include vsg/nodes/Geometry.h #include vsg/state/BindGraphicsPipeline.h #include vsg/state/GraphicsPipeline.h #include vsg/state/DescriptorSet.h #include vsg/state/ColorBlendState.h #include vsg/state/RasterizationState.h #include vsg/state/InputAssemblyState.h #include vsg/state/MultisampleState.h #include vsg/state/DepthStencilState.h #include vsg/state/ShaderStage.h #include vsg/maths/transform.h #include vsg/io/read.h // 创建描边高亮几何体线框模式 vsg::ref_ptrvsg::Geometry createHighlightGeometry(vsg::ref_ptrvsg::Geometry originalGeometry, float scaleFactor 1.05f) { // 复制原始几何体的顶点数据 auto vertices vsg::ref_ptrvsg::vec3Array(originalGeometry-getArrays()[0].castvsg::vec3Array()); if (!vertices) return {}; // 创建缩放后的顶点数据 auto scaledVertices vsg::vec3Array::create(vertices-gt;size()); for (size_t i 0; i lt; vertices-gt;size(); i) { (*scaledVertices)[i] (*vertices)[i] * scaleFactor; } // 创建高亮几何体使用线框绘制 auto highlightGeometry vsg::Geometry::create(); highlightGeometry-gt;arrays vsg::DataList{scaledVertices}; highlightGeometry-gt;indices originalGeometry-gt;indices; // 使用相同的索引 // 设置绘制命令为线框模式 auto drawCommands vsg::Commands::create(); drawCommands-gt;addChild(vsg::BindVertexBuffers::create(0, vsg::DataList{scaledVertices})); drawCommands-gt;addChild(vsg::BindIndexBuffer::create(originalGeometry-gt;indices)); drawCommands-gt;addChild(vsg::DrawIndexed::create( originalGeometry-gt;indices-gt;valueCount(), 1, 0, 0, 0 )); highlightGeometry-gt;commands {drawCommands}; return highlightGeometry; } // 创建高亮管线红色线框 vsg::ref_ptrvsg::BindGraphicsPipeline createHighlightPipeline(vsg::ref_ptrvsg::Device device) { // 创建简单的线框着色器 auto vertexShader vsg::read_castvsg::ShaderStage(shaders/highlight.vert, device); auto fragmentShader vsg::read_castvsg::ShaderStage(shaders/highlight.frag, device); if (!vertexShader || !fragmentShader) { // 使用内置简单着色器作为备选 vertexShader vsg::ShaderStage::create(VK_SHADER_STAGE_VERTEX_BIT, main, #version 450\n layout(location 0) in vec3 inPosition;\n layout(binding 0) uniform Uniforms { mat4 modelViewProjection; };\n void main() { gl_Position modelViewProjection * vec4(inPosition, 1.0); }); fragmentShader vsg::ShaderStage::create(VK_SHADER_STAGE_FRAGMENT_BIT, main, #version 450\n layout(location 0) out vec4 outColor;\n void main() { outColor vec4(1.0, 0.0, 0.0, 1.0); }); // 红色 } // 创建图形管线 auto pipeline vsg::GraphicsPipeline::create( vsg::VertexInputState::create(), vsg::InputAssemblyState::create(VK_PRIMITIVE_TOPOLOGY_LINE_LIST), // 线框模式 vsg::RasterizationState::create(), vsg::MultisampleState::create(), vsg::ColorBlendState::create(), vsg::DepthStencilState::create(), vsg::ShaderStages{vertexShader, fragmentShader} ); // 修改光栅化状态为线框模式 auto rasterizationState const_castlt;vsg::RasterizationState*gt;(pipeline-gt;rasterizationState.get()); rasterizationState-gt;polygonMode VK_POLYGON_MODE_LINE; rasterizationState-gt;lineWidth 2.0f; return vsg::BindGraphicsPipeline::create(pipeline); }2. 选中节点管理器创建一个类来管理选中节点和高亮状态#include vsg/nodes/Group.h #include vsg/nodes/MatrixTransform.h #include vsg/nodes/StateGroup.h #include vsg/utils/Intersector.h #include unordered_set class SelectionManager : public vsg::Inheritvsg::Object, SelectionManager { public: SelectionManager(vsg::ref_ptrvsg::Device device, vsg::ref_ptrvsg::Group sceneRoot) : _device(device), _sceneRoot(sceneRoot) { _highlightPipeline createHighlightPipeline(device); } // 选中节点添加高亮 void selectNode(vsg::ref_ptrlt;vsg::Nodegt; node) { if (!node || _selectedNodes.count(node) gt; 0) return; // 查找或创建高亮几何体 auto highlightNode createHighlightForNode(node); if (!highlightNode) return; // 添加到场景根节点 _sceneRoot-amp;gt;addChild(highlightNode); _highlightNodes[node] highlightNode; _selectedNodes.insert(node); std::cout amp;lt;amp;lt; 选中节点: amp;lt;amp;lt; node-amp;gt;className() amp;lt;amp;lt; std::endl; } // 取消选中节点移除高亮 void deselectNode(vsg::ref_ptrlt;vsg::Nodegt; node) { auto it _highlightNodes.find(node); if (it ! _highlightNodes.end()) { _sceneRoot-gt;removeChild(it-gt;second); _highlightNodes.erase(it); _selectedNodes.erase(node); std::cout lt;lt; 取消选中节点: lt;lt; node-gt;className() lt;lt; std::endl; } } // 清除所有选中 void clearSelection() { for (autoamp; pair : _highlightNodes) { _sceneRoot-gt;removeChild(pair.second); } _highlightNodes.clear(); _selectedNodes.clear(); std::cout lt;lt; 清除所有选中 lt;lt; std::endl; } // 处理拾取结果 void handlePickResult(vsg::ref_ptrlt;vsg::LineSegmentIntersectorgt; intersector) { if (!intersector || intersector-gt;intersections.empty()) { clearSelection(); return; } // 获取最近交点 autoamp;amp; hit intersector-amp;gt;intersections.front(); // 从节点路径中查找可选的节点优先 StateGroup 或 MatrixTransform vsg::ref_ptramp;lt;vsg::Nodeamp;gt; selectedNode findSelectableNode(hit-amp;gt;nodePath); if (selectedNode) { // 切换选中状态 if (_selectedNodes.count(selectedNode) amp;gt; 0) { deselectNode(selectedNode); } else { selectNode(selectedNode); } } } // 获取当前选中的节点 const std::unordered_setlt;vsg::ref_ptrlt;vsg::Nodegt;gt;amp; getSelectedNodes() const { return _selectedNodes; } private: // 为节点创建高亮版本 vsg::ref_ptrvsg::Node createHighlightForNode(vsg::ref_ptrvsg::Node node) { // 查找几何体 auto geometry findGeometry(node); if (!geometry) return {}; // 创建高亮几何体 auto highlightGeometry createHighlightGeometry(geometry, 1.05f); if (!highlightGeometry) return {}; // 创建状态组并绑定高亮管线 auto stateGroup vsg::StateGroup::create(); stateGroup-amp;gt;add(_highlightPipeline); stateGroup-amp;gt;addChild(highlightGeometry); return stateGroup; } // 在节点路径中查找可选的节点 vsg::ref_ptrlt;vsg::Nodegt; findSelectableNode(const vsg::NodePathamp; nodePath) { // 反向遍历从叶子到根优先选择 StateGroup 或 MatrixTransform for (auto it nodePath.rbegin(); it ! nodePath.rend(); it) { auto node *it; if (node.castlt;vsg::StateGroupgt;() || node.castlt;vsg::MatrixTransformgt;()) { return node; } } return !nodePath.empty() ? nodePath.back() : vsg::ref_ptrlt;vsg::Nodegt;(); } // 查找节点中的几何体 vsg::ref_ptrlt;vsg::Geometrygt; findGeometry(vsg::ref_ptrlt;vsg::Nodegt; node) { // 简化实现实际应用中可能需要遍历子节点 return node.castlt;vsg::Geometrygt;(); } vsg::ref_ptrlt;vsg::Devicegt; _device; vsg::ref_ptrlt;vsg::Groupgt; _sceneRoot; vsg::ref_ptrlt;vsg::BindGraphicsPipelinegt; _highlightPipeline; std::unordered_setlt;vsg::ref_ptrlt;vsg::Nodegt;gt; _selectedNodes; std::unordered_maplt;vsg::ref_ptrlt;vsg::Nodegt;, vsg::ref_ptrlt;vsg::Nodegt;gt; _highlightNodes; };3. 在拾取回调中集成将选中管理器集成到鼠标拾取回调中#include vsg/ui/PointerEvent.h #include vsg/utils/Intersector.h // 创建选中管理器在应用初始化时 auto selectionManager SelectionManager::create(device, sceneRoot); // 鼠标点击拾取回调 auto pickCallback [camera, scene, selectionManager](vsg::PointerEvent event) { if (event.button 1 event.action vsg::ButtonEvent::PRESS) // 左键按下 { // 创建射线相交器 auto intersector vsg::LineSegmentIntersector::create(*camera, event.x, event.y); scene-accept(*intersector); // 处理拾取结果 selectionManager-gt;handlePickResult(intersector); // 可选打印拾取信息 if (!intersector-amp;gt;intersections.empty()) { autoamp;amp; hit intersector-amp;gt;intersections.front(); std::cout amp;lt;amp;lt; 拾取坐标: amp;lt;amp;lt; hit-amp;gt;worldIntersection amp;lt;amp;lt; , 距离: amp;lt;amp;lt; hit-amp;gt;ratio amp;lt;amp;lt; std::endl; } } }; // 将回调添加到事件处理器 viewer-addEventHandler(vsg::EventHandler::create(pickCallback));4. 使用示例// 完整的使用流程示例 int main() { // 1. 初始化 VSG 和场景 auto options vsg::Options::create(); auto scene createScene(); // 创建你的场景 // 2. 创建选中管理器 auto selectionManager SelectionManager::create(viewer-gt;getDevice(), scene); // 3. 设置拾取回调 viewer-gt;addEventHandler(vsg::EventHandler::create( [amp;camera, scene, selectionManager](vsg::PointerEventamp; event) { if (event.button 1 amp;amp; event.action vsg::ButtonEvent::PRESS) { auto intersector vsg::LineSegmentIntersector::create(*camera, event.x, event.y); scene-gt;accept(*intersector); selectionManager-gt;handlePickResult(intersector); } } )); // 4. 运行渲染循环 while (viewer-gt;advanceToNextFrame()) { viewer-gt;handleEvents(); viewer-gt;update(); viewer-gt;recordAndSubmit(); viewer-gt;present(); } return 0; }实现要点非侵入式高亮通过叠加独立的高亮几何体不修改原始节点的状态避免管线冲突。性能优化高亮几何体可以预创建并复用避免每帧动态创建。多选支持SelectionManager支持同时高亮多个节点可通过Ctrl点击扩展。撤销/重做选中状态可序列化便于实现撤销操作。19.7 完整示例点击打印世界坐标// 在事件处理回调里PointerEvent 的 press 事件 auto pick [](int32_t x, int32_t y) { vsg::LineSegmentIntersector intersector(*camera, x, y); scene-accept(intersector); if (!intersector.intersections.empty()) { auto hit intersector.intersections.front(); std::cout pick hit-worldIntersection \n; } };屏幕坐标x, y的原点与窗口一致左上角。若拾取偏移检查坐标系/视口是否匹配。19.8 常见问题现象原因解决永远拾取不到线段方向反了或坐标错用「相机屏幕坐标」构造器核对x,y原点命中了错误物体取了非最近交点取intersections.front()已按距离排序只拾取到外壳内部节点被遮挡遍历会收集所有相交按需筛选nodePath性能差每帧全场景拾取仅在鼠标按下时拾取或先用视锥/包围球粗筛19.9 小结射线拾取用LineSegmentIntersector(camera, x, y)scene-accept(intersector)触发intersections取结果体选择用PolytopeIntersector框选/视锥调试交点含世界坐标与nodePath高亮通过临时叠加状态/描边壳实现。19.10 延伸阅读与下一章预告第 20 章《后处理效果》拾取也可用于屏幕空间后处理遮罩例如可以将本章的拾取结果如选中节点的屏幕坐标或深度信息作为遮罩输入在后处理阶段对选中区域应用发光、描边或颜色增强等特效。第 24 章《模型查看器》把拾取做成「点击选中、拖拽旋转」本章的SelectionManager和拾取回调是基础第 24 章将在此基础上实现完整的交互逻辑包括点击选中节点、拖拽旋转模型、以及通过鼠标事件控制视图变换。第 11 章《相机与视图》拾取射线从相机出发。本章使用的LineSegmentIntersector(camera, x, y)构造器直接依赖第 11 章介绍的相机投影和视图矩阵用于将屏幕坐标转换为世界空间射线是拾取功能的核心依赖。
返回列表