ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Draco 元数据解码(Metadata Decoder)格式规范详解

Draco 元数据解码(Metadata Decoder)格式规范详解 图形学3D渲染【免费下载链接】dracoDraco is a library for compressing and decompressing 3D geometric meshes and point clouds. It is intended to improve the storage and transmission of 3D graphics.项目地址https://gitcode.com/gh_mirrors/draco1/draco点击查看免费下载导读本文围绕 Draco 开源 3D 几何网格与点云压缩库的官方格式规范文档 docs/spec/metadata.decoder.md系统讲解.drc比特流中元数据Metadata的解码过程与二进制布局。元数据是 Draco 流中用于携带几何体之外的说明性信息如顶点属性名称、单位、自定义键值对、嵌套子元数据的可选部分理解其解码器实现是开发自定义解码器、调试 .drc 文件、或为 Draco 扩展元数据能力的基础。读完本文你将掌握元数据解码的完整调用流程、每一字段的位级编码方式varint / UI8 / I8、键值对与嵌套结构的还原规则以及仓库中对应的源码与测试证据。一、元数据在 Draco 解码流程中的位置Draco 的解码总入口Decode()按固定顺序处理比特流解析文件头ParseHeader→ 若标志位含METADATA_FLAG_MASK则调用DecodeMetadata()→ 解码连通性数据 → 解码属性数据见 docs/spec/draco.decoder.mdvoid Decode() { ParseHeader(); if (flags METADATA_FLAG_MASK) DecodeMetadata(); DecodeConnectivityData(); DecodeAttributeData(); }这说明元数据解码是可选阶段只有当头部 flags 设置了元数据标志位时比特流中才存在元数据块。元数据不参与几何体本身的压缩而是作为一种自包含的旁路信息块存在解码失败通常只会导致Status::DRACO_ERROR而不会破坏后续几何数据解析的字节对齐因为编码与解码顺序严格对称。二、解码器整体架构与伪代码骨架2.1 顶层过程 DecodeMetadata()规范文档给出的顶层伪代码如下void DecodeMetadata() { ParseMetadataCount(); for (i 0; i num_att_metadata; i) { ParseAttributeMetadataId(i); DecodeMetadataElement(att_metadata[i]); } DecodeMetadataElement(file_metadata); }即元数据块由两部分串联构成属性元数据区先读取属性元数据个数num_att_metadata再逐个读取每个属性元数据的唯一 ID并递归解码其内容文件级元数据区紧接着解码一个属于整个几何体point cloud / mesh的文件元数据。这与仓库中MetadataDecoder::DecodeGeometryMetadata()的实现完全对应src/draco/metadata/metadata_decoder.ccbool MetadataDecoder::DecodeGeometryMetadata(DecoderBuffer *in_buffer, GeometryMetadata *metadata) { buffer_ in_buffer; uint32_t num_att_metadata 0; if (!DecodeVarint(num_att_metadata, buffer_)) return false; for (uint32_t i 0; i num_att_metadata; i) { uint32_t att_unique_id; if (!DecodeVarint(att_unique_id, buffer_)) return false; std::unique_ptrAttributeMetadata att_metadata(new AttributeMetadata()); att_metadata-set_att_unique_id(att_unique_id); if (!DecodeMetadata(static_castMetadata *(att_metadata.get()))) return false; metadata-AddAttributeMetadata(std::move(att_metadata)); } return DecodeMetadata(static_castMetadata *(metadata)); }注意伪代码中的ParseMetadataCount()与ParseAttributeMetadataId()在真实实现中合并进了同一个函数属性元数据个数与每个属性元数据的唯一 ID 均以 varUI32varint 编码的 uint32读取。AttributeMetadata的att_unique_id必须与点云中对应属性的唯一 ID 一致src/draco/metadata/geometry_metadata.h解码后的属性元数据通过AddAttributeMetadata()挂到GeometryMetadata下。2.2 顶层调用的真实入口在点云解码器中PointCloudDecoder::DecodeMetadata()会创建GeometryMetadata调用MetadataDecoder::DecodeGeometryMetadata()成功后通过point_cloud_-AddMetadata()挂载到解码结果上src/draco/compression/point_cloud/point_cloud_decoder.cc。整个调用链为PointCloudDecoder::Decode() └─ PointCloudDecoder::DecodeMetadata() └─ MetadataDecoder::DecodeGeometryMetadata() ├─ MetadataDecoder::DecodeMetadata() // 每个属性元数据 └─ MetadataDecoder::DecodeMetadata() // 文件级元数据 └─ DecodeEntry() / DecodeName()三、字段级位级布局每种类型如何编码规范文档中多次出现三类类型标注其含义如下标注含义varUI32varint 编码的无符号 32 位整数长度可变小数值占用更少字节UI8无符号 8 位整数固定 1 字节I8[sz]sz个有符号 8 位整数即sz字节的原始数据3.1 varint 编码细节varUI32的编解码实现在 src/draco/core/varint_encoding.h 与 src/draco/core/varint_decoding.h 中。规则为每个字节低 7 位存放数据最高位bit 7为 1 表示还有后续字节为 0 表示本字节是最后一个解码时按从最高有效字节到最低有效字节的顺序递归读取并左移 7 位拼装。因此值0 ~ 127只占 1 字节值128 ~ 16383占 2 字节依此类推uint32最大占用 5 字节。这正是元数据条目数、键长度、值长度等计数都用 varint的原因——绝大多数元数据条目数量都很小用 varint 可以显著节省空间。3.2 键与值的长度前缀规则规范中键/子元数据键的长度用UI81 字节无符号整数表示因此单个键名最长 255 字节。这一点在编码器侧有明确约束src/draco/metadata/metadata_encoder.ccbool MetadataEncoder::EncodeString(EncoderBuffer *out_buffer, const std::string str) { // We only support string of maximum length 255 which is using one byte to // encode the length. if (str.size() 255) return false; ... }对应解码器DecodeName()src/draco/metadata/metadata_decoder.cc读取 1 字节长度后再按长度读取原始字节。空字符串编码为单字节0x00。四、核心过程逐个拆解4.1 ParseMetadataCount()void ParseMetadataCount() { num_att_metadata varUI32 }读取属性元数据的总个数。伪代码中用独立过程表达实际实现中是DecodeGeometryMetadata()开头的第一个DecodeVarint(num_att_metadata, buffer_)调用。4.2 ParseAttributeMetadataId()void ParseAttributeMetadataId(index) { att_metadata_id[index] varUI32 }按索引依次读取每个属性元数据对应的属性唯一 IDvarUI32并据此创建带att_unique_id的AttributeMetadata对象。4.3 ParseMetadataElement()键值对与子元数据计数void ParseMetadataElement(metadata) { metadata.num_entries varUI32 for (i 0; i metadata.num_entries; i) { sz metadata.key_size[i] UI8 metadata.key[i] I8[sz] sz metadata.value_size[i] UI8 metadata.value[i] I8[sz] } metadata.num_sub_metadata varUI32 }这一段定义了单个元数据对象的二进制结构可分为三部分条目数num_entriesvarUI32即本元数据中键值对的个数条目序列对每个条目先写键名长度UI8 键名字节I8[sz]再写值长度UI8 值字节I8[sz]。注意规范中键和值的长度都用 UI8但真实实现中值长度使用的是 varint见下节 4.5且值被整体视为二进制 blob 存储子元数据个数num_sub_metadatavarUI32用于后续递归解码嵌套元数据。在仓库中这一阶段对应MetadataDecoder::DecodeMetadata()的主体循环src/draco/metadata/metadata_decoder.ccuint32_t num_entries 0; if (!DecodeVarint(num_entries, buffer_)) return false; for (uint32_t i 0; i num_entries; i) { if (!DecodeEntry(metadata)) return false; } uint32_t num_sub_metadata 0; if (!DecodeVarint(num_sub_metadata, buffer_)) return false; if (num_sub_metadata buffer_-remaining_size()) return false; for (uint32_t i 0; i num_sub_metadata; i) { metadata_stack.push_back({metadata, nullptr, ...}); }值得注意的健壮性细节num_sub_metadata解码后会与buffer_-remaining_size()比较若子元数据数量异常偏大超过剩余字节数则直接返回 false——这是一种防御性校验防止恶意或损坏的比特流触发大量无效分配。4.4 ParseSubMetadataKey()嵌套键void ParseSubMetadataKey(metadata, index) { sz metadata.sub_metadata_key_size[index] UI8 metadata.sub_metadata_key[index] I8[sz] }每个子元数据都带有一个键名1 字节长度 字节串。解码器读取该名称后通过parent_metadata-AddSubMetadata(sub_metadata_name, ...)把新解析出的子元数据挂到父元数据下。若同名子元数据已存在AddSubMetadata()返回 falsesrc/draco/metadata/metadata.cc解码即失败。4.5 DecodeMetadataElement()递归解码嵌套结构void DecodeMetadataElement(metadata) { ParseMetadataElement(metadata); for (i 0; i metadata.num_sub_metadata; i) { ParseSubMetadataKey(metadata, i); DecodeMetadataElement(metadata.sub_metadata[i]); } }这是元数据解码的递归核心先解析当前元数据的条目与子元数据计数然后对每个子元数据先读其键名再递归解码其内容形成一棵以文件级元数据为根、可任意嵌套的元数据树。真实实现采用显式栈 迭代而非递归原因在注释中写得很清楚Limit metadata nesting depth to avoid stack overflow in destructor嵌套深度上限为kMaxSubmetadataLevel 1000src/draco/metadata/metadata_decoder.cc。每个栈帧记录(parent_metadata, decoded_metadata, level)子元数据入栈后统一出栈处理层级超过 1000 即返回失败。这样既保持了与伪代码等价的解码顺序又避免了深度嵌套导致的栈溢出。4.6 DecodeEntry()单条键值对的还原void DecodeEntry(metadata) { DecodeName(entry_name); // UI8 长度 字节 DecodeVarint(data_size); // 值长度varint if (data_size 0) return false; if (data_size buffer_-remaining_size()) return false; std::vectoruint8_t entry_value(data_size); buffer_-Decode(entry_value[0], data_size); metadata-AddEntryBinary(entry_name, entry_value); }结构对应 src/draco/metadata/metadata_decoder.cc。这里有三个关键实现细节与规范伪代码略有差异以仓库源码为准键名同样走DecodeName()UI8 长度前缀值长度规范写的是 UI8实际代码用DecodeVarint读取值长度节省空间也更灵活值语义规范写I8[sz]实际代码把值作为原始字节缓冲读取std::vectoruint8_t随后统一通过AddEntryBinary()存入元数据。所有类型int、double、string、数组、二进制在序列化时都先被压平为字节缓冲解码端按EntryValue存储src/draco/metadata/metadata.h。此外还有三重防御校验data_size 0返回 false拒绝空值条目data_size buffer_-remaining_size()返回 false防止越界读取Decode失败同样返回 false。五、解码侧数据模型Metadata / EntryValue / AttributeMetadata解码结果最终落在三类对象上Metadata通用元数据容器内部是std::mapstd::string, EntryValue entries_与std::mapstd::string, std::unique_ptrMetadata sub_metadatas_src/draco/metadata/metadata.h。提供AddEntryInt/IntArray/Double/DoubleArray/String/Binary及对应GetEntry*访问接口同名条目会被覆盖同名子元数据则拒绝添加。EntryValue条目的值类型内部是std::vectoruint8_t字节缓冲支持按 int/double/string/vector 等类型读取模板GetValueT()。AttributeMetadata : Metadata带att_unique_id的属性元数据通过唯一 ID 与点云中的属性关联src/draco/metadata/geometry_metadata.hGeometryMetadata : Metadata则持有属性元数据列表att_metadatas_并提供按唯一 ID 查询的接口GetAttributeMetadataByUniqueId。解码完成后的典型使用方式通过point_cloud-GetMetadata()获取GeometryMetadata再调用GetAttributeMetadataByUniqueId(id)定位某个属性的元数据用GetEntryString(name, value)等接口读取具体键值。六、编码端对照保证可逆的关键元数据解码的字节顺序并非任意约定而是编码器的严格镜像。MetadataEncodersrc/draco/metadata/metadata_encoder.cc的写顺序为EncodeGeometryMetadata()写属性元数据个数varint→ 对每个属性元数据写att_unique_idvarint 其条目与子元数据 → 最后写文件级元数据EncodeMetadata()写条目数varint→ 逐条目写键名UI8 长度 字节 值长度varint 值字节 → 写子元数据个数varint→ 逐子元数据写键名 递归内容。解码端顺序与之完全对称这也是MetadataEncoderTest能做编码→解码→逐字段比对的前提。测试用例覆盖了单条目、多类型条目int/double/string、数组条目、二进制条目、嵌套子元数据、带属性元数据的几何元数据等场景src/draco/metadata/metadata_encoder_test.cc例如TEST_F(MetadataEncoderTest, TestEncodingNestedMetadata) { metadata.AddEntryDouble(double, 1.234); std::unique_ptrdraco::Metadata sub_metadata(new draco::Metadata()); sub_metadata-AddEntryInt(int, 100); metadata.AddSubMetadata(sub0, std::move(sub_metadata)); TestEncodingMetadata(); }TestEncodingMetadata()内部先EncodeMetadata写入EncoderBuffer再用DecoderBuffer::Init()装载同一段字节流调用DecodeMetadata()还原最后递归比对条目字节与子元数据树src/draco/metadata/metadata_encoder_test.cc。七、格式要点速查数据编码类型说明num_att_metadatavarUI32属性元数据个数att_metadata_id[i]varUI32第 i 个属性元数据对应的属性唯一 IDnum_entriesvarUI32当前元数据键值对个数key_size/keyUI8 I8[sz]键名长度 ≤ 255 字节value_size/valuevarint 字节值按二进制 blob 存储num_sub_metadatavarUI32子元数据个数sub_metadata_key_size/ keyUI8 I8[sz]子元数据键名八、局限性与兼容性说明键名长度上限 255 字节是编码器强制约束超出即编码失败解码器按 UI8 读取与之匹配值长度在规范文档中写作 UI8但当前仓库实现使用 varint因此以仓库源码为准元数据嵌套深度上限 1000kMaxSubmetadataLevel超限解码失败只有文件头 flags 携带METADATA_FLAG_MASK时比特流中才包含元数据块无元数据的 .drc 文件不会产生该部分解码结果依赖点云/网格解码器在后续阶段按att_unique_id正确关联属性因此属性元数据 ID 必须与属性唯一 ID 一致。结语Draco 的元数据解码器在格式上只依赖三类原语varint、UI8 长度前缀、原始字节串在结构上是一棵可任意嵌套的键值对树顶层由属性元数据列表 文件级元数据组成。规范文档 docs/spec/metadata.decoder.md 给出了完整的伪代码骨架而仓库实现 src/draco/metadata/metadata_decoder.cc 在保持相同字节序的基础上补充了迭代式解码、深度上限、剩余字节校验等健壮性措施。理解这一对称的编解码结构是安全解析 .drc 比特流、为 Draco 增加自定义元数据能力或开发兼容实现的第一步。赞分享图形学3D渲染【免费下载链接】dracoDraco is a library for compressing and decompressing 3D geometric meshes and point clouds. It is intended to improve the storage and transmission of 3D graphics.项目地址https://gitcode.com/gh_mirrors/draco1/draco点击查看免费下载相关推荐Metadata as Code 规范详解Knowledge CatalogDataplex元数据即代码的工程设计Metadata as Code 规范详解Knowledge CatalogDataplex元数据即代码的工程设计 导读 本文基于 knowledge c数据目录AI Agent人工智能知识管理示例工程Alcatraz包格式详解JSON规范与元数据结构Alcatraz包格式详解JSON规范与元数据结构 引言 作为Xcode的Package Manager包管理器Alcatraz的核心功能在于对各类扩展开发工具RuboCop 0.35.0 升级指南配置继承新玩法与一批新检测规则RuboCop 0.35.0 升级指南配置继承新玩法与一批新检测规则 团队想把统一的 RuboCop 配置打包成 gem 分发但过去 .rubocop.ym代码质量Lint格式化静态分析开发工具上一篇CPDS-analyzer实战教程监控Kubernetes集群容器健康状态的终极指南下一篇揭秘oee_archive为什么它是openEuler Embedded开发者的必备工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表