Draco encode过程代码分析 - 探究其数据编码方式

Introduction

Draco是谷歌在2017年1月发布的一个3D图形开源压缩库,提供了多种算法进行压缩和解压缩。
对于encoder过程,Draco整体思路是将网格的连接信息和几何信息进行分别编码并进行存储。
其中,连接信息使用了edgebreaker等算法进行了编码压缩,几何信息对数据进行量化、预测压缩、熵编码。其中熵编码采用了rANS算法。
不过和原先预想的不同,Draco并未建立一张静态查询表,而是利用算法动态地生成。
本文从encoder入口开始,一层一层分析其代码,试图弄清其数据编码方式。
github源码: https://github.com/google/draco

encoder 过程

encode 过程入口

…/tools/draco_encoder.cc: int main()

int main(int argc, char **argv) {Options options;const int argc_check = argc - 1;for (int i = 1; i < argc; ++i) {if (!strcmp("-h", argv[i]) || !strcmp("-?", argv[i])) {Usage();return 0;} else if (!strcmp("-i", argv[i]) && i < argc_check) {options.input = argv[++i];} else if (!strcmp("-o", argv[i]) && i < argc_check) {options.output = argv[++i];} else if (!strcmp("-point_cloud", argv[i])) {options.is_point_cloud = true;} else if (!strcmp("-qp", argv[i]) && i < argc_check) {options.pos_quantization_bits = StringToInt(argv[++i]);if (options.pos_quantization_bits > 31) {printf("Error: The maximum number of quantization bits for the position ""attribute is 31.\n");return -1;}} else if (!strcmp("-qt", argv[i]) && i < argc_check) {options.tex_coords_quantization_bits = StringToInt(argv[++i]);if (options.tex_coords_quantization_bits > 31) {printf("Error: The maximum number of quantization bits for the texture ""coordinate attribute is 31.\n");return -1;}} else if (!strcmp("-qn", argv[i]) && i < argc_check) {options.normals_quantization_bits = StringToInt(argv[++i]);if (options.normals_quantization_bits > 31) {printf("Error: The maximum number of quantization bits for the normal ""attribute is 31.\n");return -1;}} else if (!strcmp("-cl", argv[i]) && i < argc_check) {options.compression_level = StringToInt(argv[++i]);}}if (argc < 3 || options.input.empty()) {Usage();return -1;}std::unique_ptr<draco::PointCloud> pc;draco::Mesh *mesh = nullptr;if (!options.is_point_cloud) {std::unique_ptr<draco::Mesh> in_mesh =draco::ReadMeshFromFile(options.input);if (!in_mesh) {printf("Failed loading the input mesh.\n");return -1;}mesh = in_mesh.get();pc = std::move(in_mesh);} else {pc = draco::ReadPointCloudFromFile(options.input);if (!pc) {printf("Failed loading the input point cloud.\n");return -1;}}// Setup encoder options.draco::EncoderOptions encoder_options = draco::CreateDefaultEncoderOptions();if (options.pos_quantization_bits > 0) {draco::SetNamedAttributeQuantization(&encoder_options, *pc.get(),draco::GeometryAttribute::POSITION,options.pos_quantization_bits);}if (options.tex_coords_quantization_bits > 0) {draco::SetNamedAttributeQuantization(&encoder_options, *pc.get(),draco::GeometryAttribute::TEX_COORD,options.tex_coords_quantization_bits);}if (options.normals_quantization_bits > 0) {draco::SetNamedAttributeQuantization(&encoder_options, *pc.get(),draco::GeometryAttribute::NORMAL,options.normals_quantization_bits);}// Convert compression level to speed (that 0 = slowest, 10 = fastest).const int speed = 10 - options.compression_level;draco::SetSpeedOptions(&encoder_options, speed, speed);if (options.output.empty()) {// Create a default output file by attaching .drc to the input file name.options.output = options.input + ".drc";}PrintOptions(*pc.get(), options);int ret = -1;if (mesh && mesh->num_faces() > 0)ret = EncodeMeshToFile(*mesh, encoder_options, options.output);elseret = EncodePointCloudToFile(*pc.get(), encoder_options, options.output);if (ret != -1 && options.compression_level < 10) {printf("For better compression, increase the compression level '-cl' (up to ""10).\n\n");}return ret;
}

这一部分前大半都是在处理命令,核心代码就是最后两行:

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

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

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

其中,两个函数都会一个bool变量,来表示是否成功
>
PointCloud是n维空间中的点的集合,PointCloud的相关信息在 …/point_cloud/point_cloud.h 中,内容较多,暂不贴出
而Mesh是PointCloud的继承类,Mesh只是比PointCloud多了Face数据:
…/mesh/mesh.h

class Mesh : public PointCloud {public:typedef std::array<PointIndex, 3> Face;Mesh();void AddFace(const Face &face) { faces_.push_back(face); }void SetFace(FaceIndex face_id, const Face &face) {if (face_id >= faces_.size()) {faces_.resize(face_id.value() + 1, Face());}faces_[face_id] = face;}

MeshToFile

接下来分析MeshToFile过程

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的前两个参数是输入,第三个参数是输出,即buffer

draco::EncodeMeshToBuffer

这个函数的功能是根据option选择相应的encoder并进行编码,将结果存入buffer中
先来看下整个函数:
…/compression/encode.cc

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 10if (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);
}

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

  • Sequential encoding
  • Edgebreaker encoding
  if (encoding_method == MESH_EDGEBREAKER_ENCODING) {encoder = std::unique_ptr<MeshEncoder>(new MeshEdgeBreakerEncoder());} else {encoder = std::unique_ptr<MeshEncoder>(new MeshSequentialEncoder());}

然后利用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函数了)

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

return EncodeGeometryToBuffer(encoder.get(), options, out_buffer);

这个函数从encoder中拿到encoder的指针,进行相应操作,然后将结果写入buffer中

EncodeGeometryToBuffer

…/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

encoder->Encode

在这个函数中,具体对encoder data、geometry data、point atrributes进行了编码
…/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;
}

这里InitializeEncoder的功能是
>
Can be implemented by derived classes to perform any custom initialization of the encoder. Called in the Encode() method.

EncodeEncoderData的功能是
>
Should be used to encode any encoder-specific data.

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

EncoderGeometryData()会把mesh 的 connectivity进行编码并存入buffer,根据encoder的不同,编码connectivity的方式会分为sequential encoding 和 edgebreaker encoding。
这里暂时先暂停对connectivity编码过程的进一步探究,先研究顶点坐标等信息是如何压缩编码的

EncodePointAttributes

这里point attributes 的意思是
>
point specific data about each attribute. In general, multiple points stored in a point cloud can share the same attribute value and this class provides the necessary mapping between point ids and attribute value ids.

是顶点坐标一类的信息

…/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;
}

Initialize() 初始化值传递 将值传入AttributesEncoder中

RearrangeAttributesEncoders()
(有待仔细求证)
这里主要考虑了不止有顶点信息这一种attribute,可能还有color等attribute,而这里的作用就是把这些attribute的encoder排好序。
暂不继续探究,继续探究float数据是如何储存的

EncodeAttributesEncoderIdentifier是一个预留接口,无实质内容,直接返回true

然后用每一种attribute encoder,对attribute的一些基本信息进行编码和记录。
基本信息包括:attribute number、attribute type、data type、components count等

最后用EncodeAllAttributes对数据进行编码

bool PointCloudEncoder::EncodeAllAttributes() {for (int att_encoder_id : attributes_encoder_ids_order_) {if (!attributes_encoders_[att_encoder_id]->EncodeAttributes(buffer_))return false;}return true;
}

EncodeAttributes()是多态的,框架目前实现了SequentialAttributeEncodersController和KdTreeAttributesEncoder两种encoder,我们选前者进行讨论

SequentialAttributeEncodersController

…/compression/attributes/sequential_attribute_encoders_controller.cc

bool SequentialAttributeEncodersController::EncodeAttributes(EncoderBuffer *buffer) {if (!sequencer_ || !sequencer_->GenerateSequence(&point_ids_))return false;for (uint32_t i = 0; i < sequential_encoders_.size(); ++i) {if (!sequential_encoders_[i]->Encode(point_ids_, buffer))return false;}return true;
}

(这里不明白哪来那么多sequential_encoders…还有哪些sequential_encoders)

继续深一层 SequentialAttributeEncoder::Encode

SequentialAttributeEncodersController

…/compression/attributes/sequential_attribute_encoders_encoder.cc

bool SequentialAttributeEncoder::Encode(const std::vector<PointIndex> &point_ids, EncoderBuffer *out_buffer) {if (!EncodeValues(point_ids, out_buffer))return false;if (is_parent_encoder_ && IsLossyEncoder()) {if (!PrepareLossyAttributeData())return false;}return true;
}

这里又是一层封装 而后面的is_parent_encoder是与预测有关,IsLossyEncoder是拓展接口,未实现,直接返回false

深入EncodeValues

EncodeValues

…/compression/attributes/sequential_attribute_encoder.cc

bool SequentialAttributeEncoder::EncodeValues(const std::vector<PointIndex> &point_ids, EncoderBuffer *out_buffer) {const int entry_size = attribute_->byte_stride();const std::unique_ptr<uint8_t[]> value_data_ptr(new uint8_t[entry_size]);uint8_t *const value_data = value_data_ptr.get();// Encode all attribute values in their native raw format.for (uint32_t i = 0; i < point_ids.size(); ++i) {const AttributeValueIndex entry_id = attribute_->mapped_index(point_ids[i]);attribute_->GetValue(entry_id, value_data);out_buffer->Encode(value_data, entry_size);}return true;
}

该函数根据index将atrribute的值取出,然后存入buffer
>
这里out_buffer->Encode是直接将内容插入buffer末尾,并未进行压缩或编码,可能之后谷歌出2.0还会对这个地方做一些文章吧
…/core/encoder_buffer.h

bool Encode(const void *data, size_t data_size) {if (bit_encoder_active())return false;const uint8_t *src_data = reinterpret_cast<const uint8_t *>(data);buffer_.insert(buffer_.end(), src_data, src_data + data_size);return true;}

这个地方看起来很纳闷,怎么会没有进行任何压缩呢?
但SequentialAttributeEncoder这个类是这样描述的:
>
A base class for encoding attribute values of a single attribute using a given sequence of point ids. The default implementation encodes all attribute values directly to the buffer but derived classes can perform any custom encoding (such as quantization) by overriding the EncodeValues() method.

查看SequentialAttributeEncoder类就可以发现,它有一些继承
那么到底在哪里决定具体的继承呢?
通过搜索和查找,我们发现在 这个地方 决定了具体是哪个继承类
刚才在PointCloudEncoder::EncodePointAttributes的分析中,没有对Initialize进行仔细分析
…/compression/point_cloud/point_cloud_encoder.cc

  // 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;}

追踪入Initialize
…/compression/attribute/attribute_encoder.cc

bool AttributesEncoder::Initialize(PointCloudEncoder *encoder,const PointCloud *pc) {point_cloud_encoder_ = encoder;point_cloud_ = pc;return true;
}

原先以为这就是初始化,仅仅做了一些初始化值传递,但事实上这是一个虚函数
…/compression/attributes/sequential/attribute_encoders_controller.cc

bool SequentialAttributeEncodersController::Initialize(PointCloudEncoder *encoder, const PointCloud *pc) {if (!AttributesEncoder::Initialize(encoder, pc))return false;if (!CreateSequentialEncoders())return false;// Initialize all value encoders.for (int i = 0; i < num_attributes(); ++i) {const int32_t att_id = GetAttributeId(i);if (!sequential_encoders_[i]->Initialize(encoder, att_id))return false;}return true;
}

而其中的CreateSequentialEncoders()干了这样一件事:
它用另一个函数CreateSequentialEncoder为每一个attribute建立了一个encoder
…/compression/attributes/sequential/attribute_encoders_controller.cc

equentialAttributeEncodersController::CreateSequentialEncoder(int i) {const int32_t att_id = GetAttributeId(i);const PointAttribute *const att = encoder()->point_cloud()->attribute(att_id);switch (att->data_type()) {case DT_UINT8:case DT_INT8:case DT_UINT16:case DT_INT16:case DT_UINT32:case DT_INT32:return std::unique_ptr<SequentialAttributeEncoder>(new SequentialIntegerAttributeEncoder());case DT_FLOAT32:if (encoder()->options()->GetAttributeInt(att_id, "quantization_bits",-1) > 0) {if (att->attribute_type() == GeometryAttribute::NORMAL) {// We currently only support normals with float coordinates// and must be quantized.return std::unique_ptr<SequentialAttributeEncoder>(new SequentialNormalAttributeEncoder());} else {return std::unique_ptr<SequentialAttributeEncoder>(new SequentialQuantizationAttributeEncoder());}}break;default:break;}// Return the default attribute encoder.return std::unique_ptr<SequentialAttributeEncoder>(new SequentialAttributeEncoder());
}

而CreateSequentialEncoder则根据data type建立对应的encoder,目前框架内支持的只有三种:SequentialIntegerAttributeEncoder, SequentialNormalAttributeEncoder, SequentialQuantizationAttributeEncoder

我们可以继续分析EncodeValues了,因为SequentialNormalAttributeEncoder, SequentialQuantizationAttributeEncoder是SequentialIntegerAttributeEncoder的继承类,我们来分析SequentialIntegerAttributeEncoder

SequentialIntegerAttributeEncoder

…/compression/attributes/sequential_integer_attribute_encoder.cc

bool SequentialIntegerAttributeEncoder::EncodeValues(const std::vector<PointIndex> &point_ids, EncoderBuffer *out_buffer) {// Initialize general quantization data.const PointAttribute *const attrib = attribute();if (attrib->size() == 0)return true;int8_t prediction_scheme_method = PREDICTION_NONE;if (prediction_scheme_)prediction_scheme_method =static_cast<int8_t>(prediction_scheme_->GetPredictionMethod());out_buffer->Encode(prediction_scheme_method);if (prediction_scheme_) {out_buffer->Encode(static_cast<int8_t>(prediction_scheme_->GetTransformType()));}if (!PrepareValues(point_ids))return false;const int num_components = values_.size() / point_ids.size();// All integer values are initialized. Process them using the prediction// scheme if we have one.if (prediction_scheme_) {prediction_scheme_->Encode(values_.data(), &values_[0], values_.size(),num_components, point_ids.data());}if (prediction_scheme_ == nullptr ||!prediction_scheme_->AreCorrectionsPositive()) {ConvertSignedIntsToSymbols(values_.data(), values_.size(),reinterpret_cast<uint32_t *>(values_.data()));}if (encoder() == nullptr || encoder()->options()->GetGlobalBool("use_built_in_attribute_compression", true)) {out_buffer->Encode(static_cast<uint8_t>(1));if (!EncodeSymbols(reinterpret_cast<uint32_t *>(values_.data()),point_ids.size() * num_components, num_components,out_buffer)) {return false;}} else {// No compression. Just store the raw integer values, using the number of// bytes as needed.// To compute the maximum bit-length, first OR all values.uint32_t masked_value = 0;for (uint32_t i = 0; i < values_.size(); ++i) {masked_value |= values_[i];}// Compute the msb of the ORed value.int value_msb_pos = 0;if (masked_value != 0) {value_msb_pos = bits::MostSignificantBit(masked_value);}const int num_bytes = 1 + value_msb_pos / 8;out_buffer->Encode(static_cast<uint8_t>(0));out_buffer->Encode(static_cast<uint8_t>(num_bytes));if (num_bytes == sizeof(decltype(values_)::value_type)) {out_buffer->Encode(values_.data(), sizeof(int32_t) * values_.size());} else {for (uint32_t i = 0; i < values_.size(); ++i) {out_buffer->Encode(&values_[i], num_bytes);}}}if (prediction_scheme_) {prediction_scheme_->EncodePredictionData(out_buffer);}return true;
}

这里主要做了三件事

  • 预处理数据
  • 利用预测压缩数据
  • 将int编码为symbol 并利用rANS熵编码算法进行压缩编码

预处理 - PrepareValues
进行一些格式转换等处理,此外,在SequentialQuantizationAttributeEncoder和SequentialNormalAttributeEncoder中,会进行量化

利用预测压缩数据

 prediction_scheme_->Encode(values_.data(), &values_[0], values_.size(),num_components, point_ids.data());

目前框架内包含了parallelogram, multi parallelogram, tex coords三种预测方法

进行压缩编码

EncodeSymbols(reinterpret_cast<uint32_t *>(values_.data()),point_ids.size() * num_components, num_components,out_buffer)

EncodeSymbols

EncodeSymbols内部是先对待处理数据进行了测算,根据测算结果决定利用哪种熵编码技术

…/core/symbol_encoding.cc

bool EncodeSymbols(const uint32_t *symbols, int num_values, int num_components,EncoderBuffer *target_buffer) {if (num_values < 0)return false;if (num_values == 0)return true;if (num_components <= 0)num_components = 1;std::vector<int> bit_lengths;uint32_t max_value;ComputeBitLengths(symbols, num_values, num_components, &bit_lengths,&max_value);// Compute the total bit length used by all values. This will be used for// computing a heuristic that chooses the optimal entropy encoding scheme.uint64_t total_bit_length = 0;for (size_t i = 0; i < bit_lengths.size(); ++i) {total_bit_length += bit_lengths[i];}const int64_t num_component_values = num_values / num_components;// The average number of bits necessary for encoding a single entry value.const int64_t average_bit_length =static_cast<int64_t>(ceil(static_cast<double>(total_bit_length) /static_cast<double>(num_component_values)));// The estimated average number of bits necessary for encoding a single// bit-length tag.int64_t average_bits_per_tag = static_cast<int64_t>(ceil(static_cast<float>(bits::MostSignificantBit(average_bit_length)) /static_cast<float>(num_components)));if (average_bits_per_tag <= 0)average_bits_per_tag = 1;// Estimate the number of bits needed for encoding the values using the tagged// scheme. 32 * 8 is the overhead for encoding the entropy encoding table.const int64_t tagged_scheme_total_bits =num_component_values *(num_components * average_bit_length + average_bits_per_tag) +32 * 8;// Estimate the number of bits needed by the "raw" scheme. In this case,// max_value * 8 is the overhead of the entropy table.const int64_t raw_scheme_total_bits =num_values * average_bit_length + max_value * 8;// The maximum bit length of a single entry value that we can encode using// the raw scheme.const int max_value_bit_length = bits::MostSignificantBit(max_value) + 1;if (tagged_scheme_total_bits < raw_scheme_total_bits ||max_value_bit_length > kMaxRawEncodingBitLength) {// Use the tagged scheme.target_buffer->Encode(static_cast<uint8_t>(0));return EncodeTaggedSymbols<RAnsSymbolEncoder>(symbols, num_values, num_components, bit_lengths, target_buffer);}// Else use the raw scheme.target_buffer->Encode(static_cast<uint8_t>(1));return EncodeRawSymbols<RAnsSymbolEncoder>(symbols, num_values, &max_value,target_buffer);
}

前面大部分都在算各种特征,来决定用哪种scheme

  // The maximum bit length of a single entry value that we can encode using// the raw scheme.const int max_value_bit_length = bits::MostSignificantBit(max_value) + 1;if (tagged_scheme_total_bits < raw_scheme_total_bits ||max_value_bit_length > kMaxRawEncodingBitLength) {// Use the tagged scheme.target_buffer->Encode(static_cast<uint8_t>(0));return EncodeTaggedSymbols<RAnsSymbolEncoder>(symbols, num_values, num_components, bit_lengths, target_buffer);}// Else use the raw scheme.target_buffer->Encode(static_cast<uint8_t>(1));return EncodeRawSymbols<RAnsSymbolEncoder>(symbols, num_values, &max_value,target_buffer);

有TaggedSymbols和RawSymbols两种,但是都用了RAnsSymbolEncoder
其中,rANS(Asymmetric Numeral Systems)是一种熵编码,框架参考了http://arxiv.org/abs/1311.2540v2实现

小结

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

@Woolsey

2017/3/19

Draco代码分析(一) —— Encode过程探究其数据编码方式相关推荐

  1. DispatcherServlet代码分析及运行过程

    DispatcherServlet代码分析及运行过程 1    首先该类有一静态语块,用以加载缺省策略. static { ClassPathResource resource =new ClassP ...

  2. host速度 mtk usb_mtk-usb代码分析之枚举过程

    基于mt6750T,Android 7.0,kernel 3.18.35,本文主要简述了USB的枚举过程,主要是从host的角度来看. 一.USB的拓扑结构 简单来说,USB由host和device两 ...

  3. DRM驱动代码分析:开机过程中显示驱动做了什么

    前言: 有些信息是在网上查资料后总结的,没有去追代码验证.如果有说得不对的地方,欢迎提出指正.感谢! 手机启动的大致流程 1.长按开机键 2.执行存储在ROM里(应该是某一个固定地址或是预定义的地址) ...

  4. 【SemiDrive源码分析】【X9芯片启动流程】23 - MailBox 核间通信机制介绍(代码分析篇)之 RPMSG-IPCC Kernel 篇

    [SemiDrive源码分析][X9芯片启动流程]23 - MailBox 核间通信机制介绍(代码分析篇)之 RPMSG-IPCC Kernel 篇 一.RPMSG 接口 1.1 Linux Kern ...

  5. 【SemiDrive源码分析】【X9芯片启动流程】25 - MailBox 核间通信机制介绍(代码分析篇)之 RPMSG-IPCC RTOS QNX篇

    [SemiDrive源码分析][X9芯片启动流程]25 - MailBox 核间通信机制介绍(代码分析篇)之 RPMSG-IPCC RTOS & QNX篇 一.RPMSG 接口 1.1 Lin ...

  6. 模块加载过程代码分析1

    一.概述 模块是作为ELF对象文件存放在文件系统中的,并通过执行insmod程序链接到内核中.对于每个模块,系统都要分配一个包含以下数据结构的内存区. 一个module对象,表示模块名的一个以null ...

  7. H264编码- 码率控制 RQ 模型参数推导过程以及JM代码分析

    摘要:本文主要介绍H264码率控制过程中,RQ模型参数更新推导过程,并结合JM19.0代码分析其功能实现. H264码率控制中比较重要的一个模型是RQ模型,不管是图像级码率控制还是基本单元码率控制都会 ...

  8. TCP三次握手代码分析与过程跟踪

    https://www.cnblogs.com/luoyang712/p/12099983.html 1.内核TCP三次握手代码分析 首先客户端调用connect主动发起连接 SYSCALL_DEFI ...

  9. 【Android4.4蓝牙代码分析】- 蓝牙Enable过程

    [Android4.4蓝牙代码分析]- 蓝牙Enable过程 (一)基本说明 本文的代码选用的是Android4.4的kitkat代码. 本文是在Windows环境下通过source insight进 ...

  10. WFD连接过程代码分析(Sink端)

    WFD连接过程代码分析(Sink端) WFD建立连接首先必需建立P2P连接,随后WFD使用P2P连接的IP和端口号建立RTSP连接.本文着重分析P2P连接建立后的RTSP连接建立过程,且为一个Sour ...

最新文章

  1. 【问题解决方案】ImportError: No module named ‘openpyxl‘/‘xlrd’
  2. ANSI,ASCII,UNICODE
  3. 126. 最大的和【思维 前缀和】
  4. ICCV 2019 | 基于无标签单目视频序列的单帧三维人体姿态估计算法
  5. 程序猿,该在一个公司呆多久?
  6. 什么是执行落地岗位_激发产品业务价值,用数据驱动理念落地
  7. python中int对象不可迭代_python - 情感分析接收错误:'int'对象不可迭代_python-3.x_酷徒编程知识库...
  8. Hie with the Pie(poj3311)
  9. 逆向某某单车-iOS客户端
  10. python中的os.path.realpath(__file__)
  11. 对于C语言可移植性的思考
  12. Java深圳工作面试经历(真实经历)!!!
  13. 已知ip地址和其子网掩码如何求网络号子网号主机号
  14. new date的含义
  15. wps英文文档排版小技巧
  16. 光阴似箭,日月如梭(时间都去哪了?)
  17. Navicat fro oracle 简体中文破解版(亲测可用)
  18. SOA(面向服务的架构) 架构与微服务架构的区别
  19. 局域网限速软件_教你如何自建不限速私有云盘
  20. 【数据结构】哈希底层结构

热门文章

  1. e4a 安卓获取ROOT权限的方法思路 转载
  2. python怎么保留整数输出_python怎么保留整数
  3. php退款,PHP实现微信申请退款流程的方法
  4. MySQL~锁的大集合(S锁、X锁、意向锁、自增锁、元数据锁、记录锁、间隙锁、临键锁、全局锁、死锁)
  5. 【MySQL】 锁机制:InnoDB引擎中锁分类以及表锁、行锁、页锁详解
  6. Pair Project: 电梯调度问题API设计(by 谢俊源 陆元伟)
  7. 内网渗透系列:隐匿攻击方法
  8. bzoj1779 [Usaco2010 Hol]Cowwar 奶牛战争(网络流)
  9. CAD制图技巧,如何旋转图形?
  10. hgame 2022 逆向 reverse 部分题目 Writeup