【OSG学习笔记】Day 47:相机漫游实现

相机漫游实现:基于 CameraManipulator 自定义匀速环绕相机

在 OpenSceneGraph(OSG)三维开发中,相机漫游是最核心的交互能力之一。

它指虚拟相机按照预设的轨迹(如圆周、直线、自定义路径)在场景中自动运动,实现模型环绕观察、场景巡航、自动演示等效果。

本文将基于新版 OSG 标准的 CameraManipulator ,从零实现一个稳定、匀速、环绕原点 的相机漫游控制器,并结合完整可运行代码,深入讲解类继承关系、相机矩阵原理、圆周旋转数学实现,彻底解决黑屏、模型丢失、旋转异常等问题。


核心基础

OSG 3.x 之后,所有相机控制器必须继承 osgGA::CameraManipulator,它是官方唯一标准的相机操控抽象基类。

完整继承链

复制代码
osg::Referenced        // 智能指针内存管理(OSG 所有对象基类)
    ↓
osg::Object            // 基础对象属性
    ↓
osgGA::GUIEventHandler  // 事件处理器(接收鼠标/键盘/帧事件)
    ↓
osgGA::CameraManipulator  // 【抽象相机控制器】我们自定义类的父类
    ↓
自定义类:PathCamera      // 本文实现的环绕漫游相机

必须实现的核心接口

CameraManipulator抽象类,子类必须重写 4 个纯虚函数:

  1. getMatrix():返回相机自身变换矩阵(最核心
  2. getInverseMatrix():返回逆矩阵(渲染管线必需)
  3. setByMatrix():通过矩阵设置相机(本文无需使用,空实现)
  4. setByInverseMatrix():通过逆矩阵设置相机(本文无需使用,空实现)

同时继承 GUIEventHandlerhandle() 函数,处理帧更新事件,驱动相机运动。


相机漫游原理

本文实现的是水平圆周环绕漫游

  1. 相机在 X-Y 平面 绕原点 (0,0,0) 做匀速圆周运动
  2. 相机高度固定(Z=20)
  3. 相机始终看向原点(保证模型永远在画面中心)
  4. 使用三角函数 sin / cos 计算圆周坐标
  5. 严格遵循 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 自定义相机漫游控制器,是三维场景自动演示、环绕观察、虚拟巡航的标准方案:

  1. 基于新版 osgGA::CameraManipulator,替代废弃的 MatrixManipulator
  2. 使用三角函数实现标准圆周环绕运动
  3. 通过帧事件 驱动时间,实现绝对匀速旋转
  4. 严格遵循 OSG 矩阵规则,保证模型可见
  5. 代码轻量、稳定、可直接用于项目
相关推荐
Hello_Embed2 小时前
嵌入式上位机开发入门(二十):写文件功能的 RTU/TCP 双协议适配
网络·笔记·单片机·网络协议·tcp/ip·嵌入式
知识分享小能手2 小时前
MongoDB入门学习教程,从入门到精通,MongoDB监控完全指南(22)
数据库·学习·mongodb
无敌昊哥战神2 小时前
【算法与数据结构】深入浅出回溯算法:理论基础与核心模板(C/C++与Python三语解析)
c语言·数据结构·c++·笔记·python·算法
zore_c2 小时前
【C++】基础语法(命名空间、引用、缺省以及输入输出)
c语言·开发语言·数据结构·c++·经验分享·笔记
久违 °2 小时前
【经营管理】企业经营管理沙盘笔记(一)
笔记
_李小白2 小时前
【OSG学习笔记】Day 46: CameraManipulator(相机操控器)
笔记·数码相机·学习
我登哥MVP2 小时前
【Spring6笔记】 - 13 - 面向切面编程(AOP)
java·开发语言·spring boot·笔记·spring·aop
Z.风止2 小时前
Large Model-learning(4)
人工智能·pytorch·笔记·python·深度学习·机器学习
ZPC82102 小时前
相机引导机器人移动
数码相机·机器人