当前位置: 首页 > news >正文

[3D数据存储] 对象 | OObject | IObject | 属性 | O<类型>Property | I<类型>Property

第二章:对象(场景节点)

在第一章:归档(文件容器)中,我们了解到归档类似于专用.zip文件,保存全部3D场景数据。但这些数据如何组织?

这正是对象(又称场景节点)概念的用武之地。

何为对象?为何需要它?

想象MayaBlender3ds Max中的3D场景,场景并非无序三角面堆砌,而是按层级组织:

  • 存在"根"组
  • 其下可能有"角色"组和"环境"组
  • “角色"组内包含"主角”,后者可能包含"身体"、“头部”、"左臂"等
  • 每个部分通常有自己的位置、旋转和缩放(“变换”)

这种结构化组织方式称为场景图层级结构,其中的每个元素(组、几何体、灯光或摄像机)本质上都是节点

在Alembic中,这些节点称为对象。

对象承担两大功能:

  1. 层级结构:构建树状结构,类似文件系统目录或DCC软件中的组。对象可包含其他对象作为"子级"
  2. 数据容器:对象本身定义场景结构,同时持有实际3D数据(存储在属性中,详见第三章:属性(数据容器))

示例场景结构:
在这里插入图片描述

code:https://github.com/lvy010/Cpp-Lib-test/tree/main/3D/Alembic

每个Alembic归档自动包含特殊"顶层"对象,作为文件内场景图的根节点,类似文件系统根目录(/)。

所有其他对象都是该顶层对象的直接或间接子级。

写入对象(OObject

使用OObject类(“O"代表"输出/写入”)在归档中创建对象及层级结构:

#include <Alembic/AbcCoreHDF5/All.h> // HDF5写入支持
#include <Alembic/Abc/All.h>         // OArchive, OObject// ... 函数内 ...
Alembic::Abc::OArchive outArchive = Alembic::Abc::OArchive
(Alembic::AbcCoreHDF5::WriteArchive(),"my_scene.abc",Alembic::Abc::MetaData(),Alembic::Abc::ErrorHandler::kThrowPolicy
);// 获取归档顶层对象
Alembic::Abc::OObject topObject = outArchive.getTop();
printf("顶层对象名称: %s\n", topObject.getName().c_str()); // 输出: 顶层对象名称: AbcExport// 创建"my_scene_group"作为顶层对象的子级
Alembic::Abc::OObject mySceneGroup(topObject, "my_scene_group");
printf("创建对象: %s\n", mySceneGroup.getFullName().c_str()); // 输出: 创建对象: /my_scene_group// 创建"Desk_Group"作为my_scene_group的子级
Alembic::Abc::OObject deskGroup(mySceneGroup, "Desk_Group");
printf("创建对象: %s\n", deskGroup.getFullName().c_str()); // 输出: 创建对象: /my_scene_group/Desk_Group// 创建"Chair_Group"作为my_scene_group的子级
Alembic::Abc::OObject chairGroup(mySceneGroup, "Chair_Group");
printf("创建对象: %s\n", chairGroup.getFullName().c_str()); // 输出: 创建对象: /my_scene_group/Chair_Group

关键点解析:

  1. outArchive.getTop():获取归档内初始OObject,默认名称常为"AbcExport",完整路径为/
  2. OObject构造函数通过父对象和名称创建子对象,建立父子关系
  3. getFullName():获取对象在归档内的完整路径

读取对象(IObject

使用IObject类(“I"代表"输入/读取”)从现有归档读取对象:

#include <Alembic/AbcCoreFactory/All.h> // IFactory
#include <Alembic/Abc/All.h>           // IArchive, IObject// ... 函数内 ...
Alembic::AbcCoreFactory::IFactory factory;
Alembic::AbcCoreFactory::IFactory::CoreType coreType;Alembic::Abc::IArchive inArchive = factory.getArchive("my_scene.abc", coreType);if (!inArchive.valid()) {printf("错误: 无法打开my_scene.abc\n");return 1;
}// 获取归档顶层对象
Alembic::Abc::IObject topObject = inArchive.getTop();
printf("根对象名称: %s\n", topObject.getName().c_str());// 获取顶层对象的直接子级数量
size_t numTopChildren = topObject.getNumChildren();
printf("根对象有 %zu 个子级\n", numTopChildren);// 访问第一个子级(应为"my_scene_group")
if (numTopChildren > 0) {// 通过索引获取子级Alembic::Abc::IObject mySceneGroup = topObject.getChild(0);printf("第一个子级: %s (完整路径: %s)\n",mySceneGroup.getName().c_str(), mySceneGroup.getFullName().c_str());// 通过名称获取子级Alembic::Abc::IObject deskGroup = mySceneGroup.getChild("Desk_Group");printf("  my_scene_group的子级: %s (完整路径: %s)\n",deskGroup.getName().c_str(), deskGroup.getFullName().c_str());// 获取对象父级Alembic::Abc::IObject parentOfDesk = deskGroup.getParent();printf("  Desk_Group的父级: %s\n", parentOfDesk.getName().c_str());
}

关键读取方法:

  1. getNumChildren():获取直接子级数量
  2. getChild(index/name)通过索引或名称获取子对象
  3. getParent():获取父对象

底层原理:对象工作机制

创建对象(OObject

在这里插入图片描述

读取对象(IObject

在这里插入图片描述

总结

本章核心要点:

  • 对象是组织3D场景数据为**层级树结构**的基础单元
  • 每个归档包含特殊顶层对象作为场景图根节点
  • OObject用于写入时创建对象层级
  • IObject用于读取时遍历层级结构

对象定义场景图中的位置,实际数据存储于属性中。下章将深入探讨:第三章:属性(数据容器)


第三章:属性(数据容器)

code: https://github.com/lvy010/Cpp-Lib-test/tree/main/3D/Alembic

在第二章:对象(场景节点)中,我们了解到对象(或场景节点)帮助我们将3D场景组织成层级结构,就像计算机中的文件夹。一个对象定义了场景中某物的位置,但它并不保存实际数据,比如桌子的形状或椅子的颜色。

那么实际数据存储在哪里呢?这就是属性的用武之地!

什么是属性?为什么需要它?

我们可以把对象想象成文件夹。文件夹对组织管理很有用,但没有内部文件时它是空的。属性就像对象文件夹内存储具体数据的文档或电子表格

例如,如果我们有一个"桌子"对象

  • 它的高度可以是数值(如0.75米)
  • 它的材质颜色可以是三个数值(红、绿、蓝)
  • 实际的3D形状(所有顶点坐标)可以是长数值列表

这些数据片段都存储在Alembic属性中。属性让我们能够以结构化方式将特定数据附加到特定对象

Alembic定义了三种主要属性类型:

  1. 标量属性(ScalarProperty):存储单个数据(如单个数、单词或简单3D向量)
  2. 数组属性(ArrayProperty):存储多个相同数据元素的列表(如网格的所有顶点位置)
  3. 复合属性(CompoundProperty):作为其他属性的容器(类似子文件夹),用于组织复杂数据

Alembic中的每个对象都自动拥有特殊的"顶层"复合属性。这是存储该对象所有标量数组和其他复合属性的地方,可以视为该对象的主数据目录。

让我们继续使用上一章的"Desk_Group"示例。我们将为其添加现实属性:高度(标量)、材质属性(组织在复合属性中)和顶点位置(数组属性)。

写入属性(O<类型>Property)

为了将数据写入Alembic归档文件中的对象,我们使用第二章中的OObject,然后创建O<类型>Property实例。

首先为桌子设置OArchiveOObject

#include <Alembic/AbcCoreHDF5/All.h> // HDF5写入支持
#include <Alembic/Abc/All.h>         // OArchive、OObject和属性// 用于V3f(3D浮点向量)示例
#include <vector>// ... 在main等函数内部 ...Alembic::Abc::OArchive outArchive = Alembic::Abc::OArchive(Alembic::AbcCoreHDF5::WriteArchive(),"my_scene_with_props.abc",Alembic::Abc::MetaData(),Alembic::Abc::ErrorHandler::kThrowPolicy
);Alembic::Abc::OObject topObject = outArchive.getTop();// 从第二章重建"Desk_Group"
Alembic::Abc::OObject deskGroup(topObject, "Desk_Group");// 获取deskGroup的顶层复合属性
Alembic::Abc::OCompoundProperty deskProps = deskGroup.getProperties();printf("准备向/Desk_Group添加属性\n");

设置完成后,deskProps成为Desk_Group对象的"数据文件夹"。

写入标量属性(OScalarProperty)

标量属性保存单个值,可以是floatintstringbool,甚至是固定大小的类型如Vec3f(3浮点向量)或Matrix44d(4x4双精度矩阵)。

添加desk_height作为double类型标量属性:

// 假设已有前文中的'deskProps'// 创建名为"desk_height"的双精度标量属性
Alembic::Abc::ODoubleProperty deskHeightProp(deskProps,      // 父属性容器"desk_height"   // 属性名称
);// 设置高度值
double height_value = 0.75; // 0.75米
deskHeightProp.set(height_value);
printf("  设置desk_height为: %.2f\n", height_value); // 输出:设置desk_height为: 0.75

写入数组属性(OArrayProperty)

数组属性存储同类型值的列表,非常适合网格顶点、UV坐标或颜色列表等数据。

添加desk_vertices作为V3f数组:

// 假设已有前文中的'deskProps'// 创建存储3D浮点向量(V3f)的数组属性
Alembic::Abc::OV3fArrayProperty deskVerticesProp(deskProps,          // 父属性容器"desk_vertices"     // 属性名称
);// 准备示例数据:两个3D点
std::vector<Alembic::Abc::V3f> points = {{0.0f, 0.0f, 0.0f}, // 第一个顶点{1.0f, 2.0f, 3.0f}  // 第二个顶点
};// 创建数组样本
Alembic::Abc::V3fArraySample pointsSample( points.data(), points.size() );// 将样本写入属性
deskVerticesProp.set(pointsSample);
printf("  写入%zu个顶点位置\n", points.size()); // 输出:写入2个顶点位置

写入复合属性(OCompoundProperty)

复合属性本身不存储数据,而是作为其他属性的容器,用于组织复杂数据结构(如包含多个标量属性的材质定义)。

创建material复合属性并添加colorroughness

// 假设已有前文中的'deskProps'// 在deskProps中创建"material"复合属性
Alembic::Abc::OCompoundProperty materialProps(deskProps, "material");// 在material中添加颜色标量属性
Alembic::Abc::OV3fProperty materialColor(materialProps, // 父属性现在是materialProps"color"        
);
Alembic::Abc::V3f color_value = {0.8f, 0.6f, 0.4f}; // RGB颜色
materialColor.set(color_value);
printf("  设置材质颜色为(%.1f, %.1f, %.1f)\n", color_value.x, color_value.y, color_value.z);// 添加粗糙度标量属性
Alembic::Abc::OFloatProperty materialRoughness(materialProps, "roughness"    
);
float roughness_value = 0.5f;
materialRoughness.set(roughness_value);
printf("  设置材质粗糙度为: %.1f\n", roughness_value);

读取属性(I<类型>Property)

从现有Alembic归档文件读取数据时,使用IObject并创建I<类型>Property实例。

设置读取环境:

#include <Alembic/AbcCoreFactory/All.h> // IFactory
#include <Alembic/Abc/All.h>           // IArchive、IObject和属性// 用于V3f示例
#include <vector>// ... 在main等函数内部 ...Alembic::AbcCoreFactory::IFactory factory;
Alembic::AbcCoreFactory::IFactory::CoreType coreType;Alembic::Abc::IArchive inArchive = factory.getArchive("my_scene_with_props.abc", coreType);// 验证并获取顶层对象
Alembic::Abc::IObject deskGroup = inArchive.getTop().getChild("Desk_Group");
Alembic::Abc::ICompoundProperty deskProps = deskGroup.getProperties();

读取标量属性

Alembic::Abc::IDoubleProperty deskHeightProp(deskProps, "desk_height");
double height_value;
deskHeightProp.get(&height_value);
printf("  读取desk_height: %.2f\n", height_value);

读取数组属性

Alembic::Abc::IV3fArrayProperty deskVerticesProp(deskProps, "desk_vertices");
Alembic::Abc::V3fArraySample sample;
deskVerticesProp.get(sample);
printf("  读取%zu个顶点位置\n", sample.size());

读取复合属性

Alembic::Abc::ICompoundProperty materialProps(deskProps, "material");Alembic::Abc::IV3fProperty materialColor(materialProps, "color");
Alembic::Abc::V3f color_value;
materialColor.get(&color_value);Alembic::Abc::IFloatProperty materialRoughness(materialProps, "roughness");
float roughness_value;
materialRoughness.get(&roughness_value);

底层原理

当创建或读取属性时,Alembic管理其在归档文件中的存储和检索。

每个属性都需要知道其数据类型(如单精度浮点或3浮点向量)和元素数量。

属性创建流程

在这里插入图片描述

实现了属性和对象的解耦,方便复用

属性读取流程

在这里插入图片描述

总结

本章我们学习了Alembic对象中的数据容器——属性

  • 标量属性:存储单个数值
  • 数组属性:存储同类型数据序列
  • 复合属性:作为属性容器实现嵌套结构
  • 使用O<类型>Property类进行数据写入
  • 使用I<类型>Property类进行数据读取

属性存储着定义3D场景的几何体、动画等核心数据。

当涉及随时间变化的动画数据时,Alembic如何管理时间维度?这正是我们接下来要探讨的内容

第四章:时间采样(动画时间线)

http://www.dtcms.com/a/321945.html

相关文章:

  • 安全基础DAY2-等级保护
  • linux-文件系统
  • AD8032ARZ-REEL7 ADI亚德诺 运算放大器 集成电路IC
  • 阿拉伯文识别技术:为连接古老智慧与数字未来铺设了关键道路
  • scratch笔记和练习-第11课:穿越峡谷
  • Cell-cultured meat: The new favorite on the future dining table
  • AR眼镜:能源行业设备维护的“安全守护者”
  • Shell脚本实现自动封禁恶意扫描IP
  • 考研复习-计算机组成原理-第四章-指令系统
  • nvm安装低版本的node失败(The system cannot find the file specified)
  • Mysql 如何使用 binlog 日志回滚操作失误的数据
  • 系统构成与 Shell 核心:从零认识操作系统的心脏与外壳
  • 物联网电能表在企业能耗监测系统中的应用
  • 人工智能与交通:出行方式的革新
  • Android 监听task 栈变化
  • 基于R语言,“上百种机器学习模型”学习教程 | Mime包
  • qt qtablewidget自定义表头
  • ubantu20.04 orin nx 显示器驱动
  • 【C++】类和对象--类中6个默认成员函数(2) --运算符重载
  • 【C#】掌握并发利器:深入理解 .NET 中的 Task.WhenAll
  • Docker容器部署前端Vue服务
  • 复杂路况误报率↓78%!陌讯轻量化模型在车辆违停识别的边缘计算优化​
  • 2025-08-08 李沐深度学习11——深度学习计算
  • 位置编码——RoPE篇
  • 机器学习算法篇(七)深入浅出K-means算法:从原理到实战全解析
  • 【深度学习新浪潮】近三年3DGS在大规模场景重建中的研究进展(2022-2025)
  • 北京上海深圳广州打捞日记
  • 一种基于空间聚类的低特征场景下多目标跟踪技术
  • 什么是ros功能包和ros节点
  • C++程序库选择:权衡与取舍的艺术——以iostream和stdio为例