第 6 章:vtkRenderer 与 vtkCamera
学习目标
本章把“场景中有什么”和“从哪里看”分开。读完后,你应该能添加可见对象、取得活动相机、手动设置视角,理解重置相机与重置裁剪范围的区别,并能说明透视投影与平行投影各适合观察什么。
6.1 Renderer 管理可见内容
在第 2 阶段示例里,<code>vtkGenericRenderWindow</code> 已经创建了一个 Renderer:
const renderer = genericRenderWindow.getRenderer();
随后两条 Source 管线各自生成球体和立方体,两个 Actor 被加入同一个 Renderer:
renderer.addActor(sphereActor);
renderer.addActor(cubeActor);
renderer.resetCamera();
renderWindow.render();
<code>renderer.addActor(…)</code> 是表面对象常用的加入方式。后面学习体渲染时,你会使用 <code>renderer.addVolume(…)</code>。Renderer 还管理背景、灯光以及活动相机;本示例的背景由 Generic 对象在初始化时设置。
一个 Actor 有没有被加入 Renderer,与它是否已经持有 Mapper 是两件事。Actor 即使有正确数据和颜色,若没有加入正在显示的 Renderer,也不会出现在这幅画面中。
6.2 相机由三个向量描述主要视角
取得当前相机:
const camera = renderer.getActiveCamera();
先抓住三个基本量:
- <code>position</code>:相机在世界坐标中的位置。
- <code>focalPoint</code>:相机正在看的目标位置。
- <code>viewUp</code>:画面“向上”大致对应的世界方向。
相机从 <code>position</code> 朝 <code>focalPoint</code> 看;<code>viewUp</code> 决定画面怎样旋转。若你只改相机位置而忘记焦点,可能得到一个意外的观察方向。若 <code>viewUp</code> 与观察方向几乎平行,视角也可能不稳定。
本示例中的“斜视图”按钮显式设置这三个量:
camera.setPosition(4, 3, 6);
camera.setFocalPoint(0, 0, 0);
camera.setViewUp(0, 1, 0);
renderer.resetCameraClippingRange();
renderWindow.render();
你已经熟悉 Three.js 相机,因此这里的重点是这些操作作用于 Renderer 的活动相机,而不是作用于 Actor 的几何数据。
6.3 resetCamera 与 resetCameraClippingRange
这两个方法名字相近,但解决的问题不同。
<code>renderer.resetCamera()</code> 根据当前可见对象的边界重新取景,调整焦点、相机位置与显示尺度,使内容进入视野。它适合在添加或移除对象后调用,也适合提供“回到完整数据”按钮。应先把要观察的 Actor 加入 Renderer,再调用它。
<code>renderer.resetCameraClippingRange()</code> 主要重新计算相机的近、远裁剪范围。它不负责把相机重新对准对象。手动移动相机之后,若模型的一部分突然被近裁剪面或远裁剪面切掉,可以考虑调用它。
| 操作 | 主要作用 | 典型时机 |
|---|---|---|
| <code>resetCamera()</code> | 重新取景,使可见对象进入画面 | 初次加入对象、点击“显示全部” |
| <code>resetCameraClippingRange()</code> | 更新近、远裁剪范围 | 手动改相机后出现裁剪问题 |
不要把 <code>resetCamera()</code> 当成每次鼠标操作后的例行调用。它会覆盖用户刚调好的视角。默认交互样式本身会管理常规相机操作及所需渲染。
6.4 透视与平行投影
透视投影具有近大远小的效果,适合直观观察三维形状。平行投影中,物体尺寸不随与相机的距离按透视规律缩小,在医学影像、工程图和对齐比较中经常有用。
本示例提供了切换按钮:
camera.setParallelProjection(!camera.getParallelProjection());
renderer.resetCameraClippingRange();
renderWindow.render();
平行投影下,<code>parallelScale</code> 控制视图的纵向尺度;它不是“镜头焦距”。<code>resetCamera()</code> 会基于可见对象重新设置合适的显示尺度。如果切换投影后画面显得过大或过小,先检查当前相机尺度与位置,再决定是否重置相机。
注意:切换投影不会更改 Source、Mapper 或 Actor。球和立方体仍是同一份数据,只是投影到屏幕的方法变了。
6.5 Renderer 的边界与相机的边界
可以把 Renderer 理解成“这幅画面要显示哪些对象,并用哪个相机看它们”,但它不是数据处理器。裁剪数据、生成等值面或修改点上的物理值,不应通过 Camera 完成。
同样,<code>actor.setPosition(…)</code> 与 <code>camera.setPosition(…)</code> 在屏幕上都可能让物体左右移动,却表达不同的操作:
- 移动 Actor:改变一个可见对象相对场景的位置。
- 移动 Camera:改变观察所有对象的视角。
这一点在同屏显示多个数据集时尤其重要。若只想移动一个对象,不应移动相机;若想从另一侧观察全部对象,不应逐个移动 Actor。
6.6 排查视角问题的顺序
画面里找不到对象时,先确认它已加入 Renderer,再看相机:
- <code>renderer.getActors()</code> 是否包含预期 Actor?
- 添加对象后是否调用过 <code>resetCamera()</code>?
- 相机的 <code>position</code> 与 <code>focalPoint</code> 是否合理?
- 是否只是裁剪范围太窄?必要时调用 <code>resetCameraClippingRange()</code>。
- 对象是否被设置为不可见,或尺寸远小于当前观察范围?
第 2 阶段示例下方实时显示相机位置、焦点与投影方式。你可以先点“斜视图”,再用鼠标旋转,观察这些数值如何变化。
本章小结
Renderer 管理画面中的对象与活动相机。Camera 决定观察位置、目标方向和投影方式。<code>resetCamera()</code> 用于重新取景,<code>resetCameraClippingRange()</code> 主要处理近远裁剪。下一章说明鼠标事件怎样经过 Interactor 和交互样式改变相机。