当前位置: 首页 > 工具软件 > Draco.NET > 使用案例 >

Draco库简析

鄢晔
2023-12-01

Draco库简析

一.简介

​ Draco是谷歌Chrome 团队在2017年1月发布的一个Draco是用于压缩和解压缩3D几何网格和点云的库,旨在改善3D图形的存储和传输,大幅加速 3D 数据的编码、传输和解码。这个开源算法的首要应用对象是浏览器。但可以去探索 Draco 在其他场景的应用,比如说非网页端。目前,谷歌提供了它的两个版本: JavaScript 和 C++。

Draco 可以被用来压缩 mesh 和点云数据。它还支持压缩点( compressing points),连接信息,纹理协调,颜色信息,法线以及其他与几何相关的通用属性

​ Draco 的算法**既支持有损模式,也支持无损。**有损压缩带来的轻微画质改变完全在承受范围之内。用于游戏和 VR 应用完全没有问题,降低的画质很难用肉眼察觉。但在科研点云数据中,任何信息都不能丢失,就应使用无损压缩。

Draco是一种基于量化的压缩方法,是一种开放格式,具有一些关键优势

顺序优化编码:Draco编码器使用基于KD-tree的编码器重新排列点以实现最佳压缩。

通用属性:Draco可以压缩通用属性,例如强度和分类。

可配置的压缩:根据容忍的有损性,点云可以使用1到31个量化位之间的任何位置。压缩率可以调整为毫米或更高的精度。

二.文献收集

1. 文献-用Draco压缩ROS传感器和几何信息-Compressing ROS Sensor and Geometry Messages with Draco

例如,Draco非常依赖Edgebreaker[7]技术来压缩网格数据。有关基本网格压缩技术的详细综述见[8]。

对于点云数据,大多采用基于八叉树的方法,编码复杂。为了减少传输数据量,八叉树的深度被限制为取决于可用带宽的确定值。对于三维几何体,现有的方法将连通性编码和其他度量考虑在内。例如,Draco非常依赖Edgebreaker[7]技术来压缩网格数据。有关基本网格压缩技术的详细综述见[8]。

在[10]中,Doumanoglou等人。在3D几何数据的实时流媒体环境中,对Google Draco[6]、O3dgc[11]、Corto[12]和OpenCTM[13]进行了广泛的评估。他们得出了两个有趣的发现,**启发了本文的工作:Draco在延迟更高的场景中优于其他库;其次,Draco还可以有效地包含额外的顶点属性,如法线或颜色。**在这项工作中,我们利用了这种可扩展性,还将人脸属性(如材质)包含到编码的数据流中,而据我们所知,这些数据流以前从未被评估过。

Draco是Google[6]开发的一种压缩算法,可以压缩网格和点云数据。由于两种数据表示在本质上是不同的,它不依赖于单一的压缩算法,而是使用多种不同的技术在压缩比、解码速度和离散化损失方面对这两种表示进行优化压缩。因此,与一般用途的算法(例如gzip)相比,由于这种专门化,它提供了更高的性能。**对于点云数据,Draco主要依靠使用kd树重新排列点的顺序优化编码。位置数据由可配置数量的量化比特离散化。虽然这自然会导致空间分辨率的损失,但可以根据精度或视觉质量的要求对其进行微调。Draco还支持压缩任意点属性,使其非常适合于异构数据。为了压缩网格拓扑,Draco依赖于Edgebreaker算法[7]。Edgebreaker尝试以螺旋形的形式对网格进行编码,将每个三角形面在字符串中的连接进行编码,同时跟踪已经访问过的顶点和面。然后这些字符串由库单独压缩。虽然Draco允许微调许多内部参数,但压缩主要受用于压缩的量化比特数的影响(最小值为1,最大值为31)。它们可以为每个顶点属性单独设置,并允许根据所需精度以不同方式处理每个属性。另外,可以定义速度设置,其允许用户调整压缩和解压缩时间的比率以及预期的压缩比。在压缩时间上,库根据这些需求选择压缩特性的最佳组合。这种参数化允许在最佳精度以及根据需要快速编码和解码速度之间对压缩进行非常细粒度的调整。**对于我们的用户,我们坚持使用自动压缩设置并评估量化设置,因为这个参数对压缩率和几何质量有着最显著的影响。

[7] J. Rossignac, “Edgebreaker: Connectivity compression for triangle meshes,” IEEE Transactions on Visualization and Computer Graphics, vol. 5, no. 1, pp. 47–61, Jan. 1999

[8] J. Peng, C.-S. Kim, and C.-C. J. Kuo, “Technologies for 3d mesh compression: A survey,” Journal of Visual Communication and Image Representation, vol. 16, no. 6, pp. 688–733, 2005.

[10] A. Doumanoglou, P . Drakoulis, N. Zioulis, D. Zarpalas, and P . Daras,“Benchmarking open-source static 3d mesh codecs for immersivemedia interactive live streaming,” IEEE Journal on Emerging andSelected Topics in Circuits and Systems, vol. 9, no. 1, pp. 190–203,
March 2019.

2. 文献-用于沉浸式媒体交互式实时流媒体的开源静态三维网格编解码器的基准测试-Benchmarking Open-Source Static 3D Mesh Codecs for Immersive Media Interactive Live Streaming

更具体地说,我们考虑了以下编解码器:Google Draco[12]、Corto[13]、MPEG的开放式3D图形压缩(O3dgc)[14]和OpenCTM[15]。在这些开源实现中,Draco基于[16],Corto基于[17],O3dgc基于[18],而OpenCTM是唯一不基于任何学术出版物的库。

Draco使用Edgebreaker[16]作为其底层网格压缩算法。

**Edgebreaker:**通过一系列步骤遍历3D网格。在每一步,算法访问并编码一个尚未访问的三角形网格。在每个阶段,输入网格被划分为可以共享一个顶点但没有边的脱节区域。包围每个区域的边构成一条多边形曲线,称为“环”。循环的边缘被称为“门”,每一步都有一个门处于活动状态。在每一步中,都有一个三角形与活动门相连,但尚未访问。让v表示这个三角形的顶点,它不与门相关联。Edgebreaker编码了v相对于门的环边界和门本身的关系。Edgebreaker突出显示了5个标记为:C、L、E、R、S的情况。当v不属于活动门回路时,此情况标记为C。当v属于主动门回路并且也与主动门相连时,则根据v与(R或L)相关联的活动门侧,将此情况标记为R、L或E。当v入射到有源门的两侧时,该条件被标记为E。最后,v属于环路但不与有源门相关的条件被标记为S。在图2中,描述了所有Edgebreaker情况的示例。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6T0dwBzJ-1596521986792)(E:\markdown笔记\picture\image-20200801215436803.png)]

图3:TFAN编码示例。已经处理了黑色三角形。尚未处理蓝色三角形。当前编码为灰色的TF。将顶点聚焦为绿色。第一次访问的顶点为黄色。顶点已访问为红色。根据顶点的遍历顺序标记为焦点顶点。左:编码向量:S={1,1,1,1},I={}。右:S={0,1,1,0},I={2,1}。

编解码器配置文件在本小节中,我们将简要概述可通过参与此基准测试的每个3D mesh编解码器的可编程接口访问的可用配置选项。对于其中的每一个,相应配置的值隐式地影响其速率失真性能和执行时间。正如前面在第四节开头所介绍的,我们选择并命名特定的“概要文件”,这些配置文件包括每个编解码器的一组特定配置选项。在下面的小节中,将介绍这些概要文件及其选项。

**Draco:Draco对各种浮点顶点属性(位置、法线和自定义属性)进行编码,细节级别由给定的量化位指定。还支持自定义整数属性。**在我们的例子中,pervertex纹理标识符被无损地编码为整数,而纹理混合权重的精度由指定数量的量化比特控制。Draco接口允许通过“速度”设置来调整处理时间与压缩比混合比例,该设置以整数刻度表示,从10(表示最小化处理时间的选项组合)到0(表示导致最可能压缩表示的选项)。我们选择了三种配置进行基准测试,即“Draco2”、“Draco5”和“Draco9”,速度设置分别设置为2、5(默认值)和9(使用底层EdgeBreaker[16]算法的最快设置)。

本文对Corto、Draco、O3dgc和OpenCTM四种开源静态3D-mesh编解码器的比特率、失真度和处理时间进行了广泛、系统和一致的基准测试。与其他工作相比,我们的工作彻底检查了编解码器的性能,不仅压缩几何体和连通性,而且在压缩顶点法线和属性。首先,我们评估了失真率,以及正常的几何失真属性。随后,我们在上述所有方面设定了失真的目标水平,这导致了良好质量(至少在客观方面)的压缩表示,但仍然显著可压缩。对于这些预设的失真程度,我们从比特率和处理时间两个方面评估了编解码器之间的性能。我们得出的结论是,通过相对分析来判断哪个编解码器性能最好并不容易,因为没有一个编解码器在所有比特率、编码和解码时间方面都名列前茅。因此,我们继续对编解码器的性能进行研究,通过在一个远程沉浸式交互式实时流媒体场景中检查它们的理论性能。我们建立了一个TI管道的理论模型,分析计算了端到端延迟的下限和上限,以及在使用这些编解码器时某些常见网络条件下的预期帧速率。我们对TI管道理论模型的拟合值是通过之前进行的大量基准测试得到的。总的来说,我们发现在直播场景中,对于O3dgc的每个配置文件,确实存在一个在所有相关的编解码器方面性能更好的Draco概要文件。此外,除了压缩几何体和连通性的情况外,Corto编解码器在所有方面的性能都比OpenCTM的配置文件好。我们的实时流分析表明,在TI管道中选择Corto和Draco应该是一个基于网络条件的决定,Corto在RTT较低的网络设置上表现最好,而当线路的RTT增加时,Draco的表现更好。本研究的结果可供设计人员选择最佳的三维网状编解码器。此外,上述分析可作为未来3D压缩研究的基准,因为很明显,仍有改进的空间,因为根据我们的理论分析,当所使用的网络参数与实际互联网更接近时,次优帧速率被大多数人实现编解码器。

[16] J. Rossignac, “Edgebreaker: Connectivity compression for triangle meshes,” IEEE Transactions on Visualization and Computer Graphics, vol. 5, no. 1, pp. 47–61, Jan. 1999

3.文献-纹理网格vs彩色点云:V体积视频压缩的主观研究-Textured Mesh vs Coloured Point Cloud A Subjective Study for Volumetric Video Compression

Doumanoglou et al.[22]和Christaki et al.[23]研究了不同的开源网格压缩算法,在比较中发现谷歌的Draco表现最好。

考虑到VV将在不久的将来与传统的视觉媒体一起使用,我们在本文中考虑了压缩场景。考虑到最近的研究结果,我们选择了[22]、[23]、谷歌的Draco编码器来压缩以多边形网格表示的VV。在这种情况下,JPEG压缩被用于纹理图集。对于表示为点云的VVs,我们考虑了MPEG标准化工作[9]:G-PCC和V-PCC中正在开发的最先进的压缩算法。Draco和G-PCC被提出用于压缩静态体积含量,而V-PCC被开发用于压缩VV。由于Draco和G-PCC不考虑时间冗余,因此我们加入了V -PCC的all-intra选项以公平地比较它们。

三.安装和编译

源码地址:https://github.com/google/draco

编译好的程序:https://pan.baidu.com/s/1I4E0-9qgrDCZOOHYeaEZXA

源码编译

在Windows下通过Cmake构建VS2017项目并打开

C*:**\ Users \ nobody> cmake …/ -G “ Visual Studio 15 2017 Win64”*

以release版本和64位进行编译即可在debug目录下得到draco编译好的编码器和解码器在Linux下直接Cmake后在make就能编译

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TUk3dH4p-1596521986794)(E:\markdown笔记\picture\clip_image002.jpg)]

程序使用

draco_encoder将读取OBJ或PLY文件作为输入,并输出Draco编码的文件。

./draco_encoder -i testdata/bun_zipper.ply -o out.drc

量化参数的值0将不会对指定属性执行任何量化。除0以外的任何值都会将指定属性的输入值量化为该位数。例如

./draco_encoder -i testdata/bun_zipper.ply -o out.drc -qp 14

将位置量化为14位(位置坐标的默认值为11)。

一般来说,量化属性越多,压缩率就越高。由你的项目来决定它能容忍多少偏差。一般来说,大多数项目可以将量化值设置为大约11,而不会在质量上有任何明显的差异。

· 压缩级别(-cl)参数打开/关闭不同的压缩功能

./draco_encoder -i testdata/bun_zipper.ply -o out.drc -cl 8

编码点云

通过指定-point_cloud参数,可以使用draco_encoder对点云数据进行编码。如果使用网格输入文件指定-point_cloud参数,则draco_encoder将忽略连接数据并从网格文件编码位置

./draco_encoder -point_cloud -i testdata/bun_zipper.ply -o out.drc

此命令行将网格输入编码为点云,即使输入可能不会产生代表其他点云的压缩

解码点云

draco_decoder将读取draco文件作为输入,并输出OBJ或PLY文件。基本命令行如下所示:

./draco_decoder -i in.drc -o out.obj

压缩测试

(1)40m obj文件压缩成1m,时间在1s之后,在浏览器解码渲染时间也在1s之内,快速,但是没有材质信息,模型质量还未验证。

(2)150m压缩成6m,cl=7,encodeTime=3.6s,decodeTime=5.2s,importTime=1.5s。

压缩效率很高,浏览器解码和渲染时间也能接受,但是这样直接压缩大型文件,在浏览器渲染时会占用大量本地内存,高达1.3G内存都被这个页面占用。渲染质量未验证。

四.框架及源码初步分析

1.编码框架

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y1MGirDe-1596521986796)(E:\markdown笔记\picture\image-20200803131119370.png)]

2.支持的算法、属性等信息

以下为配置代码,可以简要了解Draco库中支持的各种算法和方案

总结:

  • 支持的比特流版本?

    kDracoPointCloudBitstreamVersionMajor = 2;

    kDracoPointCloudBitstreamVersionMinor = 3;

    kDracoMeshBitstreamVersionMajor = 2;

    kDracoMeshBitstreamVersionMinor = 2;

  • 比特流连接支持的版本?

    kDracoPointCloudBitstreamVersion =

    DRACO_BITSTREAM_VERSION (kDracoPointCloudBitstreamVersionMajor,

    ​ kDracoPointCloudBitstreamVersionMinor);

    DracoMeshBitstreamVersion = DRACO_BITSTREAM_VERSION (

    kDracoMeshBitstreamVersionMajor,kDracoMeshBitstreamVersionMinor);

  • 当前支持的文件形式

    点云

    三角网格

  • 点云的编码方法

    POINT_CLOUD_SEQUENTIAL_ENCODING

    POINT_CLOUD_KD_TREE_ENCODING

  • 网格的编码方法

    MESH_SEQUENTIAL_ENCODING

    MESH_EDGEBREAKER_ENCODING

  • 支持的各种属性编码器

    BASIC_ATTRIBUTE_ENCODER

    MESH_TRAVERSAL_ATTRIBUTE_ENCODER

    KD_TREE_ATTRIBUTE_ENCODER

  • 支持的顺序编码器?

    SEQUENTIAL_ATTRIBUTE_ENCODER_GENERIC = 0 通用

    SEQUENTIAL_ATTRIBUTE_ENCODER_INTEGER 整数

    SEQUENTIAL_ATTRIBUTE_ENCODER_QUANTIZATION 量化

    SEQUENTIAL_ATTRIBUTE_ENCODER_NORMALS 法线

  • 支持的预测算法(mesh)?

    PREDICTION_NONE = -2, 不使用

    PREDICTION_UNDEFINED = -1, 未定义

    PREDICTION_DIFFERENCE = 0, 差分

    PREDICTION_PARALLELOGRAM = 1 平行四边形

    PREDICTION_MULTI_PARALLELOGRAM = 2 多平行四边形

    PREDICTION_TEX_COORDS_DEPRECATED = 3, 特克斯弃用坐标?

    PREDICTION_CONSTRAINED_MULTI_PARALLELOGRAM = 4 约束多平行四边形

    PREDICTION_TEX_COORDS_PORTABLE = 5 特克斯坐标便携式

    PREDICTION_GEOMETRIC_NORMAL = 6 几何法线

  • 支持的预测方案转换列表

    PREDICTION_TRANSFORM_NONE = -1,

    //基本delta变换,其中将预测计算为差预测值和原始值。

    PREDICTION_TRANSFORM_DELTA = 0,

    //改进的增量转换,其中所有计算的增量值都被包装围绕固定间隔降低熵。

    PREDICTION_TRANSFORM_WRAP = 1

    //使用倒置图块对法线坐标进行专门的转换。

    PREDICTION_TRANSFORM_NORMAL_OCTAHEDRON = 2

    //使用规范化的倒数对法线坐标进行专门的变换磁贴。

    PREDICTION_TRANSFORM_NORMAL_OCTAHEDRON_CANONICALIZED = 3,

  • 支持的网格遍历算法

    MESH_TRAVERSAL_DEPTH_FIRST = 0, 深度优先

    MESH_TRAVERSAL_PREDICTION_DEGREE = 1 等级?优先

  • 支持用于压缩edgebreaker算法的变体算法列表?

    // 用于网格连通性压缩的边断方法的所有变体列表。

    MESH_EDGEBREAKER_STANDARD_ENCODING = 0, 标准编码

    MESH_EDGEBREAKER_PREDICTIVE_ENCODING = 1, 预测编码

    MESH_EDGEBREAKER_VALENCE_ENCODING = 2 瓦兰斯编码?

//draco_enc_config   compression_share.h
// Currently, we support point cloud and triangular mesh encoding.
// TODO(draco-eng) Convert enum to enum class (safety, not performance).
//支持点云和三角网格编码
enum EncodedGeometryType {
  INVALID_GEOMETRY_TYPE = -1,
  POINT_CLOUD = 0,
  TRIANGULAR_MESH,
};
// List of encoding methods for point clouds.
//对于点云支持顺序编码和八叉树编码
enum PointCloudEncodingMethod {
  POINT_CLOUD_SEQUENTIAL_ENCODING = 0,
  POINT_CLOUD_KD_TREE_ENCODING
};
// List of encoding methods for meshes.
// 对于网格支持顺序编码和EDGEBREAKER编码
enum MeshEncoderMethod {
  MESH_SEQUENTIAL_ENCODING = 0,
  MESH_EDGEBREAKER_ENCODING,
};
// List of various attribute encoders supported by our framework. The entries
// are used as unique identifiers of the encoders and their values should not
// be changed!
//各种属性的列表编码器支持我们的框架。编码器的条目作为惟一标识符和它们的值不应该改变!
enum AttributeEncoderType {
  BASIC_ATTRIBUTE_ENCODER = 0,
  MESH_TRAVERSAL_ATTRIBUTE_ENCODER,
  KD_TREE_ATTRIBUTE_ENCODER,
};
//各种连续属性列表编码器/解码器,可以用于我们的管道。的值代表解码器使用的惟一标识符,他们不应该被改变。
enum SequentialAttributeEncoderType {
  SEQUENTIAL_ATTRIBUTE_ENCODER_GENERIC = 0,
  SEQUENTIAL_ATTRIBUTE_ENCODER_INTEGER,
  SEQUENTIAL_ATTRIBUTE_ENCODER_QUANTIZATION,
  SEQUENTIAL_ATTRIBUTE_ENCODER_NORMALS,
};
// List of all prediction methods currently supported by our framework.
//draco目前支持的预测编码
enum PredictionSchemeMethod {
  // Special value indicating that no prediction scheme was used.
  PREDICTION_NONE = -2,
  // Used when no specific prediction scheme is required.
  PREDICTION_UNDEFINED = -1,
  PREDICTION_DIFFERENCE = 0,
  MESH_PREDICTION_PARALLELOGRAM = 1,
  MESH_PREDICTION_MULTI_PARALLELOGRAM = 2,
  MESH_PREDICTION_TEX_COORDS_DEPRECATED = 3,
  MESH_PREDICTION_CONSTRAINED_MULTI_PARALLELOGRAM = 4,
  MESH_PREDICTION_TEX_COORDS_PORTABLE = 5,
  MESH_PREDICTION_GEOMETRIC_NORMAL = 6,
  NUM_PREDICTION_SCHEMES
};
// List of all prediction scheme transforms used by our framework.
//预测方案转换列表?
enum PredictionSchemeTransformType {
  PREDICTION_TRANSFORM_NONE = -1,
  // Basic delta transform where the prediction is computed as difference the
  // predicted and original value.
  PREDICTION_TRANSFORM_DELTA = 0,
  // An improved delta transform where all computed delta values are wrapped
  // around a fixed interval which lowers the entropy.
  PREDICTION_TRANSFORM_WRAP = 1,
  // Specialized transform for normal coordinates using inverted tiles.
  PREDICTION_TRANSFORM_NORMAL_OCTAHEDRON = 2,
  // Specialized transform for normal coordinates using canonicalized inverted
  // tiles.
  PREDICTION_TRANSFORM_NORMAL_OCTAHEDRON_CANONICALIZED = 3,
};

// List of all mesh traversal methods supported by Draco framework.
//列出Draco框架支持的所有网格遍历方法。
enum MeshTraversalMethod {
  MESH_TRAVERSAL_DEPTH_FIRST = 0,
  MESH_TRAVERSAL_PREDICTION_DEGREE = 1,
  NUM_TRAVERSAL_METHODS
};

// List of all variant of the edgebreaker method that is used for compression
// of mesh connectivity.
//用于网格连通性压缩的边断方法的所有变体列表。
enum MeshEdgebreakerConnectivityEncodingMethod {
  MESH_EDGEBREAKER_STANDARD_ENCODING = 0,
  MESH_EDGEBREAKER_PREDICTIVE_ENCODING = 1,  // Deprecated.
  MESH_EDGEBREAKER_VALENCE_ENCODING = 2,
};

// Draco header V1
struct DracoHeader {
  int8_t draco_string[5];
  uint8_t version_major;
  uint8_t version_minor;
  uint8_t encoder_type;
  uint8_t encoder_method;
  uint16_t flags;
};

enum NormalPredictionMode {
  ONE_TRIANGLE = 0,  // To be deprecated.
  TRIANGLE_AREA = 1,
};

// Different methods used for symbol entropy encoding.
//符号熵编码的不同方法
enum SymbolCodingMethod {
  SYMBOL_CODING_TAGGED = 0,
  SYMBOL_CODING_RAW = 1,
  NUM_SYMBOL_CODING_METHODS,
};

// Mask for setting and getting the bit for metadata in |flags| of header.
#define METADATA_FLAG_MASK 0x8000

}  // namespace draco

#endif  // DRACO_COMPRESSION_CONFIG_COMPRESSION_SHARED_H_

3.Draco实现过程简析(mesh)

参考网址:https://blog.csdn.net/woolseyyy/article/details/63684045?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-2&spm=1001.2101.3001.4242

3.1.encoder过程

encode入口/tools/draco_encoder.cc: int main()

代码前面大多是对用户设定的数值和编码器等进行选择,其核心代码:

if (mesh && mesh->num_faces() > 0)
    ret = EncodeMeshToFile(*mesh, encoder_options, options.output);
  else
    ret = EncodePointCloudToFile(*pc.get(), encoder_options, options.output);

这里先做判断,判断数据中是否有多个面:

  • 如果有面,则进行Mesh to File的过程
  • 若不存在面,则只进行PointCloud to File的过程

PointCloud是n维空间中的点的集合,PointCloud的相关信息在*…/point_cloud/point_cloud.h* 中,内容较多,暂不贴出
而Mesh是PointCloud的继承类,Mesh只是比PointCloud多了Face数据:
…/mesh/mesh.h

3.2.MeshToFile和MeshToBuffer过程

核心代码:

int EncodeMeshToFile(const draco::Mesh &mesh,
                     const draco::EncoderOptions &options,
                     const std::string &file) {
  draco::CycleTimer timer;
  // Encode the geometry.
  draco::EncoderBuffer buffer;
  timer.Start();
  if (!draco::EncodeMeshToBuffer(mesh, options, &buffer)) {
    printf("Failed to encode the mesh.\n");
    return -1;
  }
  timer.Stop();
  // Save the encoded geometry into a file.
  std::ofstream out_file(file, std::ios::binary);
  if (!out_file) {
    printf("Failed to create the output file.\n");
    return -1;
  }
  out_file.write(buffer.data(), buffer.size());
  printf("Encoded mesh saved to %s (%" PRId64 " ms to encode)\n", file.c_str(),
         timer.GetInMs());
  printf("\nEncoded size = %zu bytes\n\n", buffer.size());
  return 0;
}

这里可以分为两步:

  • 调用 draco::EncodeMeshToBuffer 将 Mesh encode 并存入buffer中
  • 将buffer内容调用系统i/o写入file中

函数:draco::EncodeMeshToBuffer

这里draco::EncodeMeshToBuffer的前两个参数是输入,第三个参数是输出,即buffer。这个函数的功能是根据option选择相应的encoder并进行编码,将结果存入buffer中

这个函数前面一部分都在处理option,根据option选择哪种encoder:

函数源码:

bool EncodeMeshToBuffer(const Mesh &m, const EncoderOptions &options,
                        EncoderBuffer *out_buffer) {
  std::unique_ptr<MeshEncoder> encoder;
  // Select the encoding method only based on the provided options.
  int encoding_method = options.GetGlobalInt("encoding_method", -1);
  if (encoding_method == -1) {
    // For now select the edgebreaker for all options expect of speed 10
    if (options.GetSpeed() == 10) {
      encoding_method = MESH_SEQUENTIAL_ENCODING;
    } else {
      encoding_method = MESH_EDGEBREAKER_ENCODING;
    }
  }
  if (encoding_method == MESH_EDGEBREAKER_ENCODING) {
    encoder = std::unique_ptr<MeshEncoder>(new MeshEdgeBreakerEncoder());
  } else {
    encoder = std::unique_ptr<MeshEncoder>(new MeshSequentialEncoder());
  }
  if (encoder)
    encoder->SetMesh(m);
  return EncodeGeometryToBuffer(encoder.get(), options, out_buffer);
}

然后利用encoder->SetMesh(const Mesh &m)来对encoder内部的一些数据进行了初始化,就是把Mesh和CloudPoint信息在Encoder里也存了一下

源码:

…/compression/mesh/mesh_encoder.cc
void MeshEncoder::SetMesh(const Mesh &m) {
  mesh_ = &m;
  SetPointCloud(m);
}
…/compression/point_cloud/point_cloud_encoder.cc
void PointCloudEncoder::SetPointCloud(const PointCloud &pc) {
  point_cloud_ = &pc;
}

(这里因为Mesh是PointCloud的继承,所以直接把Mesh数据传进SetPointCloud函数了)

最后进行了几何信息的编码,这是这个函数的核心代码,之前都没干什么正事,这里进行了真正的编码

3.3.EncodeGeometryToBuffer过程

最后进行了几何信息的编码,这是这个函数的核心代码

return EncodeGeometryToBuffer(encoder.get(), options, out_buffer);
…/compression/encode.cc
bool EncodeGeometryToBuffer(PointCloudEncoder *encoder,
                            const EncoderOptions &options,
                            EncoderBuffer *out_buffer) {
  if (!encoder)
    return false;
  if (!EncodeHeader(*encoder, out_buffer))
    return false;
  if (!encoder->Encode(options, out_buffer))
    return false;
  return true;
}

这里首先用EncoderHeader将encoder的一些基本信息写入buffer,以便今后decode
这些信息包括:”DRACO”字符串标识、major version、minor version、encoder type(point cloud, mesh, …)、selected encoding method (edgebreaker, etc…)、保留标识

然后用encoder->Encode根据option对数据进行编码,写入buffer.

3.4.Encoder编码过程

在这个函数中,具体对encoder data、geometry data、point atrributes进行了编码。

这里InitializeEncoder的功能是可以由派生类实现,以执行编码器的任何自定义初始化。在Encode()方法中调用。

EncodeEncoderData的功能是决定是否应该用于编码任何编码特定的数据

不过这两个都是预留接口,设置了虚函数但没有实现,直接返回true

EncoderGeometryData()会把mesh 的 connectivity进行编码并存入buffer,根据encoder的不同,编码connectivity的方式会分为sequential encoding 和 edgebreaker encoding。

…/compression/point_cloud/point_cloud_encoder.cc
bool PointCloudEncoder::Encode(const EncoderOptions &options,
                               EncoderBuffer *out_buffer) {
  options_ = &options;
  buffer_ = out_buffer;

  // Cleanup from previous runs.
  attributes_encoders_.clear();
  attribute_to_encoder_map_.clear();
  attributes_encoder_ids_order_.clear();

  if (!point_cloud_)
    return false;
  if (!InitializeEncoder())
    return false;
  if (!EncodeEncoderData())
    return false;
  if (!EncodeGeometryData())
    return false;
  if (!EncodePointAttributes())
    return false;
  return true;
}

3.4.EncodePointAttributes

这里point attributes 的意思是指定每个属性的特定数据。通常,存储在点云中的多个点可以共享相同的属性值,该类在点id和属性值id之间提供了必要的映射。

我的理解是,通过这个函数进行属性压缩。

源码及其位置:
…/compression/point_cloud/point_cloud_encoder.cc
bool PointCloudEncoder::EncodePointAttributes() {
  if (!GenerateAttributesEncoders())
    return false;

  // Encode the number of attribute encoders.
  buffer_->Encode(static_cast<uint8_t>(attributes_encoders_.size()));

  // Initialize all the encoders (this is used for example to init attribute
  // dependencies, no data is encoded in this step).
  for (auto &att_enc : attributes_encoders_) {
    if (!att_enc->Initialize(this, point_cloud_))
      return false;
  }

  // Rearrange attributes to respect dependencies between individual attributes.
  if (!RearrangeAttributesEncoders())
    return false;

  // Encode any data that is necessary to create the corresponding attribute
  // decoder.
  for (int att_encoder_id : attributes_encoder_ids_order_) {
    if (!EncodeAttributesEncoderIdentifier(att_encoder_id))
      return false;
  }

  // Also encode any attribute encoder data (such as the info about encoded
  // attributes).
  for (int att_encoder_id : attributes_encoder_ids_order_) {
    if (!attributes_encoders_[att_encoder_id]->EncodeAttributesEncoderData(
            buffer_))
      return false;
  }

  // Lastly encode all the attributes using the provided attribute encoders.
  if (!EncodeAllAttributes())
    return false;
  return true;
}

3.6、7、8…还有好多,实在看不懂,先放着了。

4.Draco库的代码应用案例

这是网上找到的一个通过调用库函数实现自建代码实现的案例,源码如下:

压缩三角网,包括点的位置meshPositions、法线meshnormals、纹理坐标meshUv
示例使用c++完成。
具体步骤
1、初始化变量和数据。
初始化一个四边形,两个三角网
//顶点信息
float meshPositions[12] = {0, 0, 0,
                           100, 0, 0,
                           100, 100, 0,
                           0, 100, 0};
//法线信息
float meshnormals[12] = {0, 1, 0,
                         0, 1, 0,
                         0, 1, 0,
                         0, 1, 0};
//纹理信息
float meshUv[8] = {0, 0,
                   0, 1,
                   1, 1,
                   1, 0};
//索引信息,两个三角形
unsigned short primitiveIndices[6] = {3, 1, 0,
                                      3, 2, 1};
2、将数据初始化到draco中
创建draco对象
// Add faces to Draco mesh.  向Draco网格添加面。
std::unique_ptr<draco::Mesh> dracoMesh(new draco::Mesh());
//写入面索引
const int numTriangles = sizeof(primitiveIndices) / sizeof(primitiveIndices[0]) / 3;//面个数
dracoMesh->SetNumFaces(numTriangles);//设置面个数
for (draco::FaceIndex i(0); i < numTriangles; ++i) {
    draco::Mesh::Face face;
    face[0] = primitiveIndices[i.value() * 3];
    face[1] = primitiveIndices[i.value() * 3 + 1];
    face[2] = primitiveIndices[i.value() * 3 + 2];
    dracoMesh->SetFace(i, face);
}
压缩顶点
//顶点位置
//为Draco网格创建属性。
draco::GeometryAttribute::Type att_type = draco::GeometryAttribute::POSITION;
const int componentCount = 3;//每个顶点有xyz三个值
//顶点
const int vertexCount = sizeof(meshPositions) / sizeof(meshPositions[0]) / componentCount;
draco::PointAttribute att;
int byte_stride = sizeof(float) * componentCount;//每个顶点的步长,xyz坐标和数据大小,3*4  xyz是3每个是浮点型
att.Init(att_type, componentCount, draco::DT_FLOAT32, false, byte_stride);
int att_id = dracoMesh->AddAttribute(att, /* identity_mapping */ true, vertexCount);
draco::PointAttribute *att_ptr = dracoMesh->attribute(att_id);
//要注意压缩网格后属性id不必与唯一ID相同,但unqiue id不会改变。添加顶点
for (draco::PointIndex i(0); i < vertexCount; i++) {
    std::vector<float> vertex_data(componentCount);
    memcpy(&vertex_data[0], &meshPositions[i.value() * componentCount], sizeof(float) * componentCount);
    att_ptr->SetAttributeValue(att_ptr->mapped_index(i), &vertex_data[0]);
}
法线
//法线
const int normalCount = sizeof(meshnormals) / sizeof(meshnormals[0]) / componentCount;
att_type = draco::GeometryAttribute::NORMAL;
draco::PointAttribute attnormal;
attnormal.Init(att_type, componentCount, draco::DT_FLOAT32, false, sizeof(float) * componentCount);
int normalatt_id = dracoMesh->AddAttribute(attnormal, /* identity_mapping */ true, normalCount);
draco::PointAttribute *normalatt_ptr = dracoMesh->attribute(normalatt_id);
//要注意压缩网格后属性id不必与唯一ID相同,但unqiue id不会改变。添加法线
for (draco::PointIndex i(0); i < normalCount; i++) {
    std::vector<float> vertex_data(componentCount);
    memcpy(&vertex_data[0], &meshnormals[i.value() * componentCount], sizeof(float) * componentCount);
    normalatt_ptr->SetAttributeValue(normalatt_ptr->mapped_index(i), &vertex_data[0]);
}
纹理

//纹理
int uvCounts=2;
const int uvCount = sizeof(meshUv) / sizeof(meshUv[0]) / uvCounts;
att_type = draco::GeometryAttribute::TEX_COORD;
draco::PointAttribute attUv;
attnormal.Init(att_type, 2, draco::DT_FLOAT32, false, sizeof(float) * uvCounts);
int uvatt_id = dracoMesh->AddAttribute(attnormal,  true, normalCount);
draco::PointAttribute *uvatt_ptr = dracoMesh->attribute(uvatt_id);
for (draco::PointIndex i(0); i < uvCount; i++) {
    std::vector<float> vertex_data(uvCounts);
    memcpy(&vertex_data[0], &meshUv[i.value() * uvCounts], sizeof(float) * uvCounts);
    uvatt_ptr->SetAttributeValue(uvatt_ptr->mapped_index(i), &vertex_data[0]);
}
3、创建draco压缩对象

//压缩draco
draco::Encoder encoder;
const int posQuantizationBits = 14;
const int texcoordsQuantizationBits = 10;
const int normalsQuantizationBits = 10;
const int colorQuantizationBits = 8;
const int genericQuantizationBits = 8;

//设置压缩参数
encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, posQuantizationBits);
encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, texcoordsQuantizationBits);
encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, normalsQuantizationBits);
encoder.SetAttributeQuantization(draco::GeometryAttribute::COLOR, colorQuantizationBits);
encoder.SetAttributeQuantization(draco::GeometryAttribute::GENERIC, genericQuantizationBits);
draco::EncoderBuffer dracoBuffer;//压缩后存储的对象
const draco::Status status = encoder.EncodeMeshToBuffer(*dracoMesh, &dracoBuffer);//压缩数据
4、获取压缩的数据

if (!status.ok()) {
    std::cerr << "Error: Encode mesh.\n";
} else {
    std::cout << "success" << std::endl;
    std::cout << dracoBuffer.size() << std::endl;//压缩后的数据大小
    std::cout << dracoBuffer.data() << std::endl;//压缩后的数据
}

完整代码如下
int main(int argc, char **argv) {
    std::cout << "Hello, World!" << std::endl;
    //顶点信息
    float meshPositions[12] = {0, 0, 0,
                               100, 0, 0,
                               100, 100, 0,
                               0, 100, 0};
    //法线信息
    float meshnormals[12] = {0, 1, 0,
                             0, 1, 0,
                             0, 1, 0,
                             0, 1, 0};
    //纹理信息
    float meshUv[8] = {0, 0,
                       0, 1,
                       1, 1,
                       1, 0};
    //索引信息,两个三角形
    unsigned short primitiveIndices[6] = {3, 1, 0,
                                          3, 2, 1};
    // Add faces to Draco mesh.  向Draco网格添加面。
    std::unique_ptr<draco::Mesh> dracoMesh(new draco::Mesh());
    //写入面索引
    const int numTriangles = sizeof(primitiveIndices) / sizeof(primitiveIndices[0]) / 3;//面个数
    dracoMesh->SetNumFaces(numTriangles);//设置面个数
    for (draco::FaceIndex i(0); i < numTriangles; ++i) {
        draco::Mesh::Face face;
        face[0] = primitiveIndices[i.value() * 3];
        face[1] = primitiveIndices[i.value() * 3 + 1];
        face[2] = primitiveIndices[i.value() * 3 + 2];
        dracoMesh->SetFace(i, face);
    }

    //顶点位置
    //为Draco网格创建属性。
    draco::GeometryAttribute::Type att_type = draco::GeometryAttribute::POSITION;
    const int componentCount = 3;//每个顶点有xyz三个值
    //顶点
    const int vertexCount = sizeof(meshPositions) / sizeof(meshPositions[0]) / componentCount;
    draco::PointAttribute att;
    int byte_stride = sizeof(float) * componentCount;//每个顶点的步长,xyz坐标和数据大小,3*4  xyz是3每个是浮点型
    att.Init(att_type, componentCount, draco::DT_FLOAT32, false, byte_stride);
    int att_id = dracoMesh->AddAttribute(att, /* identity_mapping */ true, vertexCount);
    draco::PointAttribute *att_ptr = dracoMesh->attribute(att_id);
    //要注意压缩网格后属性id不必与唯一ID相同,但unqiue id不会改变。添加顶点
    for (draco::PointIndex i(0); i < vertexCount; i++) {
        std::vector<float> vertex_data(componentCount);
        memcpy(&vertex_data[0], &meshPositions[i.value() * componentCount], sizeof(float) * componentCount);
        att_ptr->SetAttributeValue(att_ptr->mapped_index(i), &vertex_data[0]);
    }
    //法线
    const int normalCount = sizeof(meshnormals) / sizeof(meshnormals[0]) / componentCount;
    att_type = draco::GeometryAttribute::NORMAL;
    draco::PointAttribute attnormal;
    attnormal.Init(att_type, componentCount, draco::DT_FLOAT32, false, sizeof(float) * componentCount);
    int normalatt_id = dracoMesh->AddAttribute(attnormal, /* identity_mapping */ true, normalCount);
    draco::PointAttribute *normalatt_ptr = dracoMesh->attribute(normalatt_id);
    //要注意压缩网格后属性id不必与唯一ID相同,但unqiue id不会改变。添加法线
    for (draco::PointIndex i(0); i < normalCount; i++) {
        std::vector<float> vertex_data(componentCount);
        memcpy(&vertex_data[0], &meshnormals[i.value() * componentCount], sizeof(float) * componentCount);
        normalatt_ptr->SetAttributeValue(normalatt_ptr->mapped_index(i), &vertex_data[0]);
    }

    //纹理
    int uvCounts=2;
    const int uvCount = sizeof(meshUv) / sizeof(meshUv[0]) / uvCounts;
    att_type = draco::GeometryAttribute::TEX_COORD;
    draco::PointAttribute attUv;
    attnormal.Init(att_type, 2, draco::DT_FLOAT32, false, sizeof(float) * uvCounts);
    int uvatt_id = dracoMesh->AddAttribute(attnormal,  true, normalCount);
    draco::PointAttribute *uvatt_ptr = dracoMesh->attribute(uvatt_id);
    for (draco::PointIndex i(0); i < uvCount; i++) {
        std::vector<float> vertex_data(uvCounts);
        memcpy(&vertex_data[0], &meshUv[i.value() * uvCounts], sizeof(float) * uvCounts);
        uvatt_ptr->SetAttributeValue(uvatt_ptr->mapped_index(i), &vertex_data[0]);
    }

    //压缩draco
    draco::Encoder encoder;
    const int posQuantizationBits = 14;
    const int texcoordsQuantizationBits = 10;
    const int normalsQuantizationBits = 10;
    const int colorQuantizationBits = 8;
    const int genericQuantizationBits = 8;

    //设置压缩参数
    encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, posQuantizationBits);
    encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, texcoordsQuantizationBits);
    encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, normalsQuantizationBits);
    encoder.SetAttributeQuantization(draco::GeometryAttribute::COLOR, colorQuantizationBits);
    encoder.SetAttributeQuantization(draco::GeometryAttribute::GENERIC, genericQuantizationBits);
    draco::EncoderBuffer dracoBuffer;//压缩后存储的对象
    const draco::Status status = encoder.EncodeMeshToBuffer(*dracoMesh, &dracoBuffer);//压缩数据
    if (!status.ok()) {
        std::cerr << "Error: Encode mesh.\n";
    } else {
        std::cout << "success" << std::endl;
        std::cout << dracoBuffer.size() << std::endl;//压缩后的数据大小
        std::cout << dracoBuffer.data() << std::endl;//压缩后的数据
    }
    system("pause");
    return 0;
}

五.总结

它用 edge breaker 算法去压缩面的信息,并产生 CornerTable,用平行四边形差分方式压缩顶点属性信息。

Draco 里面还有其他的压缩顶点属性值的算法,如kd-tree、差分等,详细见各种 PredictionScheme。

1、Draco 在当前的VR、AR生态下,基本上没有直接使用的价值,想用它必须理解原理并修改源码。
2、Draco 里面有比较好的mesh压缩方案,有利于游戏引擎公司基于该代码优化自己的模型设计。
3、Draco 所能压缩的只是3D模型文件中的一部分,而3D相关的资源大头(图片)还是一个老大难的问题

总的来说,Draco整体思路是将网格的连接信息和几何信息进行分别编码并进行存储。
bute::GENERIC, genericQuantizationBits);
draco::EncoderBuffer dracoBuffer;//压缩后存储的对象
const draco::Status status = encoder.EncodeMeshToBuffer(*dracoMesh, &dracoBuffer);//压缩数据
if (!status.ok()) {
std::cerr << “Error: Encode mesh.\n”;
} else {
std::cout << “success” << std::endl;
std::cout << dracoBuffer.size() << std::endl;//压缩后的数据大小
std::cout << dracoBuffer.data() << std::endl;//压缩后的数据
}
system(“pause”);
return 0;
}


## 五.总结

它用 edge breaker 算法去压缩面的信息,并产生 CornerTable,用平行四边形差分方式压缩顶点属性信息。

Draco 里面还有其他的压缩顶点属性值的算法,如kd-tree、差分等,详细见各种 PredictionScheme。

1、Draco 在当前的VR、AR生态下,基本上没有直接使用的价值,想用它必须理解原理并修改源码。
2、Draco 里面有比较好的mesh压缩方案,有利于游戏引擎公司基于该代码优化自己的模型设计。
3、Draco 所能压缩的只是3D模型文件中的一部分,而3D相关的资源大头(图片)还是一个老大难的问题

总的来说,Draco整体思路是将网格的连接信息和几何信息进行分别编码并进行存储。
其中,几何信息使用SequentialAttributeEncodersController/KdTreeAttributesEncoder进行编码。而Sequential内部会对数据进行量化、预测压缩、熵编码。其中熵编码采用了rANS算法。Draco进行了层层封装、科学地使用多态,使得代码层次清晰,可扩展性好,但也带来了一定的阅读障碍。采用的Edgebreaker算法压缩性能方面还有待提升,对float的编码采用动态建立熵编码可能会比较慢,在直播情景下还需要实验验证并改进。
 类似资料: