【OSG案例详细分析与讲解】之四:【3D动画场景】

发布时间:2024年01月11日

文章目录

一、【3D动画场景】前言

二、【3D动画场景】实现效果

三、【3D动画场景】创建动画路径

1、实现目的

2、创建动画路径步骤

3、核心代码

4、知识要点

5、AnimationPath详讲

四、【3D动画场景】创建基础模型

1、实现目的

2、创建基础模型步骤

3、核心代码

4、知识要点

5、osg::Geometry详讲

五、【3D动画场景】创建移动模型

1、实现目的

2、创建移动模型步骤

3、核心代码

4、知识要点

六、【3D动画场景】组合模型

1、实现目的

2、实现组合模型步骤

3、核心代码

4、知识要点

5、osgSim::OverlayNode详讲

七、【3D动画场景】程序

1、程序代码

2、qt pro文件

八、【3D动画场景】总结


一、【3D动画场景】前言

? ? ? ?OpenSceneGraph(OSG)是一个强大的开源三维图形引擎,提供了丰富的功能和工具来创建逼真的3D场景。本文将介绍如何使用OSG创建一个动画场景,包括创建动画路径基础模型移动模型,并将它们组合在一起形成一个完整的场景。同时还将讨论如何使用叠加节点来实现额外的效果。让我们一起来看看吧!


二、【3D动画场景】实现效果

? ? ? ?一个逼真的3D动画场景,其中包括黑白相间的地面、移动的飞机模型和预定义的动画路径。


三、【3D动画场景】创建动画路径

1、实现目的

? ? ? ?通过定义createAnimationPath函数,实现一个动画路径来控制模型的运动。可以创建一个环形运动路径,这个路径包含了一系列的控制点,每个控制点包括位置和旋转信息。通过调整控制点的位置和旋转,可以实现物体在场景中的运动。

2、创建动画路径步骤

  • 使用osg::AnimationPath类创建动画路径对象。
  • 通过调用addControlPoint方法添加控制点,设置位置和旋转信息。

3、核心代码

// 创建一个动画路径,以 center 为圆心,radius 为半径,looptime 为循环时间
osg::AnimationPath* createAnimationPath(const osg::Vec3& center, float radius, double looptime)
{
    // 设置动画路径的循环模式为 LOOP
    osg::ref_ptr<osg::AnimationPath> animationPath = new osg::AnimationPath;
    animationPath->setLoopMode(osg::AnimationPath::LOOP);

    int numSamples = 40; // 定义采样点数为 40
    float yaw = 0.0f; // 初始化偏航角为 0
    float yaw_delta = 2.0f * osg::PI / (numSamples - 1.0f); // 每个采样点之间的偏航角度增量
    float roll = osg::inDegrees(30.0f); // 翻滚角度为 30 度

    double time = 0.0f; // 初始化时间为 0
    double time_delta = looptime / numSamples; // 时间增量为循环时间除以采样点数
    for (int i = 0; i < numSamples; ++i) // 循环遍历每个采样点
    {
        // 根据当前的偏航角度计算当前位置
        osg::Vec3 position(center + osg::Vec3(sinf(yaw) * radius, cosf(yaw) * radius, 0.0f));
        
        // 根据当前的偏航角度和翻滚角度计算当前旋转角度
        osg::Quat rotation(osg::Quat(roll, osg::Vec3(0.0, 1.0, 0.0)) * osg::Quat(-(yaw + osg::inDegrees(90.0f)), osg::Vec3(0.0, 0.0, 1.0)));

        // 将当前位置和旋转角度插入到动画路径中
        animationPath->insert(time, osg::AnimationPath::ControlPoint(position, rotation));

        // 更新偏航角度和时间
        yaw += yaw_delta;
        time += time_delta;
    }
    
    // 返回创建好的动画路径
    return animationPath.release();
}

4、知识要点

? ? ? ? 函数接受三个参数:center表示圆心的位置,radius表示圆的半径,looptime表示动画循环一周所需的时间。

? ? ? ? 首先,该函数创建了一个osg::AnimationPath对象,并将循环模式设置为LOOP,表示动画会无限循环。

? ? ? ? 然后,通过循环遍历,计算出每个采样点的位置和旋转角度。偏航角(yaw)从0开始,根据采样点数(numSamples)计算出每个采样点之间的偏航角度增量(yaw_delta)。根据当前的偏航角度,使用三角函数计算出当前位置相对于圆心的偏移量,得到当前位置(position)

? ? ? ? 同时,根据当前的偏航角度和固定的翻滚角度(roll),计算出当前旋转角度(rotation)

? ? ? ? 将当前位置和旋转角度插入到动画路径中,使用时间(time)作为关键帧的时间点,通过不断累加时间增量(time_delta)更新时间。

? ? ? ?最后,返回创建好的动画路径。

? ? ? ?调用该函数可以获得一个动画路径对象,可以在渲染场景的过程中使用该动画路径来控制物体沿着圆形轨道进行旋转动画。

5、AnimationPath详讲

? ? ? osg::AnimationPath是OpenSceneGraph中用于描述动画路径的类,通过设置一系列的关键帧,描述物体在不同时间点上的位置、旋转和缩放等状态。在场景渲染时可以使用osg::AnimationPath控制物体进行动画效果的播放。

osg::AnimationPath的主要成员函数包括:

  • setLoopMode(int mode):设置动画循环模式,参数mode可以取值为osg::AnimationPath::NO_LOOPING(不循环)、osg::AnimationPath::LOOPING(循环)或osg::AnimationPath::SWINGING(来回循环)。
  • insert(double time, const osg::AnimationPath::ControlPoint& controlPoint):插入一个关键帧,time表示该关键帧的时间点,controlPoint表示该关键帧所对应的物体状态(位置、旋转和缩放等)。
  • getNumControlPoints():获取关键帧的数量。
  • getControlPointAt(unsigned int index):获取指定索引处的关键帧。
  • getFirstTime() / getLastTime():获取动画路径的第一个关键帧和最后一个关键帧的时间点。

? ? ? 使用osg::AnimationPath可以方便地实现物体在3D场景中的动画效果,比如物体的平移、旋转、缩放等操作。同时,osg::AnimationPath提供了多种循环模式插值方式,使得开发者能够更加灵活地控制动画效果的表现。


四、【3D动画场景】创建基础模型

1、实现目的

? ? ? ?通过定义createBase函数,创建一个基础模型。这个模型是一个黑白相间的方格地面,通过设置顶点和颜色数组,以及绘制图元来定义模型的形状。基础模型将作为场景的底部,并提供一个参照平面供其他模型进行运动。

2、创建基础模型步骤

  • 使用osg::Geometry类创建基础模型。
  • 设置顶点和颜色数组,以及绘制图元。

3、核心代码

osg::Node* createBase(const osg::Vec3& center, float radius)
{
    int numTilesX = 10; // 网格行数
    int numTilesY = 10; // 网格列数
    float width = 2 * radius; // 网格宽度
    float height = 2 * radius; // 网格高度

    osg::Vec3 v000(center - osg::Vec3(width * 0.5f, height * 0.5f, 0.0f)); // 左下角顶点坐标
    osg::Vec3 dx(osg::Vec3(width / static_cast<float>(numTilesX), 0.0f, 0.0f)); // 横向增量
    osg::Vec3 dy(osg::Vec3(0.0f, height / static_cast<float>(numTilesY), 0.0f)); // 纵向增量

    osg::Vec3Array* coords = new osg::Vec3Array; // 顶点数组
    int iy;
    for (iy = 0; iy <= numTilesY; ++iy)
    {
        for (int ix = 0; ix <= numTilesX; ++ix)
        {
            coords->push_back(v000 + dx * static_cast<float>(ix) + dy * static_cast<float>(iy)); // 添加网格的角点
        }
    }

    osg::Vec4Array* colors = new osg::Vec4Array; // 颜色数组
    colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f)); // 白色
    colors->push_back(osg::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); // 黑色

    osg::ref_ptr<osg::DrawElementsUShort> whitePrimitives = new osg::DrawElementsUShort(GL_QUADS); // 白色网格的DrawElements对象
    osg::ref_ptr<osg::DrawElementsUShort> blackPrimitives = new osg::DrawElementsUShort(GL_QUADS); // 黑色网格的DrawElements对象

    int numIndicesPerRow = numTilesX + 1;
    for (iy = 0; iy < numTilesY; ++iy)
    {
        for (int ix = 0; ix < numTilesX; ++ix)
        {
            osg::DrawElementsUShort* primitives = ((iy + ix) % 2 == 0) ? whitePrimitives.get() : blackPrimitives.get(); // 根据行列号确定网格颜色
            primitives->push_back(ix + (iy + 1) * numIndicesPerRow); // 添加四个顶点到DrawElements对象中
            primitives->push_back(ix + iy * numIndicesPerRow);
            primitives->push_back((ix + 1) + iy * numIndicesPerRow);
            primitives->push_back((ix + 1) + (iy + 1) * numIndicesPerRow);
        }
    }

    osg::Vec3Array* normals = new osg::Vec3Array; // 法向量数组
    normals->push_back(osg::Vec3(0.0f, 0.0f, 1.0f)); // 所有网格共用同一个法向量

    osg::Geometry* geom = new osg::Geometry; // Geometry对象
    geom->setVertexArray(coords); // 设置顶点数组
    geom->setColorArray(colors, osg::Array::BIND_PER_PRIMITIVE_SET); // 设置颜色数组和绑定方式
    geom->setNormalArray(normals, osg::Array::BIND_OVERALL); // 设置法向量数组和绑定方式
    geom->addPrimitiveSet(whitePrimitives.get()); // 添加白色网格的DrawElements对象
    geom->addPrimitiveSet(blackPrimitives.get()); // 添加黑色网格的DrawElements对象

    osg::Geode* geode = new osg::Geode; // Geode节点
    geode->addDrawable(geom); // 在Geode中添加Geometry对象

    return geode; // 返回Geode节点作为结果
}

4、知识要点

实现了一个平面网格的生成,该网格由多个小正方形组成。生成网格的过程分为以下几个步骤:

  1. 定义网格的大小和行列数,以及左下角顶点的坐标。

  2. 根据行列数和网格大小计算每个小正方形的四个角点的坐标,将这些坐标存入顶点数组中。

  3. 定义两个颜色数组,用于标记相邻小正方形的颜色。

  4. 遍历每个小正方形,依次添加四个顶点到对应颜色的 DrawElements 对象中。

  5. 定义一个法向量数组,使所有小正方形共用同一个法向量。

  6. 创建 Geometry 对象,并将其顶点、颜色、法向量和 DrawElements 对象添加进去。

  7. 创建 Geode 节点,并将 Geometry 对象添加进去。

  8. 返回 Geode 节点作为结果。

最终生成的平面网格可以用于各种模型展示和场景构建。

5、osg::Geometry详讲

? ? ? ?osg::Geometry 是 OpenSceneGraph 中最重要的节点之一,它用于表示 OpenGL 中的几何体,例如点、线、三角形和多边形等。Geometry 节点可以包含多个 Drawable 对象,每个 Drawable 对象都代表一个几何体的一部分。

osg::Geometry 包含以下主要属性:

  1. 顶点数组(Vertex Array):用于存储几何体的各个顶点的坐标。OpenSceneGraph 支持多种数据类型的顶点数组,例如 osg::Vec2Array、osg::Vec3Array 和 osg::Vec4Array 等。

  2. 颜色数组(Color Array):用于指定几何体各部分的颜色。OpenSceneGraph 支持多种数据类型的颜色数组,例如 osg::Vec4Array 和 osg::Vec3Array 等。

  3. 法向量数组(Normal Array):用于指定几何体各部分的法向量。OpenSceneGraph 支持多种数据类型的法向量数组,例如 osg::Vec3Array 等。

  4. 纹理坐标数组(Texture Coordinate Array):用于存储几何体各部分的纹理坐标。OpenSceneGraph 支持多种数据类型的纹理坐标数组,例如 osg::Vec2Array 等。

  5. DrawElements 对象:用于定义几何体的绘制方式,例如 GL_POINTS、GL_LINES、GL_TRIANGLES 和 GL_QUADS 等。DrawElements 对象还可以设置索引数组,用于指定顶点数组中哪些点组成一条线或一个三角形等。

? ? ? osg::Geometry 还提供了一系列方法来设置和获取这些属性。例如,可以使用 setVertexArray() 方法设置顶点数组,使用 addPrimitiveSet() 方法向 Geometry 添加 DrawElements 对象,使用 setColorArray() 方法设置颜色数组等。

? ? ? 在渲染场景时,osg::Geometry 会将保存的几何体数据传递给 OpenGL,供其进行渲染。因此,osg::Geometry 是 OpenSceneGraph 中非常重要的一个节点,它可以帮助我们方便地构建出各种复杂的几何体。


五、【3D动画场景】创建移动模型

1、实现目的

? ? ?为了增加场景的趣味性,通过定义createMovingModel函数,可以创建一些移动的模型。我们可以创建两个移动模型,分别是飞机模型(glider.osgt和cessna.osgt)。这些模型将根据之前定义的动画路径进行运动。

2、创建移动模型步骤

  • 导入外部3D模型文件(例如glider.osgt和cessna.osgt)。
  • 创建osg::MatrixTransform对象,并将导入的模型添加为其子节点。
  • 将动画路径应用到osg::MatrixTransform对象上,使模型按照路径运动。

3、核心代码

osg::Node* createMovingModel(const osg::Vec3& center, float radius)
{
    float animationLength = 10.0f;  // 动画播放时长

    // 创建动画路径
    osg::ref_ptr<osg::AnimationPath> animationPath = createAnimationPath(center, radius, animationLength);

    osg::ref_ptr<osg::Group> model = new osg::Group;  // 创建根节点

    // 导入滑翔机模型
    osg::ref_ptr<osg::Node> glider = osgDB::readRefNodeFile("../OpenSceneGraph-Data/glider.osgt");
    if (glider)
    {
        const osg::BoundingSphere& bs = glider->getBound();  // 获取模型的包围球信息

        float size = radius / bs.radius() * 0.3f;  // 根据给定的半径计算模型的大小
        osg::ref_ptr<osg::MatrixTransform> positioned = new osg::MatrixTransform;  // 创建矩阵变换节点
        positioned->setDataVariance(osg::Object::STATIC);  // 设置数据不会频繁改变
        positioned->setMatrix(
            osg::Matrix::translate(-bs.center()) *
            osg::Matrix::scale(size, size, size) *
            osg::Matrix::rotate(osg::inDegrees(-90.0f), 0.0f, 0.0f, 1.0f));  // 设置模型的平移、缩放和旋转变换

        positioned->addChild(glider);  // 将模型添加到矩阵变换节点下

        osg::ref_ptr<osg::PositionAttitudeTransform> xform = new osg::PositionAttitudeTransform;  // 创建位置姿态变换节点
        xform->setUpdateCallback(new osg::AnimationPathCallback(animationPath, 0.0, 1.0));  // 设置更新回调,使模型按照动画路径进行移动
        xform->addChild(positioned);  // 将矩阵变换节点添加到位置姿态变换节点下

        model->addChild(xform);  // 将位置姿态变换节点添加到根节点下
    }

    // 导入塞斯纳飞机模型
    osg::ref_ptr<osg::Node> cessna = osgDB::readRefNodeFile("../OpenSceneGraph-Data/cessna.osgt");
    if (cessna)
    {
        const osg::BoundingSphere& bs = cessna->getBound();  // 获取模型的包围球信息

        float size = radius / bs.radius() * 0.3f;  // 根据给定的半径计算模型的大小
        osg::ref_ptr<osg::MatrixTransform> positioned = new osg::MatrixTransform;  // 创建矩阵变换节点
        positioned->setDataVariance(osg::Object::STATIC);  // 设置数据不会频繁改变
        positioned->setMatrix(
            osg::Matrix::translate(-bs.center()) *
            osg::Matrix::scale(size, size, size) *
            osg::Matrix::rotate(osg::inDegrees(180.0f), 0.0f, 0.0f, 1.0f));  // 设置模型的平移、缩放和旋转变换

        positioned->addChild(cessna);  // 将模型添加到矩阵变换节点下

        osg::ref_ptr<osg::MatrixTransform> xform = new osg::MatrixTransform;  // 创建矩阵变换节点
        xform->setUpdateCallback(new osg::AnimationPathCallback(animationPath, 0.0f, 2.0));  // 设置更新回调,使模型按照动画路径进行移动
        xform->addChild(positioned);  // 将矩阵变换节点添加到矩阵变换节点下

        model->addChild(xform);  // 将矩阵变换节点添加到根节点下
    }

#ifndef OSG_GLES2_AVAILABLE
    model->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON);
#endif

    return model.release();  // 返回根节点
}

4、知识要点

? ? ? ? 实现了一个创建移动模型的函数。函数接受一个中心点和半径作为参数,然后根据这些参数创建一个动画路径,并将滑翔机模型和塞斯纳飞机模型添加到场景中。

具体实现步骤如下:

  1. 创建一个根节点(osg::Group)作为模型的容器。
  2. 导入滑翔机模型和塞斯纳飞机模型,使用osgDB::readRefNodeFile函数读取osg文件并返回一个osg::Node对象。
  3. 获取模型的包围球信息,通过osg::Node的getBound()方法获取包围球的中心点和半径。
  4. 根据给定的半径计算模型的大小,使模型在指定半径范围内正确显示。
  5. 创建osg::MatrixTransform节点,将模型添加到矩阵变换节点下,并设置平移、缩放和旋转变换,使模型位于正确的位置和朝向。
  6. 创建osg::PositionAttitudeTransform节点,将矩阵变换节点添加到位置姿态变换节点下。
  7. 创建osg::AnimationPathCallback并设置为位置姿态变换节点的更新回调,使模型按照动画路径进行移动。
  8. 将位置姿态变换节点添加到根节点下。
  9. 如果支持OpenGL的GL_NORMALIZE特性,开启法线的自动归一化。
  10. 返回根节点作为最终结果。

? ? ? ? 通过以上步骤,函数创建了一个包含滑翔机和塞斯纳飞机模型的移动模型,并将其放置在指定的中心点附近,使其按照预设的动画路径进行移动。


六、【3D动画场景】组合模型

1、实现目的

? ? ? ?我们需要将基础模型和移动模型组合在一起形成一个完整的场景。通过创建一个根节点,并将基础模型和移动模型添加为其子节点,我们可以将它们组合在一起。这样,当我们渲染根节点时,所有的模型都会被一起渲染出来。

2、实现组合模型步骤

  • 创建一个根节点(osg::Group)作为场景的根节点。
  • 将基础模型和移动模型添加为根节点的子节点。

3、核心代码

osg::ref_ptr<osg::Group> createModel(bool overlay, osgSim::OverlayNode::OverlayTechnique technique)
{
    osg::Vec3 center(0.0f, 0.0f, 0.0f);  // 模型中心点坐标
    float radius = 100.0f;  // 模型半径

    osg::ref_ptr<osg::Group> root = new osg::Group;  // 创建根节点

    float baseHeight = center.z() - radius * 0.5;  // 基础模型高度为中心点z坐标减去半径的一半
    osg::ref_ptr<osg::Node> baseModel = createBase(osg::Vec3(center.x(), center.y(), baseHeight), radius);  // 创建基础模型
    osg::ref_ptr<osg::Node> movingModel = createMovingModel(center, radius * 0.8f);  // 创建移动模型

    if (overlay)
    {
        osgSim::OverlayNode* overlayNode = new osgSim::OverlayNode(technique);  // 创建覆盖节点
        overlayNode->setContinuousUpdate(true);  // 设置节点连续更新
        overlayNode->setOverlaySubgraph(movingModel);  // 设置移动模型为覆盖子图
        overlayNode->setOverlayBaseHeight(baseHeight - 0.01);  // 设置覆盖基础高度
        overlayNode->addChild(baseModel);  // 将基础模型添加到覆盖节点下
        root->addChild(overlayNode);  // 将覆盖节点添加到根节点下
    }
    else
    {
        root->addChild(baseModel);  // 将基础模型添加到根节点下
    }

    root->addChild(movingModel);  // 将移动模型添加到根节点下

    return root;  // 返回根节点
}

4、知识要点

? ? ? ?代码定义了一个创建模型的函数。函数接受两个参数,一个是布尔值overlay,用于决定是否使用覆盖节点;另一个是osgSim::OverlayNode::OverlayTechnique,表示覆盖节点的技术。

具体实现步骤如下:

  1. 定义模型的中心点坐标和半径。
  2. 创建一个根节点(osg::Group)作为模型的容器。
  3. 根据中心点和半径创建基础模型(createBase函数返回一个osg::Node对象)。
  4. 根据中心点和缩小后的半径创建移动模型(createMovingModel函数返回一个osg::Node对象)。
  5. 如果使用覆盖节点:
    • 创建一个osgSim::OverlayNode覆盖节点,并设置覆盖节点的技术。
    • 开启覆盖节点的连续更新。
    • 设置移动模型为覆盖子图。
    • 设置覆盖基础高度为基础模型的高度减去一个小的偏移量。
    • 将基础模型添加到覆盖节点下。
    • 将覆盖节点添加到根节点下。
  6. 否则,直接将基础模型添加到根节点下。
  7. 将移动模型添加到根节点下。
  8. 返回根节点作为最终结果。

? ? ? 通过以上步骤,函数可以根据传入的参数创建一个包含基础模型和移动模型的场景,并根据需要使用覆盖节点来处理渲染顺序。

5、osgSim::OverlayNode详讲

? ? ? osgSim::OverlayNode是一个OSG场景图节点,它可以将多个子图层叠在一起进行渲染,以实现覆盖、透明和混合等效果。OverlayNode节点可以使用不同的覆盖技术(OverlayTechnique)实现不同的渲染效果。

osgSim::OverlayNode节点的主要成员函数包括:

  • setOverlaySubgraph(osg::Node* node):设置覆盖子图。
  • setOverlayBaseHeight(float height):设置覆盖基础高度。覆盖节点中的所有子图都将按照其基础高度进行排序,从低到高依次渲染,以实现正确的覆盖效果。
  • setOverlayTechnique(OverlayTechnique technique):设置覆盖技术。
  • setContinuousUpdate(bool update):开启/关闭节点的连续更新功能。如果开启,节点会在每一帧重新计算并更新覆盖子图的位置和方向,以实现移动、旋转等效果。
  • setOverlayTextureSize(unsigned int width, unsigned int height):设置覆盖纹理的大小。

覆盖技术(OverlayTechnique)有三种:

  • OVERLAY_NONE:不使用覆盖技术。
  • OVERLAY_CAMERA_DISTANCE_RATIO:使用相机距离比例覆盖技术。该技术根据相机与场景中各覆盖节点的距离关系,计算出每个节点的渲染顺序。
  • OVERLAY_PRIORITY:使用优先级覆盖技术。该技术根据节点的优先级,决定其渲染顺序。

? ? ? ?osgSim::OverlayNode可以实现多种渲染效果,如透明、混合和覆盖等。在实际应用中,它通常与其他节点一起使用,以实现更加复杂的渲染效果。例如,可以将多个模型组合在一起,并使用osgSim::OverlayNode来实现正确的覆盖效果。


七、【3D动画场景】程序

结合上述代码,添加程序入口,编制qt pro文件,可实现对应的效果。

1、程序代码


#include <osg/Notify>
#include <osg/MatrixTransform>
#include <osg/PositionAttitudeTransform>
#include <osg/Geometry>
#include <osg/Geode>

#include <osgUtil/Optimizer>

#include <osgDB/Registry>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>

#include <osgGA/TrackballManipulator>
#include <osgGA/FlightManipulator>
#include <osgGA/DriveManipulator>

#include <osgSim/OverlayNode>

#include <osgViewer/Viewer>
#include <iostream>

// 创建一个动画路径,以 center 为圆心,radius 为半径,looptime 为循环时间
osg::AnimationPath* createAnimationPath(const osg::Vec3& center, float radius, double looptime)
{
    // 设置动画路径的循环模式为 LOOP
    osg::ref_ptr<osg::AnimationPath> animationPath = new osg::AnimationPath;
    animationPath->setLoopMode(osg::AnimationPath::LOOP);

    int numSamples = 40; // 定义采样点数为 40
    float yaw = 0.0f; // 初始化偏航角为 0
    float yaw_delta = 2.0f * osg::PI / (numSamples - 1.0f); // 每个采样点之间的偏航角度增量
    float roll = osg::inDegrees(30.0f); // 翻滚角度为 30 度

    double time = 0.0f; // 初始化时间为 0
    double time_delta = looptime / numSamples; // 时间增量为循环时间除以采样点数
    for (int i = 0; i < numSamples; ++i) // 循环遍历每个采样点
    {
        // 根据当前的偏航角度计算当前位置
        osg::Vec3 position(center + osg::Vec3(sinf(yaw) * radius, cosf(yaw) * radius, 0.0f));
        
        // 根据当前的偏航角度和翻滚角度计算当前旋转角度
        osg::Quat rotation(osg::Quat(roll, osg::Vec3(0.0, 1.0, 0.0)) * osg::Quat(-(yaw + osg::inDegrees(90.0f)), osg::Vec3(0.0, 0.0, 1.0)));

        // 将当前位置和旋转角度插入到动画路径中
        animationPath->insert(time, osg::AnimationPath::ControlPoint(position, rotation));

        // 更新偏航角度和时间
        yaw += yaw_delta;
        time += time_delta;
    }
    
    // 返回创建好的动画路径
    return animationPath.release();
}

//创建基础模型
osg::Node* createBase(const osg::Vec3& center, float radius)
{
    int numTilesX = 10; // 网格行数
    int numTilesY = 10; // 网格列数
    float width = 2 * radius; // 网格宽度
    float height = 2 * radius; // 网格高度

    osg::Vec3 v000(center - osg::Vec3(width * 0.5f, height * 0.5f, 0.0f)); // 左下角顶点坐标
    osg::Vec3 dx(osg::Vec3(width / static_cast<float>(numTilesX), 0.0f, 0.0f)); // 横向增量
    osg::Vec3 dy(osg::Vec3(0.0f, height / static_cast<float>(numTilesY), 0.0f)); // 纵向增量

    osg::Vec3Array* coords = new osg::Vec3Array; // 顶点数组
    int iy;
    for (iy = 0; iy <= numTilesY; ++iy)
    {
        for (int ix = 0; ix <= numTilesX; ++ix)
        {
            coords->push_back(v000 + dx * static_cast<float>(ix) + dy * static_cast<float>(iy)); // 添加网格的角点
        }
    }

    osg::Vec4Array* colors = new osg::Vec4Array; // 颜色数组
    colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f)); // 白色
    colors->push_back(osg::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); // 黑色

    osg::ref_ptr<osg::DrawElementsUShort> whitePrimitives = new osg::DrawElementsUShort(GL_QUADS); // 白色网格的DrawElements对象
    osg::ref_ptr<osg::DrawElementsUShort> blackPrimitives = new osg::DrawElementsUShort(GL_QUADS); // 黑色网格的DrawElements对象

    int numIndicesPerRow = numTilesX + 1;
    for (iy = 0; iy < numTilesY; ++iy)
    {
        for (int ix = 0; ix < numTilesX; ++ix)
        {
            osg::DrawElementsUShort* primitives = ((iy + ix) % 2 == 0) ? whitePrimitives.get() : blackPrimitives.get(); // 根据行列号确定网格颜色
            primitives->push_back(ix + (iy + 1) * numIndicesPerRow); // 添加四个顶点到DrawElements对象中
            primitives->push_back(ix + iy * numIndicesPerRow);
            primitives->push_back((ix + 1) + iy * numIndicesPerRow);
            primitives->push_back((ix + 1) + (iy + 1) * numIndicesPerRow);
        }
    }

    osg::Vec3Array* normals = new osg::Vec3Array; // 法向量数组
    normals->push_back(osg::Vec3(0.0f, 0.0f, 1.0f)); // 所有网格共用同一个法向量

    osg::Geometry* geom = new osg::Geometry; // Geometry对象
    geom->setVertexArray(coords); // 设置顶点数组
    geom->setColorArray(colors, osg::Array::BIND_PER_PRIMITIVE_SET); // 设置颜色数组和绑定方式
    geom->setNormalArray(normals, osg::Array::BIND_OVERALL); // 设置法向量数组和绑定方式
    geom->addPrimitiveSet(whitePrimitives.get()); // 添加白色网格的DrawElements对象
    geom->addPrimitiveSet(blackPrimitives.get()); // 添加黑色网格的DrawElements对象

    osg::Geode* geode = new osg::Geode; // Geode节点
    geode->addDrawable(geom); // 在Geode中添加Geometry对象

    return geode; // 返回Geode节点作为结果
}

//创建移动模型
osg::Node* createMovingModel(const osg::Vec3& center, float radius)
{
    float animationLength = 10.0f;  // 动画播放时长

    // 创建动画路径
    osg::ref_ptr<osg::AnimationPath> animationPath = createAnimationPath(center, radius, animationLength);

    osg::ref_ptr<osg::Group> model = new osg::Group;  // 创建根节点

    // 导入滑翔机模型
    osg::ref_ptr<osg::Node> glider = osgDB::readRefNodeFile("../OpenSceneGraph-Data/glider.osgt");
    if (glider)
    {
        const osg::BoundingSphere& bs = glider->getBound();  // 获取模型的包围球信息

        float size = radius / bs.radius() * 0.3f;  // 根据给定的半径计算模型的大小
        osg::ref_ptr<osg::MatrixTransform> positioned = new osg::MatrixTransform;  // 创建矩阵变换节点
        positioned->setDataVariance(osg::Object::STATIC);  // 设置数据不会频繁改变
        positioned->setMatrix(
            osg::Matrix::translate(-bs.center()) *
            osg::Matrix::scale(size, size, size) *
            osg::Matrix::rotate(osg::inDegrees(-90.0f), 0.0f, 0.0f, 1.0f));  // 设置模型的平移、缩放和旋转变换

        positioned->addChild(glider);  // 将模型添加到矩阵变换节点下

        osg::ref_ptr<osg::PositionAttitudeTransform> xform = new osg::PositionAttitudeTransform;  // 创建位置姿态变换节点
        xform->setUpdateCallback(new osg::AnimationPathCallback(animationPath, 0.0, 1.0));  // 设置更新回调,使模型按照动画路径进行移动
        xform->addChild(positioned);  // 将矩阵变换节点添加到位置姿态变换节点下

        model->addChild(xform);  // 将位置姿态变换节点添加到根节点下
    }

    // 导入塞斯纳飞机模型
    osg::ref_ptr<osg::Node> cessna = osgDB::readRefNodeFile("../OpenSceneGraph-Data/cessna.osgt");
    if (cessna)
    {
        const osg::BoundingSphere& bs = cessna->getBound();  // 获取模型的包围球信息

        float size = radius / bs.radius() * 0.3f;  // 根据给定的半径计算模型的大小
        osg::ref_ptr<osg::MatrixTransform> positioned = new osg::MatrixTransform;  // 创建矩阵变换节点
        positioned->setDataVariance(osg::Object::STATIC);  // 设置数据不会频繁改变
        positioned->setMatrix(
            osg::Matrix::translate(-bs.center()) *
            osg::Matrix::scale(size, size, size) *
            osg::Matrix::rotate(osg::inDegrees(180.0f), 0.0f, 0.0f, 1.0f));  // 设置模型的平移、缩放和旋转变换

        positioned->addChild(cessna);  // 将模型添加到矩阵变换节点下

        osg::ref_ptr<osg::MatrixTransform> xform = new osg::MatrixTransform;  // 创建矩阵变换节点
        xform->setUpdateCallback(new osg::AnimationPathCallback(animationPath, 0.0f, 2.0));  // 设置更新回调,使模型按照动画路径进行移动
        xform->addChild(positioned);  // 将矩阵变换节点添加到矩阵变换节点下

        model->addChild(xform);  // 将矩阵变换节点添加到根节点下
    }

#ifndef OSG_GLES2_AVAILABLE
    model->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON);
#endif

    return model.release();  // 返回根节点
}

//组合模型
osg::ref_ptr<osg::Group> createModel(bool overlay, osgSim::OverlayNode::OverlayTechnique technique)
{
    osg::Vec3 center(0.0f, 0.0f, 0.0f);  // 模型中心点坐标
    float radius = 100.0f;  // 模型半径

    osg::ref_ptr<osg::Group> root = new osg::Group;  // 创建根节点

    float baseHeight = center.z() - radius * 0.5;  // 基础模型高度为中心点z坐标减去半径的一半
    osg::ref_ptr<osg::Node> baseModel = createBase(osg::Vec3(center.x(), center.y(), baseHeight), radius);  // 创建基础模型
    osg::ref_ptr<osg::Node> movingModel = createMovingModel(center, radius * 0.8f);  // 创建移动模型

    if (overlay)
    {
        osgSim::OverlayNode* overlayNode = new osgSim::OverlayNode(technique);  // 创建覆盖节点
        overlayNode->setContinuousUpdate(true);  // 设置节点连续更新
        overlayNode->setOverlaySubgraph(movingModel);  // 设置移动模型为覆盖子图
        overlayNode->setOverlayBaseHeight(baseHeight - 0.01);  // 设置覆盖基础高度
        overlayNode->addChild(baseModel);  // 将基础模型添加到覆盖节点下
        root->addChild(overlayNode);  // 将覆盖节点添加到根节点下
    }
    else
    {
        root->addChild(baseModel);  // 将基础模型添加到根节点下
    }

    root->addChild(movingModel);  // 将移动模型添加到根节点下

    return root;  // 返回根节点
}

//主入口
int main(int argc, char **argv)
{
    bool overlay = false; // 是否使用覆盖效果
    osg::ArgumentParser arguments(&argc, argv);
    while (arguments.read("--overlay")) overlay = true; // 从命令行参数中读取是否开启覆盖效果

    osgSim::OverlayNode::OverlayTechnique technique = osgSim::OverlayNode::OBJECT_DEPENDENT_WITH_ORTHOGRAPHIC_OVERLAY;
    while (arguments.read("--object")) { technique = osgSim::OverlayNode::OBJECT_DEPENDENT_WITH_ORTHOGRAPHIC_OVERLAY; overlay = true; } // 从命令行参数中读取是否使用对象相关的正交投影覆盖技术
    while (arguments.read("--ortho") || arguments.read("--orthographic")) { technique = osgSim::OverlayNode::VIEW_DEPENDENT_WITH_ORTHOGRAPHIC_OVERLAY; overlay = true; } // 从命令行参数中读取是否使用视图相关的正交投影覆盖技术
    while (arguments.read("--persp") || arguments.read("--perspective")) { technique = osgSim::OverlayNode::VIEW_DEPENDENT_WITH_PERSPECTIVE_OVERLAY; overlay = true; } // 从命令行参数中读取是否使用视图相关的透视投影覆盖技术


    // 初始化Viewer
    osgViewer::Viewer viewer;

    // 从命令行参数加载节点
    osg::ref_ptr<osg::Group> model = createModel(overlay, technique);
    if (!model)
    {
        return 1;
    }

    // 倾斜场景,使默认视角从上方俯视模型
    osg::ref_ptr<osg::MatrixTransform> rootnode = new osg::MatrixTransform;
    rootnode->setMatrix(osg::Matrix::rotate(osg::inDegrees(30.0f), 1.0f, 0.0f, 0.0f));
    rootnode->addChild(model);

    // 对场景图进行优化
    osgUtil::Optimizer optimizer;
    optimizer.optimize(rootnode);

    std::string filename;
    if (arguments.read("-o", filename))
    {
        osgDB::writeNodeFile(*rootnode, filename);
        return 1;
    }

    // 设置要渲染的场景
    viewer.setSceneData(rootnode);

    viewer.setCameraManipulator(new osgGA::TrackballManipulator());

    return viewer.run(); // 启动Viewer的主循环,进入渲染状态
}

2、qt pro文件

QT += core

TEMPLATE = app
CONFIG += console

DESTDIR = ../3rdParty
if(contains(DEFINES,MSVC2015)){
    DESTDIR = ../3rdParty-2015
    CONFIG(debug, debug|release){
        TARGET = eg_animated
        MOC_DIR = ../build-OpenSceneGraph-2015/eg_animate/Debug/moc
        RCC_DIR = ../build-OpenSceneGraph-2015/eg_animate/Debug/rcc
        UI_DIR = ../build-OpenSceneGraph-2015/eg_animate/Debug/ui
        OBJECTS_DIR = ../build-OpenSceneGraph-2015/eg_animate/Debug/obj
    }else{
        TARGET = eg_animate
        MOC_DIR = ../build-OpenSceneGraph-2015/eg_animate/Release/moc
        RCC_DIR = ../build-OpenSceneGraph-2015/eg_animate/Release/rcc
        UI_DIR = ../build-OpenSceneGraph-2015/eg_animate/Release/ui
        OBJECTS_DIR = ../build-OpenSceneGraph-2015/eg_animate/Release/obj
    }
}else{
    DESTDIR = ../3rdParty
    CONFIG(debug, debug|release){
        TARGET = eg_animated
        MOC_DIR = ../build-OpenSceneGraph/eg_animate/Debug/moc
        RCC_DIR = ../build-OpenSceneGraph/eg_animate/Debug/rcc
        UI_DIR = ../build-OpenSceneGraph/eg_animate/Debug/ui
        OBJECTS_DIR = ../build-OpenSceneGraph/eg_animate/Debug/obj
    }else{
        TARGET = eg_animate
        MOC_DIR = ../build-OpenSceneGraph/eg_animate/Release/moc
        RCC_DIR = ../build-OpenSceneGraph/eg_animate/Release/rcc
        UI_DIR = ../build-OpenSceneGraph/eg_animate/Release/ui
        OBJECTS_DIR = ../build-OpenSceneGraph/eg_animate/Release/obj
    }
}

DEFINES -= UNICODE _UNICODE
win32 {
    DEFINES += _CRT_SECURE_NO_DEPRECATE _CRT_NONSTDC_NO_DEPRECATE
}

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

#当前目录
INCLUDEPATH += ./ ./include
#LIBS
#LIBS
if(contains(DEFINES,MSVC2015)){
    LIBS += -L../3rdParty-2015
}else{
    LIBS += -L../3rdParty
}
CONFIG(debug, debug|release){
    LIBS += -lOpenThreadsd -losgd -losgDBd -losgUtild -losgGAd -losgAnimationd -losgSimd -losgViewerd
}else{
    LIBS += -lOpenThreads -losg -losgDB -losgUtil -losgGA -losgAnimation -losgSim -losgViewer
}
#win32: LIBS += -lopengl32

SOURCES +=  ./examples/osganimate/osganimate.cpp


# Default rules for deployment.
#unix {
#    target.path = /usr/lib
#}
#!isEmpty(target.path): INSTALLS += target

八、【3D动画场景】总结

? ? ? 在本节文章中,我们介绍了如何使用OSG创建一个动画场景。通过创建动画路径、基础模型和移动模型,并将它们组合在一起,我们可以创建一个逼真的3D场景。同时,我们还讨论了如何使用叠加节点来添加额外的效果,详细讲解了osg::AnimationPath、osg::Geometry、osgSim::OverlayNode等类和属性。

? ? ? 希望本文对你理解OSG的使用有所帮助,并能够启发你创建更加精彩的3D场景!

文章来源:https://blog.csdn.net/LuckyHanMo/article/details/135517004
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。