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

发布于:2025-08-11 ⋅ 阅读:(9) ⋅ 点赞:(0)

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

第一章:归档(文件容器)中,我们了解到归档类似于专用.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如何管理时间维度?这正是我们接下来要探讨的内容

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


网站公告

今日签到

点亮在社区的每一天
去签到