松散八叉树、osg::Polytope 与 LRU 缓存
第三篇松散八叉树、osg::Polytope 与 LRU 缓存摘要本文结合SceneObjectIndexManager与SceneLooseOctree说明松散八叉树如何管理要素与临时对象结合FeatureDispatcher中osg::Polytope与View*Proj 逆构造视锥说明对queryFeatureObjs/queryTempObjs的调度前粗可见集并概括FeatureLRUCache、密集点云 DPCLRU、PyrmidImgLRUCache* 在节点子图与图像数据上的分工。1. 松散八叉树在工程中的位置SceneObjectIndexManager内维护SceneLooseOctree等结构expandLooseOctree、addSceneObjectToLooseOctree等实现根扩展与子树挂接。临时对象若初始无有效包围盒可先标记待加载后入树有盒则addTempSceneObjectWorkEntity直接入索引树。作用拾取/查询/调度候选集的空间一级过滤不替代 OSG 绘制级裁剪。2. 视锥osg::Polytope 与双查询接口在FeatureDispatcher的更新路径中典型步骤为viewfrustum.setToUnitFrustum()ViewMatrix * ProjMatrix合成矩阵transformProvidingInverse到世界空间SceneObjectIndexManager::queryFeatureObjs与queryTempObjs各返回一列表。头文件中query* 的注释为「用于调度时查询可见的…」与第三篇主题一致。3. 流程图从相机到「可见对象再调度」CameraParamsView * Projosg::Polytope 视棱台queryFeatureObjsqueryTempObjs可见列表合并dispatchSceneObjectsForAppend 等4. LRU 缓存分层缓存键/值思想说明FeatureLRUCache以要素场景对象相关 key 缓存osg::TransformFeatureDispatcher可配默认容量/开关缓解重复符号化/节点构建。DPCLRUCache密集点云层 id、level、code 等命中复用MatrixTransform未命中再读块数据。PyrmidImgLRUCache金字塔图 key值内OpenCV Mat与点云/金字塔体渲染相关。与「分级精度」关系分级决定要哪一级数据LRU决定该级/该块在内存中是否复用二者相乘做性能。5. 类图空间索引 查询概念索引/查询query* 入参SceneObjectIndexManagerSceneLooseOctreeSceneObjectosg_Polytope八叉树管空间归属Polytope 管当前帧粗可见性LRU 管时间局部性三者不互相替代。先 query 再 append 调度对大场景要素的意义避免全量对象参与加载决策。6典型踩坑1. 认为「进八叉树 能拾取、能参与所有查询」对临时对象若初始包围体无效资源未加载、盒未算好会走isNeedUpdateQuadtreeOrOctree一类路径先不入树待加载成功、盒有效再补登记。工程里在屏选/求交时除了queryCanBePickTempObjs走树之外还显式再扫一遍「因无有效包围盒而从未进八叉树的临时对象」列表。若你只做树查询、忘记这条旁路会出现模型已显示但点不中或拾取结果缺对象。这是数据状态与空间索引不同步的经典坑。2. LOD 与八叉树用「当前子图算出来的盒」硬塞回树在屏幕拾取对临时模型的补充逻辑里注释写得很直若已是 LOD 一类节点包围盒不固定不能用当前算出的盒去重新加入八叉树相关行甚至被注释成「先不 setBound、不 removeadd 回场景」。若强行用单帧的包围盒更新树会造成树结构抖动、查询错误或与调度状态打架。这属于分级 / 动态范围与静态空间划分混用的坑。3. 视锥体构造矩阵顺序与transformProvidingInverse的含义调度里用setToUnitFrustumViewMatrix * ProjMatrix的逆把标准视棱台变到世界/一致的空间中再交给queryFeatureObjs/queryTempObjs。若左右手系、行主列主、先 View 后 Proj 还是反过来写错表现是**「粗可见集」全空或全满**随后加载/调度全部反常。这坑通常出在从别处抄矩阵时未与当前工程的CameraParams定义对齐。4. 把「八叉树可见」等同「GPU 要画」树 Polytope粗筛是为业务调度/加载服务的OSG仍有自己的裁切和 LOD。排障时要么分两层看业务可见 vs 绘制要么加日志区分index 层与cull 层。5. 视口增删、八叉树、图层要素侧注释提到视口增删时会从八叉树和图层里同步移除部分对象相机一动可能又加回——并标明为扩展预留、实现待收束。若你在内存/闪烁上感到别扭要意识到这里存在**「粗调度策略」与「产品期望常驻」** 的潜在冲突不全是 LRU 或树深度的问题。6. LRU键变缓存失效泄漏感若同一逻辑对象在图层级、块编码、层级上因状态变化换了缓存键会表现为反复读盘、却命中不了若键过粗又会导致大颗粒淘汰误伤别的块。这属于键设计坑和第三篇的「空间与时间要一致」直接相关。7参数调优心得1. 松散八叉树深度与根范围过深节点管理开销、锁竞争和分裂次数上升过浅叶子里对象过多视锥查询退化成「半暴力」。工程侧若有maxDepth、根扩展/收缩等入口调参要配合单场景对象尺度分布和视口尺度做折中。根盒扩展策略若偏激进空场景里树也很胖第一次加载的代价大若偏保守对象一出界就动根要关注锁与全树更新频率。2. 与「盒何时有效」打配合心得入树/更新树尽量与资源加载后重算包围体同一临界区或同一回调阶段并统一isNeedUpdateQuadtreeOrOctree的置位/清除否则调树参数也救不了时序性偶发问题。临时大模型在入树前的拾取、框选要接受走旁路列表的额外成本或在产品上弱化为「载完才能精拾取」。3. 视锥查询Polytope与交类型SceneObjectIndexManager的query系* 带交型枚举如相交/包含的语义若工程里有区分从视」调度若过严会少载过松会多载。调的是「业务能接受多预载多少」而不是一味追求数学严。屏选用屏幕子区域构造PolytopeVPW逆变换时窗口坐标上下颠倒、左右界要与当前视口约定一致。4.FeatureLRUCache容量与开关FeatureDispatcher侧对要素节点缓存有默认容量与开关接口。容量过大内存与单帧淘汰成本上升过小重复符号化/挂接CPU 和锁开销上升。全关缓存适合排障或强一致刷新的调试阶段不适合作为默认发版。心得以典型场景同屏对象数、同符号重复度压一版再定容量档。5. 密集点云 / 分块类 LRU键里通常含层、级、块编码与瓦片/分级加载一致时命中率最高。调优时优先保证生成键的规则稳定再动容量避免在键里混进会每帧变的相机相关量除非你就想做帧缓存但那一般是另一套策略。6. 金字塔/图像 LRUPyrmidImgLRUCache与OpenCV 矩阵生命周期绑定心得释放路径要把clearUp/ release跑全避免在高分辨率屏上多图层时Mat 堆满内存。容量往往按单张图估计 × 可并行层数留余量。7. 调优观第三篇的旋钮不是单点而是三角关系八叉树参数管空间分桶是否合适视锥查询语义管本帧进调度集合是否合适LRU管时间局部性三者只调其一往往指标改善有限联动看日志才像资深实现者。8. 关键词SceneObjectIndexManager、SceneLooseOctree、osg::Polytope、queryFeatureObjs、queryTempObjs、FeatureLRUCache、DPCLRUCache、PyrmidImgLRUCache