6.交互与拾取
第四阶段:交互与拾取
前面我们只会写死窗口,模型只能默认左键旋转、滚轮缩放。本阶段搞懂:交互器样式、拾取(鼠标点击拿到点ID /面片ID)、选中高亮。
4.1 交互核心对象回顾
vtkRenderWindowInteractor interactor; // 交互总入口:接收鼠标键盘消息
vtkInteractorStyle // 交互行为策略:决定鼠标按下之后做什么动作
vtkRenderWindowInteractor 负责接收系统鼠标、键盘事件,但它自己不知道怎么处理这些事件,真正定义“左键干什么、右键干什么”的是 vtkInteractorStyle。
默认绑定的是 vtkInteractorStyleTrackballCamera,就是我们前面demo一直用的行为:
- 鼠标左键拖拽:旋转相机
- 鼠标右键拖拽:平移相机
- 鼠标滚轮:缩放相机
注意:是相机在动,不是模型网格数据在内存里移动。只是看的角度变了,vtkPolyData点坐标完全不变。 和
vtkTransformFilter平移有本质区别:
- 交互拖拽:相机变换,数据不变
- TransformFilter:真实修改所有点的坐标,数据本身改变
更换交互样式代码片段
#include <vtkInteractorStyleTrackballCamera.h>
// 创建交互样式对象
vtkSmartPointer<vtkInteractorStyleTrackballCamera> style =
vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();
interactor->SetInteractorStyle(style); //把样式绑定给交互器
VTK有多种style,比如
vtkInteractorStyleImage专门给图像使用;我们三维几何几乎一直用TrackballCamera。
4.2 拾取机制(Pick,非常重要,工业软件高频)
拾取:鼠标点击屏幕,反算出点击到三维模型上的哪个点、哪个面片Cell。
两类最常用拾取器:
vtkCellPicker:拾取面片Cell,拿到点击到的面片ID(最常用)vtkPointPicker:拾取顶点Point,拿到点击到的点ID
拾取工作流程: 鼠标点击屏幕坐标 → 拾取器做射线求交 → 和场景里Actor模型碰撞检测 → 返回命中的CellId / PointId。
关键接口
picker->Pick(屏幕x,屏幕y,0, renderer);
// Pick执行完成之后,就可以获取结果
picker->GetCellId(); // 获取命中面片ID,-1代表没有命中任何物体
picker->GetPointId(); // 获取命中点ID
picker->GetPickPosition(); // 获取三维空间交点坐标(x,y,z)
picker->GetActor(); // 获取被点中的Actor
⚠️注意:Pick函数参数是屏幕像素坐标,不是三维坐标。
4.3 完整最小拾取Demo(只看逻辑)
功能:鼠标左键点击,打印输出命中的面片ID和三维交点坐标。 需要使用观察者回调,监听鼠标点击事件。
#include <vtkSmartPointer.h>
#include <vtkSphereSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkCellPicker.h>
#include <vtkCommand.h>
#include <iostream>
// 自定义回调类:监听鼠标点击事件
class PickCallback : public vtkCommand
{
public:
static PickCallback* New()
{
return new PickCallback;
}
virtual void Execute(vtkObject* caller, unsigned long eventId, void* vtkNotUsed(callData)) override
{
vtkRenderWindowInteractor* interactor = static_cast<vtkRenderWindowInteractor*>(caller);
int clickX, clickY;
interactor->GetEventPosition(clickX, clickY); //拿到鼠标点击屏幕像素坐标
vtkSmartPointer<vtkCellPicker> cellPicker = vtkSmartPointer<vtkCellPicker>::New();
cellPicker->SetTolerance(0.0001);
// 执行拾取
cellPicker->Pick(clickX, clickY, 0, interactor->GetRenderWindow()->GetRenderers()->GetFirstRenderer());
int cellId = cellPicker->GetCellId();
if(cellId >= 0)
{
std::cout << "✅命中面片ID:" << cellId << std::endl;
double pos[3];
cellPicker->GetPickPosition(pos);
std::cout << "交点三维坐标 X:" << pos[0] << " Y:" << pos[1] << " Z:" << pos[2] << std::endl;
}
else
{
std::cout << "❌没有点到模型" << std::endl;
}
}
};
int main()
{
// --------生成球体模型--------
vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New();
sphereSource->SetRadius(4);
sphereSource->SetThetaResolution(30);
sphereSource->SetPhiResolution(30);
sphereSource->Update();
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputData(sphereSource->GetOutput());
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper.Get());
actor->GetProperty()->SetColor(0.2,0.7,0.2);
// --------渲染基础管线--------
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderer->AddActor(actor);
renderer->SetBackground(0,0,0);
vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
renderWindow->AddRenderer(renderer);
renderWindow->SetSize(800,600);
vtkSmartPointer<vtkRenderWindowInteractor> interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
interactor->SetRenderWindow(renderWindow);
vtkSmartPointer<vtkInteractorStyleTrackballCamera> style = vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();
interactor->SetInteractorStyle(style);
// =========绑定回调:监听鼠标左键按下事件=========
vtkSmartPointer<PickCallback> pickCb = vtkSmartPointer<PickCallback>::New();
interactor->AddObserver(vtkCommand::LeftButtonPressEvent, pickCb);
interactor->Initialize();
interactor->Start();
return 0;
}
运行现象
窗口绿色球体,鼠标左键点击模型表面,控制台打印面片ID和三维交点;点击空白处输出没有命中模型。
代码关键点拆解
vtkCommand观察者模式:VTK标准事件回调机制。AddObserver(事件类型,回调对象):监听某个事件发生,自动执行Execute函数。
-
vtkCommand::LeftButtonPressEvent:鼠标左键按下事件。后面我们会专门讲Observer观察者机制,这里先看懂用途。
interactor->GetEventPosition(clickX,clickY)获取鼠标点击的屏幕像素坐标。vtkCellPicker::Pick(x,y,0,renderer)执行拾取计算。
- 返回
cellId == -1:未命中;>=0就是命中面片索引。
拾取器的Tolerance容差:点击点离面片小于这个距离才算命中。
4.4 重要区分(极易混淆)
- 相机交互拖拽(鼠标旋转缩放):只改相机,底层vtkPolyData数据完全不动。
- 拾取Pick:屏幕坐标 → 反向求交算出三维模型上的点/面片ID,只读,不修改任何数据。
- TransformFilter:真正修改点坐标,模型数据本身发生改变。
本节小结
vtkRenderWindowInteractor接收事件;vtkInteractorStyle定义鼠标行为。- 默认
vtkInteractorStyleTrackballCamera,旋转缩放是相机变换,网格数据不变。 vtkCellPicker拾取面片,vtkPointPicker拾取顶点;Pick()执行射线求交,拿到ID与三维交点。- 使用
vtkCommand + AddObserver监听鼠标键盘事件。
新手坑
- 分不清相机变换 vs 模型真实坐标变换;
- 拾取拿到的是屏幕像素坐标,不是三维坐标;
- 忘记判断cellId==-1,直接使用ID导致数组越界崩溃。

浙公网安备 33010602011771号