Technical note
显示隐藏的快速状态切换
记录 CAD/CAE Viewer 中隐藏、显示、隔离、ShowAll 和 ResetStyle 这类高频交互的优化过程:为什么大模型下不能总是逐节点修改或全场景重建,以及如何通过 DisplayBucket、RangeTable 和 Selection Layer 控制状态恢复。
背景:显示隐藏是高频操作
在 CAD/CAE Viewer 里,隐藏、显示、隔离、ShowAll、ResetStyle 不是边缘功能。
工程用户会频繁做这些操作:
隐藏外壳,看内部结构;
隔离一个零件或一个 body;
临时隐藏选中的面或边;
调整透明度观察内部;
框选一批对象后隐藏;
修复或检查完成后 ShowAll;
材质和透明度改乱后 ResetStyle;
从工程树切换不同对象的可见状态。
这些操作看起来只是“显示状态变化”,但在大模型下很容易变成性能瓶颈。
原因是它们不仅影响渲染,还会影响拾取、高亮、工程树状态、RangeTable 语义和后续恢复路径。
所以显示隐藏优化的重点,不只是让某一次 Hide 更快,而是让整个状态系统可控。
这篇文章想记录的就是这个过程:在合批、RangeTable、Selection Layer 和空间分桶之后,显示状态应该怎么组织,才能避免每次操作都退回逐节点修改或全场景重建。
旧做法:直接切节点或改状态
旧 Viewer 中,显示状态通常和 OSG 节点直接绑定。
如果一个 face 或 edge 有自己的 Geode,那么隐藏它可以直接关节点;改颜色可以直接改节点颜色;调透明度可以直接改节点 StateSet 或材质状态。
这套路径可以简化成:
select face
-> find face node
-> set node mask / color / opacity
它直观、实现简单,也方便调试。
早期功能验证时,这种结构能快速打通工程树、拾取和三维显示联动。
但大模型会放大它的问题。
如果隐藏一个复杂对象需要逐个切换大量 face node 和 edge node,前台操作就会变慢。如果颜色、透明度、选择高亮、hover 都直接改基础节点,状态恢复就会变乱。
最典型的问题是:用户做了几轮隐藏、透明、选择、高亮之后,再执行 ShowAll 或 ResetStyle,系统必须知道每个对象应该恢复到什么状态。
旧结构里,这些状态容易混在同一层,恢复逻辑会越来越复杂。
合批之后的新问题
合批显示减少了 Geode 和 Drawable 数量,但也改变了隐藏逻辑。
旧结构里,一个 face 可以对应一个节点。隐藏它时,关掉这个节点就行。
合批之后,一个 Geometry 里可能包含很多 face。隐藏其中一个 face,不能直接关掉整个 Geometry,否则同 batch 里的其他 face 也会一起消失。
这带来新的判断:
如果整个 bucket 都要隐藏:
可以直接切 bucket 状态。
如果只隐藏 bucket 里的部分 face:
不能关整个 Geometry,
需要局部处理或重建。
如果目标规模太大:
不能阻塞前台,
需要 fast path 或 fallback。
所以合批之后,显示隐藏不再是简单的 node mask 操作,也不能每次都全场景重建。
它需要根据目标范围、bucket 覆盖情况和当前状态选择路径。
这也是大模型显示引擎里比较典型的变化:过去一个直观操作,后来会变成一套分层状态系统。
状态要分层
显示隐藏变复杂后,一个核心工程判断是:状态必须分层。
至少要区分几类状态:
Base Display State:
模型的基础颜色、材质、透明度、边显示等。
Visibility State:
当前对象、face、edge 是否隐藏或隔离。
Temporary Override:
临时颜色、透明度、局部显示覆盖。
Selection / Hover State:
选择高亮、鼠标悬停、临时提示层。
如果这些状态混在一起,就会产生污染。
例如用户先把对象改透明,再选择高亮,再隐藏,最后 ShowAll。如果选择高亮和透明度都直接写到基础颜色里,恢复时就很难判断到底应该回到原始材质、用户修改后的透明度,还是清除所有覆盖。
更稳的方式是:
基础显示保持稳定;
隐藏和隔离由 visibility state 管理;
selection 和 hover 进入 Selection Layer;
临时样式覆盖有明确生命周期。
这样 ShowAll 只恢复可见性,不应该误改基础材质。
ResetStyle 可以清理样式覆盖,但不应该残留选择高亮。
ClearSelection 应该清理 Selection Layer,但不应该重建基础 Geometry。
这些边界如果不提前分清,后续每个功能都会“顺手恢复”别的状态,最后显示状态会越来越难维护。
Fast Path:完整 bucket 直接切状态
大模型下,显示状态切换必须有 fast path。
最典型的 fast path 是完整 bucket 处理。
如果一个 DisplayBucket 里的所有 face 都要隐藏,那么不需要逐 primitive 更新,也不需要重建 Geometry。可以直接把这个 bucket 的显示状态切掉。
可以理解为:
target faces
-> locate buckets
-> classify buckets
bucket fully affected:
-> switch bucket state
bucket partially affected:
-> dirty bucket / partial rebuild / fallback
完整 bucket 的好处是路径短,恢复也清晰。
ShowAll 时把这些 bucket 的状态恢复即可,不需要重新生成所有 Geometry。
这类 fast path 对隔离也很重要。
隔离一个对象时,很多 bucket 可能完整在目标外或目标内。完整在外的 bucket 可以直接隐藏,完整在内的 bucket 可以保留。只有混合 bucket 才需要更细的处理。
这也是空间分桶对显隐优化的直接价值:它给 fast path 提供了可判断的局部边界。
局部目标:dirty bucket 和 partial rebuild
如果目标只影响 bucket 的一部分,就不能简单关掉整个 bucket。
这时需要 dirty bucket。
dirty bucket 的意义是把局部变化限制在受影响的 bucket 内:
hide selected faces
-> find affected bucket ids
-> mark dirty buckets
-> rebuild or patch affected buckets
-> update RangeTable
这样隐藏少量 face 不会退化成全场景重建。
partial rebuild 可以重建受影响 bucket 的 Geometry。partial replacement 可以把旧 bucket 批次替换成新的批次,同时保留其他 bucket 不变。
不过这里最难的不是生成新 Geometry,而是一致性:
RangeTable 要更新到新的 primitive range;
Selection Layer 里的旧高亮要清理;
hover 不能继续指向旧对象;
ShowAll 要能恢复基础状态;
edge display 也要和 face 可见性保持一致。
所以局部隐藏和局部重建不是单独的渲染优化,而是一套状态一致性问题。
画面更新只是第一步。更新后,拾取、高亮、恢复、边显示和基础状态都要对得上。
ResetStyle 和 ShowAll 的恢复路径
显示状态切换里,恢复路径往往比隐藏路径更重要。
Hide 失败,用户能看到对象没隐藏。
ShowAll 或 ResetStyle 失败,问题可能更隐蔽:某些对象颜色不对、透明度残留、边线状态不一致、选择高亮还在、拾取结果和可见状态不匹配。
所以恢复路径要明确区分:
ShowAll:
恢复可见性和隔离状态,
不应该破坏基础材质。
ClearIsolate:
清除隔离目标,
恢复隔离影响的 bucket。
ResetStyle:
清理颜色、透明度、材质覆盖,
按规则恢复基础样式。
ClearSelection:
清理 Selection Layer,
不应该重建 base display。
这几个操作如果混在一起,就会出现“ShowAll 顺便把颜色也重置了”,或者“ResetStyle 把隐藏状态也弄乱了”这类问题。
在大模型里,恢复路径还要尽量避免全量重建。
能恢复 bucket 状态,就不要重建 Geometry;能清理临时层,就不要动 base display;只有混合状态或局部替换失效时,才进入更重的 rebuild 路径。
这类判断不一定显眼,但它决定了 Viewer 是否能在长时间交互后保持稳定。
显隐、高亮和透明度不能互相污染
显示状态切换最容易出问题的地方,是隐藏、透明度和高亮叠加。
比如一个对象被设置为半透明,又被选中高亮,然后被隐藏。
此时系统至少要回答:
隐藏后是否还能被拾取?
高亮层是否需要清理?
ShowAll 后透明度是否应该保留?
ResetStyle 后透明度是否恢复默认?
ClearSelection 是否应该影响可见性?
这些问题没有一个放之四海皆准的答案,取决于产品语义。
但显示引擎必须让这些状态在结构上可区分。
一个比较稳的分层是:
Base material:
模型默认或用户持久设置。
Visibility:
hidden / isolated / visible。
Selection Layer:
selected / hover / temporary feedback。
Style override:
颜色、透明度、材质覆盖。
有了分层,状态恢复才有边界。
没有分层,每个功能都会试图“顺手恢复”别的功能,最后状态越来越乱。
和 RangeTable 的关系
RangeTable 让合批后的 Geometry 仍然能找回 CAD 语义。
显示隐藏会直接影响 RangeTable 的使用方式。
隐藏对象后,拾取不能再返回隐藏目标。隔离模式下,拾取也应该只接受当前可见范围内的对象。局部重建后,旧 primitive range 可能失效,RangeTable 必须同步更新。
所以 visibility state 不能只是一层视觉开关。
它还要影响交互语义。
可以理解为:
Display state:
控制是否画出来。
RangeTable + visibility filter:
控制是否能被解释为有效拾取目标。
否则就会出现画面上看不到,但鼠标还能点到;或者画面已经更新,但拾取返回旧 face 的问题。
这类问题在 CAD/CAE Viewer 里尤其危险,因为用户的后续操作通常依赖拾取结果。拾取语义错了,后续高亮、测量、修复、边界设置都有可能跟着错。
和 Selection Layer 的关系
Selection Layer 负责选择、hover 和临时高亮。
显示隐藏会影响它的生命周期。
隐藏一个对象时,如果它正被选中,高亮应该怎么处理?通常至少不能继续显示一个已经隐藏的对象。
ClearSelection 时,只应该清理选择层,不应该改变隐藏状态。
ShowAll 时,如果恢复了可见性,是否恢复选择高亮,取决于产品语义。但无论如何,Selection Layer 不能引用已经被局部重建替换掉的旧 Geometry。
所以状态切换里要有清理动作:
hide / isolate:
update visibility
clear or refresh affected highlights
partial rebuild:
remove stale highlight batch
rebuild highlight if needed
clear scene:
cancel pending selection highlight
clear temporary layers
这也是为什么 Selection Layer 不能散落在业务流程中,而要有统一入口和生命周期管理。
只要临时层没有清理干净,显示状态就很容易在多次操作后出现残留。
和 DisplayBucket 的关系
DisplayBucket 是显示隐藏 fast path 的基础。
没有 bucket 时,合批 Geometry 太大,局部隐藏容易变成全局操作。
有了 bucket 后,可以把目标映射到受影响 bucket:
完整 bucket 直接切状态;
混合 bucket 进入 dirty bucket;
局部变化走 partial rebuild 或 fallback;
ShowAll 恢复被修改的 bucket;
大范围操作按 chunk 分批处理。
bucket 粒度会直接影响状态切换效果。
bucket 太小,Geode / Drawable 数量又会上升,接近旧问题。
bucket 太大,局部隐藏和高亮不够轻,dirty bucket 的收益下降。
因此显示隐藏优化和空间分桶不是两件事。
前者依赖后者给出合适的局部边界。
边界和代价
快速状态切换不是免费得到的。
首先,它需要更多状态记录。
哪些 bucket 被隐藏过,哪些发生过局部替换,哪些样式被覆盖,哪些选择高亮还在,都要有明确归属。
其次,它需要多条路径。
完整 bucket 可以快,混合 bucket 要谨慎,大范围目标要分块,异常情况要 fallback。路径越多,一致性越难维护。
再次,恢复路径必须严格。
ShowAll、ClearIsolate、ResetStyle、ClearSelection 都是高频操作,它们不能互相污染。
最后,fast path 不能牺牲语义正确性。
隐藏对象后拾取仍然命中,就是错误;ResetStyle 后材质不对,也是错误;局部替换后 RangeTable 没同步,同样是错误。
所以这类优化的标准不是“某条路径最快”,而是:
常见操作足够快;
恢复路径足够可靠;
显示语义不会乱。
小结
在 CAD/CAE Viewer 中,隐藏、显示、隔离、ShowAll、ResetStyle 是高频交互,不是辅助功能。
旧 Viewer 直接切节点、改颜色、改透明度,在小模型上很合理。但大模型和合批显示之后,这种做法会遇到两个问题:逐节点更新太慢,状态恢复容易污染。
新的工程判断是:
显示状态要分层,
操作路径要分级。
基础显示、可见性、临时覆盖、选择高亮要分开。
完整 bucket 走 fast path,局部目标走 dirty bucket 或 partial rebuild,大范围操作必要时走 hint 或 fallback。
ShowAll 和 ResetStyle 要优先保证恢复路径清晰。
这部分优化连接了 DisplayBucket、RangeTable、Selection Layer 和 partial rebuild。它的目标不是让某一次 Hide 看起来更快,而是让大模型中的显示状态切换长期保持可控。
下一篇可以继续讲 CAD 拾取系统。因为当显示状态、RangeTable 和 Selection Layer 都拆开之后,鼠标点击、hover、边拾取、对象提升、框选和特征点捕捉也需要一套更清晰的语义解析流程。