VTK 设计模式:装饰模式
系列:Qt / VTK 设计模式 · VTK 06/10
参考:vtkLODActor、vtkProp
引子
在渲染包含数百万个高精三角面片的复杂 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):
- 接口一致性:对
vtkRenderer而言,vtkLODActor就是一个普通的vtkActor,直接renderer->AddActor(lodActor)即可。 - 交互拦截:在渲染帧准备阶段,
vtkLODActor监听vtkRenderWindowInteractor的StartInteractionEvent与EndInteractionEvent,动态决定切换哪一个底层 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) | 转换接口 | 改变物理接口,使原本不兼容的接口能够协同工作 |
最佳实践与踩坑避坑
- 避免几何切换时的视角跳动 (Popping):
- 装饰器中挂载的多个 LOD 级别模型必须具有一致的中心点(Center)与包围盒(Bounding Box),否则在鼠标拖拽停下的瞬间会产生肉眼可见的错位抖动。
vtkFollower告示牌装饰器:- VTK 中另一个经典的装饰 Actor 是
vtkFollower。它装饰了普通vtkActor的绘制过程,在每次渲染时自动调整自身旋转角度,使其始终正对摄像机镜头(Billboard 效果),常用于 3D 场景中的文字标签标注。
- VTK 中另一个经典的装饰 Actor 是
重点与注意
重点:
vtkLODActor是装饰模式的经典实现,对外表现为普通vtkActor,内部动态切换高低精度 Mapper。
重点:装饰模式的核心是用组合代替继承,在运行时透明地赋予对象附加职责。
注意:vtkLODActor只是改变渲染阶段的 Mapper 策略,并不修改底层vtkPolyData的物理网格数据(若要物理降采样请使用vtkDecimatePro过滤器)。
小结
VTK 装饰模式实现了渲染性能与呈现质量的动态平衡,保持了图形管线的高内聚与低耦合。
延伸阅读