Skip to content

VTK 设计模式:装饰模式

系列:Qt / VTK 设计模式 · VTK 06/10
参考:vtkLODActorvtkProp


引子

在渲染包含数百万个高精三角面片的复杂 3D 模型时,鼠标旋转交互往往会带来严重的卡顿。我们希望在交互拖拽时使用低精度点云或粗模渲染以维持 60 FPS,而在静止停下来时自动恢复高清渲染,同时保持渲染器 vtkRenderer 对该 Actor 的调用接口完全不变

解决此问题的经典设计模式是装饰模式(Decorator Pattern)动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式相比生成子类更为灵活。在 VTK 中,vtkLODActor 是装饰模式的典型代表,它在对外保持 vtkActor 接口的前提下,包装叠加了基于帧率和交互状态的动态多细节(LOD)渲染行为。


核心痛点与 GoF 装饰结构

痛点场景:通过子类继承扩展职责的爆破

cpp
// 坏方案:为各种增强组合创建不同的子类
class FastActor : public vtkActor {};
class TexturedFastActor : public FastActor {};
class ShadowedTexturedFastActor : public TexturedFastActor {};
// 组合数量引发子类爆炸 (Class Explosion)
  • 静态编译僵化:无法在运行时根据用户的交互状态动态给对象“戴上”或“脱下”功能包装。
  • 继承爆炸:功能组合繁多时,派生类数量呈乘积级增长。

GoF 装饰模式结构映射

GoF 装饰角色VTK 对应实现职责说明
抽象构件 (Component)vtkProp / vtkActor定义了场景中可渲染对象的基本接口 (RenderOpaqueGeometry(), SetVisibility())
具体构件 (ConcreteComponent)vtkActor原始的物理渲染对象,持有单套 Mapper 与 Property
装饰角色 (Decorator)vtkLODActor / vtkFollower持有构件接口,对外表现为 Component,内部包装叠加了额外的渲染控制策略

底层原理:LOD 动态包装与渲染拦截

vtkLODActor 继承自 vtkActor,内部包装了多个不同层次的 vtkMapper(或 vtkPolyData):

  1. 接口一致性:对 vtkRenderer 而言,vtkLODActor 就是一个普通的 vtkActor,直接 renderer->AddActor(lodActor) 即可。
  2. 交互拦截:在渲染帧准备阶段,vtkLODActor 监听 vtkRenderWindowInteractorStartInteractionEventEndInteractionEvent,动态决定切换哪一个底层 Mapper 进行实际绘制。

完整代码示例

使用 vtkLODActor 实现高性能交互装饰渲染

cpp
#include <vtkNew.h>
#include <vtkSmartPointer.h>
#include <vtkSphereSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkLODActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>

int main() {
  // 1. 创建高精度模型源
  vtkNew<vtkSphereSource> highResSource;
  highResSource->SetThetaResolution(100);
  highResSource->SetPhiResolution(100);

  vtkNew<vtkPolyDataMapper> highResMapper;
  highResMapper->SetInputConnection(highResSource->GetOutputPort());

  // 2. 创建低精度模型源(用于交互时的快速渲染)
  vtkNew<vtkSphereSource> lowResSource;
  lowResSource->SetThetaResolution(10);
  lowResSource->SetPhiResolution(10);

  vtkNew<vtkPolyDataMapper> lowResMapper;
  lowResMapper->SetInputConnection(lowResSource->GetOutputPort());

  // 3. 构建装饰器 Actor (vtkLODActor)
  vtkNew<vtkLODActor> lodActor;
  // 默认渲染使用高精度 Mapper
  lodActor->SetMapper(highResMapper);
  
  // 为装饰器叠加低精度 LOD 策略
  lodActor->AddLODMapper(lowResMapper);

  // 设置自动点云/极简 LOD 的触发阈值
  lodActor->SetNumberOfCloudPoints(1000);

  // 4. 渲染管线接入(渲染器透明感知不到 LODActor 与普通 Actor 的区别)
  vtkNew<vtkRenderer> renderer;
  renderer->AddActor(lodActor);
  renderer->SetBackground(0.1, 0.2, 0.3);

  vtkNew<vtkRenderWindow> renWin;
  renWin->AddRenderer(renderer);

  vtkNew<vtkRenderWindowInteractor> iren;
  iren->SetRenderWindow(renWin);

  renWin->Render();
  iren->Start();

  return 0;
}

易混淆模式对比

设计模式核心目的区别说明
装饰模式 (Decorator)增强职责保持与被包装对象相同/兼容接口,透明叠加新行为 (如 vtkLODActor)
代理模式 (Proxy)控制访问保持相同接口,但重点在于控制访问权限、懒加载或网络隔离
适配器模式 (Adapter)转换接口改变物理接口,使原本不兼容的接口能够协同工作

最佳实践与踩坑避坑

  1. 避免几何切换时的视角跳动 (Popping)
    • 装饰器中挂载的多个 LOD 级别模型必须具有一致的中心点(Center)与包围盒(Bounding Box),否则在鼠标拖拽停下的瞬间会产生肉眼可见的错位抖动。
  2. vtkFollower 告示牌装饰器
    • VTK 中另一个经典的装饰 Actor 是 vtkFollower。它装饰了普通 vtkActor 的绘制过程,在每次渲染时自动调整自身旋转角度,使其始终正对摄像机镜头(Billboard 效果),常用于 3D 场景中的文字标签标注。

重点与注意

重点vtkLODActor 是装饰模式的经典实现,对外表现为普通 vtkActor,内部动态切换高低精度 Mapper。
重点:装饰模式的核心是用组合代替继承,在运行时透明地赋予对象附加职责。
注意vtkLODActor 只是改变渲染阶段的 Mapper 策略,并不修改底层 vtkPolyData 的物理网格数据(若要物理降采样请使用 vtkDecimatePro 过滤器)。


小结

VTK 装饰模式实现了渲染性能与呈现质量的动态平衡,保持了图形管线的高内聚与低耦合。

延伸阅读

基于 VitePress 强力驱动 | 记录技术与生活