【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():通过逆矩阵设置相机(本文无需使用,空实现)

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


相机漫游原理

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

  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. 代码轻量、稳定、可直接用于项目
相关推荐
Bug收容所23 分钟前
学习LangChain day1
学习·langchain·llm·agent
传奇开心果编程25 分钟前
【现代声明式UI学与练】第7课 组件通信——父传子、子传父、跨层级通信、状态提升与 Context
学习·flutter·react native·ui·swiftui·android jetpack
Seraphina361 小时前
SQL注入(二)盲注
网络·数据库·经验分享·笔记·sql
白山编程大哥1 小时前
Carsim 2024.1 专业安装介绍:功能特性与应用场景详解
笔记
hss123456781 小时前
整装落地与效果图差距有多大?从毫米级误差到ENF板材的量化拆解
笔记
我命由我123452 小时前
Photoshop - Photoshop 矢量蒙版
学习·职场和发展·产品运营·求职招聘·职场发展·产品经理·photoshop
熊猫钓鱼>_>3 小时前
越顺,越空:当 AI 把学习 “优化“ 到消失
人工智能·学习·ai·llm·agent·ai编程·metaai
一条破秋裤3 小时前
02_注册设备号_从主次设备号到成对注销
学习
@codercjw3 小时前
企业实战电机和传感器选型
学习
zhangrelay4 小时前
用ROS2Lyrical实验报告册思路重做蓝桥云课ROS1实验并改进
linux·笔记·学习·ubuntu·机器人