
相机漫游实现:基于 CameraManipulator 自定义匀速环绕相机
在 OpenSceneGraph(OSG)三维开发中,相机漫游是最核心的交互能力之一。
它指虚拟相机按照预设的轨迹(如圆周、直线、自定义路径)在场景中自动运动,实现模型环绕观察、场景巡航、自动演示等效果。
本文将基于新版 OSG 标准的 CameraManipulator ,从零实现一个稳定、匀速、环绕原点 的相机漫游控制器,并结合完整可运行代码,深入讲解类继承关系、相机矩阵原理、圆周旋转数学实现,彻底解决黑屏、模型丢失、旋转异常等问题。
核心基础
OSG 3.x 之后,所有相机控制器必须继承 osgGA::CameraManipulator,它是官方唯一标准的相机操控抽象基类。
完整继承链
osg::Referenced // 智能指针内存管理(OSG 所有对象基类)
↓
osg::Object // 基础对象属性
↓
osgGA::GUIEventHandler // 事件处理器(接收鼠标/键盘/帧事件)
↓
osgGA::CameraManipulator // 【抽象相机控制器】我们自定义类的父类
↓
自定义类:PathCamera // 本文实现的环绕漫游相机
必须实现的核心接口
CameraManipulator 是抽象类,子类必须重写 4 个纯虚函数:
getMatrix():返回相机自身变换矩阵(最核心)getInverseMatrix():返回逆矩阵(渲染管线必需)setByMatrix():通过矩阵设置相机(本文无需使用,空实现)setByInverseMatrix():通过逆矩阵设置相机(本文无需使用,空实现)
同时继承 GUIEventHandler 的 handle() 函数,处理帧更新事件,驱动相机运动。
相机漫游原理
本文实现的是水平圆周环绕漫游:
- 相机在 X-Y 平面 绕原点
(0,0,0)做匀速圆周运动 - 相机高度固定(Z=20)
- 相机始终看向原点(保证模型永远在画面中心)
- 使用三角函数
sin / cos计算圆周坐标 - 严格遵循 OSG 相机矩阵规则,避免模型看不见
坐标系说明(OSG 默认右手系):
- X 轴:左右
- Y 轴:前后(深度)
- Z 轴:上下
完整可运行代码
cpp
#include <osgViewer/Viewer>
#include <osg/Group>
#include <osgDB/ReadFile>
#include <osgGA/CameraManipulator>
#include <iostream>
#include <cmath>
using namespace osg;
using namespace osgGA;
using namespace osgViewer;
// 自定义环绕漫游相机:继承新版标准 CameraManipulator
class PathCamera : public osgGA::CameraManipulator
{
public:
// 构造函数:初始化时间为0
PathCamera() : _time(0) {}
// 事件处理函数:继承自 GUIEventHandler,每一帧都会调用
bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) override
{
// 判断事件:是否为【帧更新事件】(每一帧触发一次)
if (ea.getEventType() == osgGA::GUIEventAdapter::FRAME)
{
// 匀速累加时间:控制旋转速度
// 数值越小旋转越慢,越大越快
_time += 0.005;
// 请求渲染窗口刷新画面
aa.requestRedraw();
// 事件已处理完成
return true;
}
// 其他事件不处理
return false;
}
// 【核心函数】获取相机变换矩阵(OSG 渲染时持续调用)
osg::Matrixd getMatrix() const override
{
// 1. 用时间作为旋转角度
double angle = _time;
// 2. 计算相机位置:圆周运动公式
// 半径 = 70,高度 Z = 20
osg::Vec3 eye(
70 * cos(angle), // X 坐标:左右圆周运动
-70 * sin(angle), // Y 坐标:前后圆周运动
20 // Z 坐标:固定高度 20
);
// 3. 相机观察目标点:原点(模型位置)
osg::Vec3 center(0, 0, 0);
// 4. 相机头顶方向:Z轴向上
osg::Vec3 up(0, 0, 1);
// 5. 生成相机矩阵:必须取 inverse()!
// lookAt 生成视图矩阵,CameraManipulator 需要返回相机自身矩阵
return osg::Matrixd::inverse(osg::Matrixd::lookAt(eye, center, up));
}
// 必须实现:返回逆矩阵
osg::Matrixd getInverseMatrix() const override
{
return osg::Matrixd::inverse(getMatrix());
}
// 必须实现:空实现(本文不需要)
void setByMatrix(const osg::Matrixd&) override {}
void setByInverseMatrix(const osg::Matrixd&) override {}
private:
double _time; // 时间变量:控制旋转角度
};
// 主函数
int main()
{
// 1. 创建 OSG 查看器(窗口+渲染循环)
ref_ptr<Viewer> viewer = new Viewer;
// 2. 创建场景根节点
ref_ptr<Group> root = new Group;
// 3. 加载牛模型
ref_ptr<Node> cow = osgDB::readNodeFile("cow.osg");
if (!cow) {
std::cerr << "ERROR:找不到 cow.osg 模型文件!" << std::endl;
return -1;
}
// 4. 将模型加入场景
root->addChild(cow);
// 5. 设置自定义环绕相机
viewer->setCameraManipulator(new PathCamera());
// 6. 设置场景数据
viewer->setSceneData(root);
// 7. 初始化渲染环境
viewer->realize();
// 8. 启动渲染循环
return viewer->run();
}

代码深度解析
1. 旋转的核心:时间驱动角度
cpp
_time += 0.005;
double angle = _time;
- 每一帧,
_time固定增加一个极小值 - 角度
angle随时间匀速增大 - 角度越大,相机位置越靠后 → 形成旋转
2. 圆周运动数学公式(关键)
cpp
osg::Vec3 eye(
70 * cos(angle),
-70 * sin(angle),
20
);
这是标准二维圆周运动公式:
x = r * cos(θ)y = -r * sin(θ)
参数含义:
r = 70:旋转半径(相机离模型的距离)θ = angle:旋转角度Z=20:相机高度固定
负号
-70*sin是为了让旋转方向符合视觉习惯。
3. 相机始终看向模型
cpp
Matrixd::lookAt(eye, center, up)
eye:相机当前位置center:观察点(原点)up:头顶方向
作用:强制相机镜头永远对准模型,不会跑偏、不会丢失目标。
4. 必须加 inverse()
cpp
return Matrixd::inverse(lookAt(...));
lookAt()生成的是视图矩阵(给 shader 用)CameraManipulator::getMatrix()要求返回相机自身的变换矩阵- 两者互为逆矩阵
- 不加
inverse()→ 相机矩阵错误 → 模型看不见
总结
本文实现的 OSG 自定义相机漫游控制器,是三维场景自动演示、环绕观察、虚拟巡航的标准方案:
- 基于新版
osgGA::CameraManipulator,替代废弃的MatrixManipulator - 使用三角函数实现标准圆周环绕运动
- 通过帧事件 驱动时间,实现绝对匀速旋转
- 严格遵循 OSG 矩阵规则,保证模型可见
- 代码轻量、稳定、可直接用于项目
