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

0 阅读8分钟

去除图片水印 (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::GroupGroup的核心能力:添加子节点,构建场景图层级
mt->addChild(model.get())osg::GroupMatrixTransform继承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::MatrixTransformosg::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