阅读视图

发现新文章,点击刷新页面。

【OSG学习笔记】Day 6: Group类与MatrixTransform类

去除图片水印 (1).png

Group与MatrixTransform

OpenSceneGraph(OSG)的场景图架构是其实现高性能3D渲染的核心,而osg::Grouposg::MatrixTransform作为场景图的关键节点,前者是“骨架”,后者是“骨骼的运动控制器”——MatrixTransform继承自Group,既拥有子节点管理能力,又扩展了矩阵驱动的空间变换能力。

本文将从继承关系、核心逻辑、代码实践三个维度解析二者的关联,并对比MatrixTransformPositionAttitudeTransform(PAT)的核心差异。

Group与MatrixTransform的核心关系

1 继承链:MatrixTransform是Group的“功能增强版”

OSG的节点体系遵循“分层封装”的设计理念,MatrixTransform的完整继承链如下:

osg::Referenced(内存管理)
    ↓
osg::Object(基础对象能力:命名、克隆)
    ↓
osg::Node(场景图节点基类:可遍历、可渲染)
    ↓
osg::Group(子节点管理:容器能力)
    ↓
osg::Transform(变换抽象基类:定义坐标转换接口)
    ↓
osg::MatrixTransform(矩阵变换节点:具体实现)

从继承关系可明确核心逻辑:

  • osg::Group是基础容器:提供addChild()/removeChild()/getChild()等子节点管理接口,是所有“可包含子节点”的节点的基类,本身无变换能力;
  • osg::Transform是变换抽象层:定义了computeLocalToWorldMatrix()等坐标转换接口,但未实现具体变换逻辑;
  • osg::MatrixTransform是具体实现:继承Group的容器能力,同时实现Transform的变换接口,通过4×4矩阵直接控制子节点的空间变换。

简言之:MatrixTransform = Group的子节点管理能力 + 矩阵驱动的空间变换能力

2 核心关联

MatrixTransform的核心价值是“对一组子节点施加统一的空间变换”,而这一价值的实现完全依赖Group的能力:

  1. 无Group则无变换目标MatrixTransform自身无几何数据、不可渲染,必须通过GroupaddChild()添加子节点(模型、几何、其他节点),才能将变换作用于具体渲染对象;
  2. Group的遍历逻辑适配变换传递Group定义了子节点的遍历规则,MatrixTransform继承后,其变换矩阵会自动传递给所有子节点——子节点的“局部坐标→世界坐标”会叠加MatrixTransform的矩阵变换;
  3. Group的线程安全复用Group内置了多线程安全的子节点管理逻辑,MatrixTransform无需重复实现,可直接在OSG的多线程渲染环境(渲染线程、更新线程)中安全增删子节点。

代码实践:Group与MatrixTransform的综合使用

以下示例完整展示如何通过osg::Group构建场景图骨架,并通过MatrixTransform实现子节点的空间变换,同时体现二者的核心关联。

1 完整可运行代码

#include <osgViewer/Viewer>
#include <osg/Group>
#include <osg/MatrixTransform>
#include <osgDB/ReadFile>
#include <osgUtil/Optimizer>
#include <osg/Notify>

// 构建带矩阵变换的模型节点
osg::ref_ptr<osg::Node> createTransformedModel(
    const std::string& modelPath, 
    const osg::Matrixd& transformMat)
{
    // 1. 加载模型(返回osg::Node,可能是Group/Geode)
    osg::ref_ptr<osg::Node> model = osgDB::readNodeFile(modelPath);
    if (!model)
    {
        osg::notify(osg::FATAL) << "模型加载失败:" << modelPath << std::endl;
        return nullptr;
    }

    // 2. 创建MatrixTransform节点(继承Group,可添加子节点)
    osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform();
    // 设置变换矩阵:核心逻辑,所有子节点会应用该矩阵
    mt->setMatrix(transformMat);

    // 3. 继承自Group的核心能力:添加子节点(变换作用于子节点)
    mt->addChild(model.get());

    return mt;
}

int main()
{
    // 1. 创建场景根节点(纯Group,仅做容器,无变换)
    osg::ref_ptr<osg::Group> root = new osg::Group();
    root->setName("SceneRoot"); // Group继承自Object的命名能力

    // 2. 构建两个不同的变换矩阵
    // 矩阵1:平移(X=-10)+ 缩放(0.5倍)+ 无旋转
    osg::Matrixd mat1;
    mat1.makeTranslate(osg::Vec3(-10.0f, 0.0f, 0.0f)); // 平移
    mat1 *= osg::Matrixd::scale(osg::Vec3(0.5f, 0.5f, 0.5f)); // 缩放

    // 矩阵2:平移(X=10)+ 原始大小 + 绕Y轴旋转90度
    osg::Matrixd mat2;
    mat2.makeTranslate(osg::Vec3(10.0f, 0.0f, 0.0f)); // 平移
    mat2 *= osg::Matrixd::rotate(osg::PI/2, osg::Vec3(0, 1, 0)); // 旋转

    // 3. 创建两个带变换的模型节点
    osg::ref_ptr<osg::Node> model1 = createTransformedModel("cow.osg", mat1);
    osg::ref_ptr<osg::Node> model2 = createTransformedModel("cow.osg", mat2);

    if (model1 && model2)
    {
        // 4. Group的核心能力:添加子节点(管理所有变换节点)
        root->addChild(model1.get());
        root->addChild(model2.get());
    }

    // 5. 优化场景图(Group的遍历逻辑适配优化)
    osgUtil::Optimizer optimizer;
    optimizer.optimize(root.get());

    // 6. 渲染场景
    osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
    viewer->setSceneData(root.get());
    viewer->realize();
    return viewer->run();
}

image.png

2 代码核心解析

(1)场景图结构:Group为骨架,MatrixTransform为运动节点

示例的场景图树形结构清晰体现了二者的关系:

root (osg::Group,纯容器)
  ├─ mt1 (osg::MatrixTransform,带mat1变换)
  │   └─ cow.osg (模型节点,被mt1变换)
  └─ mt2 (osg::MatrixTransform,带mat2变换)
      └─ cow.osg (模型节点,被mt2变换)
  • root是纯osg::Group,仅负责组织子节点,无任何变换逻辑;
  • mt1/mt2MatrixTransform,继承GroupaddChild()能力,将变换矩阵作用于子节点cow.osg
  • 同一个模型被两个MatrixTransform包裹,实现不同的空间变换,且通过OSG的引用计数共享模型数据,无内存冗余。
(2)关键API的继承关系体现
代码片段 所属父类 核心作用
root->addChild(model1.get()) osg::Group Group的核心能力:添加子节点,构建场景图层级
mt->addChild(model.get()) osg::Group MatrixTransform继承Group的能力,为变换指定目标节点
mt->setMatrix(transformMat) osg::MatrixTransform 扩展能力:设置变换矩阵,该矩阵会作用于所有子节点
root->setName("SceneRoot") osg::Object 所有节点都继承Object的基础能力(命名、克隆等)
(3)矩阵变换的执行逻辑

MatrixTransform的核心是“矩阵驱动变换”:

  1. 开发者手动构建4×4变换矩阵(mat1/mat2),包含平移、旋转、缩放等逻辑;
  2. setMatrix()将矩阵存入MatrixTransform
  3. OSG遍历场景图时,MatrixTransform会将自身矩阵与父节点矩阵叠加,计算出子节点的“世界坐标”;
  4. 渲染线程根据世界坐标绘制模型,最终呈现“左侧缩小牛、右侧旋转牛”的效果。

MatrixTransform与PositionAttitudeTransform(PAT)的核心区别

MatrixTransformPAT都是osg::Transform的子类,且均继承osg::Group,但设计理念和使用场景差异显著,是OSG开发中最易混淆的两个变换节点。

1 核心设计差异

特性 osg::MatrixTransform osg::PositionAttitudeTransform (PAT)
设计理念 底层实现:直接操作4×4变换矩阵,完全暴露矩阵逻辑 上层封装:将变换拆解为“位置、姿态、缩放、枢轴点”四个语义化参数
核心存储 单个osg::Matrixd(4×4双精度矩阵) 四个独立参数:position(Vec3)attitude(Quat)scale(Vec3)pivotPoint(Vec3)
易用性 低:需掌握矩阵运算(平移/旋转/缩放的矩阵组合) 高:无需矩阵知识,直接设置“位置/旋转/缩放”即可
灵活性 极高:支持任意线性变换(平移、旋转、缩放、剪切、投影等) 中等:仅支持“平移+旋转+缩放+枢轴偏移”的仿射变换
旋转实现 需手动构建旋转矩阵,若用欧拉角易触发万向节死锁 基于四元数(osg::Quat)旋转,天然避免万向节死锁
动态修改 繁琐:需读取矩阵→修改分量→写回矩阵 便捷:直接修改position/scale等参数,无需关心矩阵结构
性能 无参数组合开销,高频修改矩阵时更高效 参数修改时自动计算矩阵,常规场景性能略高

2 适用场景选择

优先用MatrixTransform的场景
  1. 需实现特殊变换(如模型剪切、镜像、透视投影、自定义坐标系统转换);
  2. 需复用外部矩阵数据(如从传感器、物理引擎、其他3D引擎获取的变换矩阵);
  3. 高频修改变换矩阵(如实时物理模拟、骨骼动画、动态坐标映射);
  4. 需自定义变换顺序(如先缩放→再旋转→最后平移,与PAT默认顺序不同)。
优先用PAT的场景
  1. 常规3D变换(平移、旋转、缩放),追求开发效率和低出错率;
  2. 团队中开发人员矩阵知识薄弱,无需关注矩阵底层逻辑;
  3. 3D旋转场景(如无人机、机器人姿态控制),需避免万向节死锁;
  4. 动态修改单个变换参数(如仅调整位置,不影响旋转/缩放)。

3 代码层面的对比示例

PAT的简化写法(等价于上述代码的mat1)
osg::ref_ptr<osg::PositionAttitudeTransform> pat = new osg::PositionAttitudeTransform();
pat->setPosition(osg::Vec3(-10.0f, 0.0f, 0.0f)); // 平移(替代mat1.makeTranslate)
pat->setScale(osg::Vec3(0.5f, 0.5f, 0.5f));       // 缩放(替代mat1.scale)
// 无需手动构建矩阵,直接设置语义化参数

可见:PAT是对MatrixTransform的“易用性封装”,底层仍会将参数转换为变换矩阵,只是屏蔽了矩阵运算的细节。

总结

  1. Group与MatrixTransform的核心关系MatrixTransform继承osg::Group,既拥有“子节点管理”的容器能力,又扩展了“矩阵驱动变换”的核心功能,是OSG实现“批量空间变换”的基础;
  2. 场景图设计逻辑:Group是场景图的“骨架”,负责组织节点;MatrixTransform是“运动控制器”,负责驱动节点的空间位置;二者结合构成OSG场景图“分层管理、批量控制”的核心架构;
  3. MatrixTransform与PAT的选型原则:常规变换选PAT(易用、避坑),特殊变换选MatrixTransform(灵活、底层),二者均继承Group的容器能力,可嵌套使用以实现复杂场景需求。

去除图片水印.png

【OSG学习笔记】Day 5: Group类与PositionAttitudeTransform类

去除图片水印 (1).png

OSG核心节点:osg::Group与PositionAttitudeTransform

OpenSceneGraph(OSG)作为高性能的跨平台3D图形引擎,其核心设计思想是场景图(Scene Graph) ——通过树形结构组织所有渲染对象,而osg::Group及其子类是构建场景图的基石。

本文将从继承关系、核心功能、代码实践三个维度,详解osg::Group类,并重点分析其重要子类PositionAttitudeTransform(PAT)的使用场景与实现逻辑。

osg::Group:场景图的“骨架”节点

1 继承关系:OSG节点体系的核心分支

osg::Group是OSG场景图中组节点的基类,其完整继承链如下:

osg::Referenced(引用计数基类)
    ↓
osg::Object(OSG对象基类,提供命名/克隆等基础功能)
    ↓
osg::Node(所有场景节点的根类)
    ↓
osg::Group(组节点基类)
  • 核心父类说明
    • osg::Referenced:为所有OSG对象提供线程安全的引用计数内存管理,避免手动new/delete导致的内存泄漏;
    • osg::Node:定义了场景节点的核心接口(如遍历、渲染状态、更新回调),是所有可加入场景图的对象的基类;
    • osg::Group:在osg::Node基础上扩展了子节点管理能力,是唯一能包含其他节点的核心类。

2 核心功能:场景图的“容器”

osg::Group的核心价值是管理子节点集合,它本身不包含可渲染的几何数据,仅负责组织、遍历和传递渲染状态,主要能力包括:

  1. 子节点增删查改:提供addChild()/removeChild()/getChild()/getNumChildren()等接口,支持动态维护子节点列表;
  2. 渲染状态继承:组节点设置的渲染状态(如纹理、材质)会自动传递给所有子节点(可通过StateSet控制);
  3. 遍历控制:支持设置NodeVisitor遍历回调,自定义子节点的遍历逻辑(如裁剪、拣选);
  4. 线程安全:基于osg::Referenced的线程安全设计,多线程环境下增删子节点仍能保证稳定。

3 关键特性与使用场景

  • 无数量限制:一个osg::Group可包含任意数量的子节点(包括其他osg::Grouposg::Geode),形成多层级树形结构;
  • 场景分块管理:常用于按功能划分场景(如“地形组”“模型组”“特效组”),便于批量控制显隐、更新;
  • 不可渲染osg::Group本身无几何数据,若需渲染需结合osg::Geode(几何节点)使用。

PositionAttitudeTransform:节点变换的“核心工具”

PositionAttitudeTransform(简称PAT)是osg::Group的最重要子类之一,专门用于控制子节点的空间变换(位置、姿态、缩放),是OSG中实现模型位移、旋转、缩放的核心类。

1 继承关系:基于Group的变换扩展

PAT的继承链在osg::Group基础上进一步扩展:

osg::Referenced
    ↓
osg::Objectosg::Node
    ↓
osg::Grouposg::Transform(变换节点基类)
    ↓
osg::PositionAttitudeTransform
  • 关键中间类:osg::Transformosg::Transform是所有变换节点的基类,定义了“矩阵变换”的核心接口(computeLocalToWorldMatrix()/computeWorldToLocalMatrix()),但未封装具体的变换参数;
  • PositionAttitudeTransform:在osg::Transform基础上,将变换拆解为位置(Position)、姿态(Attitude)、缩放(Scale) 三个直观参数,简化了3D空间变换的使用。

2 核心功能:三维空间变换的“封装器”

PAT将复杂的矩阵变换封装为三个易用的参数,避免手动计算变换矩阵,核心参数如下:

参数 类型 作用
Position osg::Vec3 控制子节点在世界坐标系中的位置(X/Y/Z轴偏移)
Attitude osg::Quat 控制子节点的姿态(旋转),基于四元数实现无万向节死锁的3D旋转
Scale osg::Vec3 控制子节点在X/Y/Z轴上的缩放比例(1.0为原始大小,0.5为缩小一半)
Pivot Point osg::Vec3 旋转/缩放的中心点(默认是节点局部坐标系原点)

3 核心优势

  1. 直观易用:无需手动构建4×4变换矩阵,直接设置位置、旋转、缩放参数即可;
  2. 四元数旋转:基于osg::Quat的姿态控制,避免欧拉角的万向节死锁问题;
  3. 子节点批量变换:PAT的变换会作用于所有子节点,可批量控制多个模型的空间状态;
  4. 动态更新:支持运行时修改变换参数(如动画中实时调整位置/旋转),立即生效。

代码实践:Group与PAT的综合使用

下面通过完整示例,展示如何基于osg::Group构建场景图,并通过PositionAttitudeTransform实现模型的空间变换:

源码库:osg_transform

1 完整代码

#include <osgViewer/Viewer>
#include <osg/Group>
#include <osg/PositionAttitudeTransform>
#include <osg/Geode>
#include <osgDB/ReadFile>
#include <osgUtil/Optimizer>
#include <osg/Notify>

// 创建一个带PAT变换的模型节点
osg::ref_ptr<osg::Node> createTransformedModel(const std::string& modelPath, 
                                               const osg::Vec3& pos, 
                                               const osg::Vec3& scale,
                                               const osg::Quat& attitude)
{
    // 1. 加载模型(返回osg::Node,可能是Group/Geode)
    osg::ref_ptr<osg::Node> model = osgDB::readNodeFile(modelPath);
    if (!model)
    {
        osg::notify(osg::FATAL) << "模型加载失败:" << modelPath << std::endl;
        return nullptr;
    }

    // 2. 创建PAT变换节点
    osg::ref_ptr<osg::PositionAttitudeTransform> pat = new osg::PositionAttitudeTransform();
    pat->setPosition(pos);         // 设置位置
    pat->setScale(scale);          // 设置缩放
    pat->setAttitude(attitude);    // 设置旋转姿态

    // 3. 将模型添加为PAT的子节点(变换作用于模型)
    pat->addChild(model.get());

    return pat;
}

int main()
{
    // 1. 创建场景根节点(osg::Group)
    osg::ref_ptr<osg::Group> root = new osg::Group();
    root->setName("SceneRoot"); // 设置节点名称(osg::Object的能力)

    // 2. 定义变换参数
    osg::Vec3 pos1(-10.0f, 0.0f, 0.0f);  // 左侧模型位置
    osg::Vec3 scale1(0.5f, 0.5f, 0.5f);  // 缩小为0.5倍
    osg::Quat rot1(0.0f, osg::Vec3(0, 1, 0)); // 无旋转(绕Y轴0度)

    osg::Vec3 pos2(10.0f, 0.0f, 0.0f);   // 右侧模型位置
    osg::Vec3 scale2(1.0f, 1.0f, 1.0f);  // 原始大小
    osg::Quat rot2(osg::PI/2, osg::Vec3(0, 1, 0)); // 绕Y轴旋转90度

    // 3. 创建两个带不同变换的模型节点
    osg::ref_ptr<osg::Node> model1 = createTransformedModel("cow.osg", pos1, scale1, rot1);
    osg::ref_ptr<osg::Node> model2 = createTransformedModel("cow.osg", pos2, scale2, rot2);

    if (model1 && model2)
    {
        // 4. 将变换节点添加到根节点(osg::Group的核心能力)
        root->addChild(model1.get());
        root->addChild(model2.get());
    }

    // 5. 优化场景图(提升渲染性能)
    osgUtil::Optimizer optimizer;
    optimizer.optimize(root.get());

    // 6. 创建Viewer并运行
    osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
    viewer->setSceneData(root.get());
    viewer->realize(); // 初始化窗口
    return viewer->run(); // 启动渲染循环
}

2 代码解析

  1. 场景结构: 示例中场景图的树形结构为: root (osg::Group) ├─ PAT1 (PositionAttitudeTransform) │ └─ cow.osg (模型节点) └─ PAT2 (PositionAttitudeTransform) └─ cow.osg (模型节点) 同一个模型被两个PAT节点包裹,实现不同的空间变换,且共享模型数据(OSG引用计数自动管理)。

  2. 核心API说明

    • setPosition(osg::Vec3(x,y,z)):设置PAT节点的世界位置,子节点会跟随位移;
    • setScale(osg::Vec3(x,y,z)):沿X/Y/Z轴独立缩放,示例中model1缩小为0.5倍;
    • setAttitude(osg::Quat(angle, axis)):基于四元数旋转,osg::PI/2表示90度,osg::Vec3(0,1,0)表示绕Y轴旋转;
    • root->addChild()osg::Group的核心接口,将变换后的节点加入场景根节点。
  3. 运行效果: 程序运行后会显示两个牛模型:

    • 左侧牛:位置X=-10,缩放0.5倍,无旋转;
    • 右侧牛:位置X=10,原始大小,绕Y轴旋转90度。

image.png

总结

  1. osg::Group 是OSG场景图的“骨架”,核心作用是组织子节点,本身不可渲染,但支持渲染状态继承和批量控制,是构建复杂场景的基础;
  2. PositionAttitudeTransformosg::Group的核心子类,封装了3D空间变换的核心逻辑,通过位置、姿态、缩放三个参数简化了矩阵变换的使用,是实现模型位移/旋转/缩放的首选工具;
  3. 继承关系的设计体现了OSG的模块化思想:Referenced管内存、Node管节点基础能力、Group管子节点管理、Transform管变换、PAT管具体的空间变换参数,层层封装,兼顾易用性和扩展性。

掌握osg::GroupPositionAttitudeTransform的使用,是构建OSG场景图的核心基础,在此之上可进一步扩展到更复杂的变换(如MatrixTransform)、节点回调(如UpdateCallback)等高级功能。 去除图片水印.png

❌