在这里插入图片描述

月球与公转卫星

项目简介

本项目基于 OpenSceneGraph(OSG)三维引擎实现了一套简洁且真实的太空光照演示系统,核心功能包含:月球纯纹理自发光渲染月球点光源照射卫星球体环绕月球公转,并通过卫星的明暗面直观体现月球的发光效果。

项目全程不使用复杂着色器,仅依靠 OSG 原生光照、材质、纹理、坐标变换与动画回调实现,代码结构清晰、易于理解,非常适合 OSG 初学者学习三维渲染、光照系统与场景动画。

实现效果

  1. 月球:使用纹理贴图渲染,开启自发光,自身持续明亮,不受外部环境影响
  2. 光照:月球作为点光源,向外发射光线
  3. 卫星小球:被月球光照亮,朝向月球一面高亮,背向月球阴暗,拥有真实的明暗过渡
  4. 动画:卫星小球持续围绕月球做圆周公转,光影跟随位置实时变化,直观体现月球主动发光

项目核心知识点

1. 场景树结构与基础节点

  • osg::Group:场景根节点,用于管理所有子物体(月球、光源、卫星),是OSG场景的容器。
  • osg::Geode最小可渲染叶节点,专门承载几何体,是模型显示的基础单元。
  • osg::MatrixTransform:空间变换节点,实现物体的位移、旋转、缩放,是动画实现的核心。

2. 几何体与可绘制对象

  • osg::Sphere:定义球形数学形状,确定模型的基础轮廓。
  • osg::ShapeDrawable:将数学形状转换为显卡可渲染的几何体,自动生成顶点、法线等数据。

3. 渲染状态与材质(自发光核心)

  • osg::StateSet:渲染状态集,统一管理光照、纹理、材质等外观属性。
  • osg::Material:材质类,通过 setEmission 实现物体自发光,是月球发光的关键。
  • GL_LIGHTING:开启光照模式,让物体参与光照计算,实现亮暗面效果。

4. 纹理渲染

  • osg::Texture2D:2D纹理对象,将图片转换为显卡可识别的纹理。
  • osgDB::readImageFile:加载外部纹理图片(jpg/png等)。
  • osg::TexEnv::REPLACE:纹理替换模式,让纹理颜色直接覆盖模型颜色,保证月球纹理真实显示。

5. 光照系统(光源照射核心)

  • osg::Light:定义点光源,设置光源颜色、位置。
  • osg::LightSource:光源节点,将光源加入场景树,实现光线照射效果。
  • 全局开启 GL_LIGHT0,让月球光源生效,照亮卫星小球。

6. 帧动画与回调机制(公转核心)

  • osg::NodeCallback:节点更新回调基类,实现每帧自动执行的逻辑。
  • 自定义 OrbitCallback 回调类:通过角度累加 + 三角函数,计算圆周运动坐标,实现卫星绕月公转。
  • setUpdateCallback:将回调绑定到变换节点,启动自动动画。

7. 数学与动画基础

  • 角度转弧度、三角函数(cos/sin):实现圆周运动的数学基础。
  • 帧更新动画:通过每帧修改物体位置,形成视觉上的平滑连续动画。

完整项目代码

#include <osg/Geode>
#include <osg/ShapeDrawable>
#include <osg/Texture2D>
#include <osgViewer/Viewer>
#include <osg/StateSet>
#include <osg/TexEnv>
#include <osgDB/ReadFile>
#include <osg/Material>
#include <osg/Light>
#include <osg/LightSource>
#include <osg/MatrixTransform>
#include <osg/Group>
#include <osg/NodeCallback>

// ====================== 1. 创建月球(纯纹理 + 自发光)======================
osg::ref_ptr<osg::Node> createMoon(const std::string& texturePath)
{
    // 创建球体几何体,中心在坐标原点,半径为1.0
    osg::ref_ptr<osg::Sphere> sphere = new osg::Sphere(osg::Vec3(0,0,0), 1.0f);
    // 将球体封装为可绘制对象
    osg::ref_ptr<osg::ShapeDrawable> drawable = new osg::ShapeDrawable(sphere);
    // 创建叶节点,用于承载可绘制几何体
    osg::ref_ptr<osg::Geode> geode = new osg::Geode();
    geode->addDrawable(drawable);

    // 获取节点渲染状态集
    osg::ref_ptr<osg::StateSet> ss = geode->getOrCreateStateSet();
    // 开启光照系统,保证月球可以参与光照计算
    ss->setMode(GL_LIGHTING, osg::StateAttribute::ON);

    // 设置材质,实现月球自发光
    osg::ref_ptr<osg::Material> mat = new osg::Material();
    // 设置发射颜色为纯白色,强度拉满,实现自发光效果
    mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4(1.0, 1.0, 1.0, 1.0));
    ss->setAttributeAndModes(mat, osg::StateAttribute::ON);

    // 加载月球纹理图片
    osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D();
    osg::ref_ptr<osg::Image> image = osgDB::readImageFile(texturePath);
    if (image) texture->setImage(image);

    // 设置纹理环境模式为REPLACE,纹理颜色直接替换模型颜色
    osg::ref_ptr<osg::TexEnv> texEnv = new osg::TexEnv();
    texEnv->setMode(osg::TexEnv::REPLACE);
    // 将纹理与纹理模式应用到模型
    ss->setTextureAttributeAndModes(0, texEnv, osg::StateAttribute::ON);
    ss->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON);

    return geode;
}

// ====================== 2. 创建被照亮的卫星小球 ======================
osg::ref_ptr<osg::Node> createLitBall()
{
    // 创建小球体,半径0.3
    osg::ref_ptr<osg::Sphere> sphere = new osg::Sphere(osg::Vec3(0,0,0), 0.3f);
    osg::ref_ptr<osg::ShapeDrawable> drawable = new osg::ShapeDrawable(sphere);
    osg::ref_ptr<osg::Geode> geode = new osg::Geode();
    geode->addDrawable(drawable);

    osg::ref_ptr<osg::StateSet> ss = geode->getOrCreateStateSet();
    // 开启光照,接受月球光源的照射
    ss->setMode(GL_LIGHTING, osg::StateAttribute::ON);

    // 设置小球材质,灰色漫反射,白色高光,清晰显示光照明暗面
    osg::ref_ptr<osg::Material> mat = new osg::Material();
    mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(0.6f, 0.6f, 0.6f, 1.0f));
    mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
    ss->setAttributeAndModes(mat, osg::StateAttribute::ON);

    return geode;
}

// ====================== 3. 月球点光源系统 ======================
osg::ref_ptr<osg::Node> createMoonLight()
{
    // 创建0号光源
    osg::ref_ptr<osg::Light> light = new osg::Light();
    light->setLightNum(0);
    // 设置光源颜色为暖白色,模拟月光
    light->setDiffuse(osg::Vec4(1.0f, 1.0f, 0.9f, 1.0f));
    // 光源位置固定在月球中心(原点)
    light->setPosition(osg::Vec4(0.0f, 0.0f, 0.0f, 1.0f));

    // 将光源封装为光源节点
    osg::ref_ptr<osg::LightSource> lightSource = new osg::LightSource();
    lightSource->setLight(light);
    return lightSource;
}

// ====================== 4. 公转动画回调:小球绕月球旋转 ======================
class OrbitCallback : public osg::NodeCallback
{
public:
    OrbitCallback() : _angle(0) {}

    // 每帧执行的动画更新函数
    virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
    {
        // 将节点转换为坐标变换节点
        osg::MatrixTransform* mt = dynamic_cast<osg::MatrixTransform*>(node);
        if (mt)
        {
            // 控制公转速度,数值越大旋转越快
            _angle += 0.2f;
            // 公转轨道半径
            float radius = 2.2f;

            // 将角度转换为弧度,计算圆周坐标
            float rad = osg::DegreesToRadians(_angle);
            float x = radius * cos(rad);
            float z = radius * sin(rad);

            // 设置小球位置,实现绕Y轴圆周运动
            mt->setMatrix(osg::Matrix::translate(x, 0.0f, z));
        }
        // 继续遍历子节点
        traverse(node, nv);
    }
private:
    float _angle; // 记录旋转角度
};

// ====================== 主函数:场景构建与渲染 ======================
int main()
{
    // 创建OSG渲染窗口查看器
    osgViewer::Viewer viewer;
    // 创建场景根节点
    osg::ref_ptr<osg::Group> root = new osg::Group();

    // 添加月球模型与月球光源
    root->addChild(createMoon("/home/xiaoqing/study/osg-study_1/osg_moon/moon.jpg"));
    root->addChild(createMoonLight());

    // 创建卫星小球的坐标变换节点
    osg::ref_ptr<osg::MatrixTransform> ballTrans = new osg::MatrixTransform();
    ballTrans->addChild(createLitBall());
    // 绑定公转动画回调,启动自动旋转
    ballTrans->setUpdateCallback(new OrbitCallback());
    root->addChild(ballTrans);

    // 全局开启0号光源
    osg::ref_ptr<osg::StateSet> rootSS = root->getOrCreateStateSet();
    rootSS->setMode(GL_LIGHT0, osg::StateAttribute::ON);

    // 创建窗口并设置场景数据
    viewer.setUpViewInWindow(100, 100, 800, 600);
    viewer.setSceneData(root);

    // 启动渲染循环
    return viewer.run();
}

请添加图片描述

创建节点流程总结

创建球体形状
   ↓
生成可绘制对象(Drawable)
   ↓
放进叶节点(Geode)
   ↓
设置渲染状态(光照、材质、纹理)
   ↓
节点加入场景树

createMoon 函数负责创建月球:

  1. 球体创建:使用 osg::Sphere 生成球形模型
  2. 光照开启:必须开启 GL_LIGHTING 才能让月球发光并照亮其他物体
  3. 自发光实现:通过 setEmission 设置材质发射色,让月球自身发光
  4. 纹理渲染:加载月球贴图,使用 REPLACE 模式让纹理完全覆盖模型颜色,保证画面真实

OrbitCallback 实现旋转

它是一个“每帧自动执行的动画工具”
OSG 渲染的时候,画面每刷新一次,它就自动跑一次,用来不断修改小球的位置 → 让小球转起来。

你可以把它理解为:
小球的“自动公转控制器”


代码实现

// 1. 定义一个公转回调类,继承 OSG 的 NodeCallback
// 继承后,才能被 OSG 自动调用
class OrbitCallback : public osg::NodeCallback
{
public:
    // 2. 构造函数:初始化角度为 0
    OrbitCallback() : _angle(0) {}

    // 3. 核心函数:每帧都会被 OSG 自动调用!
    // node = 小球所在的变换节点 ballTrans
    // nv = 访问器(OSG 内部遍历用)
    virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
    {
        // 4. 把传入的节点 强转成 变换节点
        // 因为我们要对小球做位移、旋转
        osg::MatrixTransform* mt = dynamic_cast<osg::MatrixTransform*>(node);

        // 5. 如果转换成功(不为空)
        if (mt)
        {
            // ==================== 动画核心 ====================
            // 6. 角度每次 +0.2(数值越大,转得越快)
            _angle += 0.2f;

            // 7. 公转半径:小球离月球多远
            float radius = 2.2f;

            // 8. 把角度转成弧度(C++ 三角函数只认弧度)
            float rad = osg::DegreesToRadians(_angle);

            // 9. 圆周运动公式:
            // 绕 Y 轴转 → X 和 Z 坐标不断变化,形成圆形轨迹
            float x = radius * cos(rad);
            float z = radius * sin(rad);

            // 10. 设置小球新位置 → 小球移动了!
            mt->setMatrix(osg::Matrix::translate(x, 0.0f, z));
        }

        // 11. 必须写:让 OSG 继续遍历子节点(小球)
        traverse(node, nv);
    }

private:
    // 12. 保存当前旋转的角度(每帧累加)
    float _angle;
};

工作流程

流程 1:创建回调
ballTrans->setUpdateCallback(new OrbitCallback());

告诉 OSG:
“请给我开启自动更新,每帧都调用这个类!”


流程 2:OSG 每帧自动调用

画面每刷新一次,就执行一次:

operator()(node, visitor);

流程 3:角度不断变大
_angle += 0.2f;

第1帧:0
第2帧:0.2
第3帧:0.4
第4帧:0.6
……
一直累加 → 角度越来越大 → 转圈


流程 4:计算圆周坐标
x = 半径 * cos(角度)
z = 半径 * sin(角度)

这就是绕 Y 轴公转的数学公式。


流程 5:设置新位置
mt->setMatrix( osg::Matrix::translate(x,0,z) );

小球瞬间移动到新坐标。


流程 6:不断重复

因为每帧都在移动,视觉上就变成了平滑公转


重要的 3 个知识点(必须记住)

1. 回调 = 每帧自动执行

你不用写 while 循环
OSG 自动帮你跑

2. operator() 是动画入口

所有旋转、位移逻辑都写在这里

3. _angle 是动画的“时间轴”

角度不断累加 → 位置不断变化 → 旋转动画


总结

本项目以简洁的代码、完整的效果,覆盖了OSG引擎场景管理、几何体渲染、材质纹理、光照系统、帧动画五大核心知识点。

通过“月球发光照亮卫星、卫星绕月公转”的直观效果,完美串联了3D渲染的基础逻辑,既是OSG入门的实战案例,也是理解3D光照与动画的优质实践项目。

在这里插入图片描述

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐