前言 Draco是谷歌在2017年1月发布的一个3D图形开源压缩库,提供了多种算法进行压缩和解压缩。 对于encoder过程,Draco整体思路是将网格的连接信息和几何信息进行分别编码并进行存储。 其中,连接信息使用了edgebreaker等算法进行了编码压缩,几何信息对数据进行量化、预测压缩、熵编码。其中熵编码采用了rANS算法。 本文对Draco源码进行分析, 另外对其进行封装成UE插件
编译
下载源码, 地址GitHub
CMake编译(可以用GUI默认编译即可),官方文档有详细说明
draco.sln启动VS编译
Debug文件夹内即可以使用的exe
工具文件
使用工具 Draco
支持模型文件后缀为obj
和ply
拿模型文件source.obj
做实例
1 ./draco_encoder.exe -i 输入文件 -o 输出文件
常用参数可以用
1 2 -qp #参数 (位置量化, 默认11) -cl #参数 (压缩级别, 默认7)
反向操作可以把*.drc
作为输入文件, 把*.obj
作为输出文件即可
源码分析 压缩和反压缩的入口文件分别是draco_encoder.cc
和draco_decoder.cc
我们已压缩做示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 if (options.pos_quantization_bits > 30 ) { printf ( "Error: The maximum number of quantization bits for the position " "attribute is 30.\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 > 30 ) { printf ( "Error: The maximum number of quantization bits for the texture " "coordinate attribute is 30.\n" ); return -1 ; } } else if (!strcmp ("-qn" , argv[i]) && i < argc_check) { options.normals_quantization_bits = StringToInt (argv[++i]); if (options.normals_quantization_bits > 30 ) { printf ( "Error: The maximum number of quantization bits for the normal " "attribute is 30.\n" ); return -1 ; } } else if (!strcmp ("-qg" , argv[i]) && i < argc_check) { options.generic_quantization_bits = StringToInt (argv[++i]); if (options.generic_quantization_bits > 30 ) { printf ( "Error: The maximum number of quantization bits for generic " "attributes is 30.\n" ); return -1 ; }
开头部分拿到args
参数以后判断几个可变参数,均是不能大于30
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 std::unique_ptr<draco::PointCloud> pc; draco::Mesh *mesh = nullptr ; if (!options.is_point_cloud) { auto maybe_mesh = draco::ReadMeshFromFile (options.input, options.use_metadata); if (!maybe_mesh.ok ()) { printf ("Failed loading the input mesh: %s.\n" , maybe_mesh.status ().error_msg ()); return -1 ; } mesh = maybe_mesh.value ().get (); pc = std::move (maybe_mesh).value (); } else { auto maybe_pc = draco::ReadPointCloudFromFile (options.input); if (!maybe_pc.ok ()) { printf ("Failed loading the input point cloud: %s.\n" , maybe_pc.status ().error_msg ()); return -1 ; } pc = std::move (maybe_pc).value (); }
上面代码从文件获取draco::mesh
变量, 封装UE插件在此处遇到了一个坑, 传送门
1 2 3 4 5 6 7 8 9 10 11 12 int ret = -1 ; const bool input_is_mesh = mesh && mesh->num_faces () > 0 ; if (input_is_mesh) ret = EncodeMeshToFile (*mesh, options.output, &encoder); else ret = EncodePointCloudToFile (*pc.get (), options.output, &encoder); if (ret != -1 && options.compression_level < 10 ) { printf ( "For better compression, increase the compression level up to '-cl 10' " ".\n\n" ); }
后面就是写入到*.drc
文件中, 实则是先写入到中间文件 draco::EncoderBuffer buffer
中, 然后在通过
draco::WriteBufferToFile
写入文件
封装UE插件 github地址
首先把Draco
源码作为第三方库包含到插件内
![image-20210315164146758](E:\OneDrive\OneDrive - shu.edu.cn\笔记\UE4\图片\Draco\image-20210315164146758.png)
声明一个结构体, 方便设置压缩参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 USTRUCT (BlueprintType)struct FOptions { GENERATED_BODY () FOptions () :is_point_cloud (false ), pos_quantization_bits (11 ), tex_coords_quantization_bits (10 ), tex_coords_deleted (false ), normals_quantization_bits (8 ), normals_deleted (false ), generic_quantization_bits (8 ), generic_deleted (false ), compression_level (7 ), use_metadata (false ) {} public : UPROPERTY (EditAnywhere, BlueprintReadWrite) bool is_point_cloud; UPROPERTY (EditAnywhere, BlueprintReadWrite) int pos_quantization_bits; UPROPERTY (EditAnywhere, BlueprintReadWrite) int tex_coords_quantization_bits; UPROPERTY (EditAnywhere, BlueprintReadWrite) bool tex_coords_deleted; UPROPERTY (EditAnywhere, BlueprintReadWrite) int normals_quantization_bits; UPROPERTY (EditAnywhere, BlueprintReadWrite) bool normals_deleted; UPROPERTY (EditAnywhere, BlueprintReadWrite) int generic_quantization_bits; UPROPERTY (EditAnywhere, BlueprintReadWrite) bool generic_deleted; UPROPERTY (EditAnywhere, BlueprintReadWrite) int compression_level; UPROPERTY (EditAnywhere, BlueprintReadWrite) bool use_metadata; };
两个蓝图库函数
1 2 3 4 UFUNCTION (BlueprintCallable, Category = UnrealDraco) static bool EncoderFromFile (const FString& inFileName, const FString& outFileName, FOptions options) ; UFUNCTION (BlueprintCallable, Category = UnrealDraco) static bool DecoderToFile (const FString& inFileName, const FString& outFileName) ;
逻辑部分基本是参考了源码main
函数内的内容, 但是抄完发现无法从模型文件导入数据, 报错位置在
file_reader_factory.cc
L41
1 2 3 4 5 6 7 8 9 10 11 12 std::unique_ptr<FileReaderInterface> FileReaderFactory::OpenReader ( const std::string &file_name) { for (auto open_function : *GetFileReaderOpenFunctions ()) { auto reader = open_function (file_name); if (reader == nullptr ) { continue ; } return reader; } FILEREADER_LOG_ERROR ("No file reader able to open input" ); return nullptr ; }
因为这个open_function
需要注册, 猜测默认没有注册, 于是就自定义一个类来完成这一步骤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class UD_FileReader : public FileReaderInterface { public : static std::unique_ptr<FileReaderInterface> Open ( const std::string &file_name) ; UD_FileReader () = delete ; UD_FileReader (const UD_FileReader &) = delete ; UD_FileReader &operator =(const UD_FileReader &) = delete ; UD_FileReader (UD_FileReader &&) = default ; UD_FileReader &operator =(UD_FileReader &&) = default ; ~UD_FileReader () override ; bool ReadFileToBuffer (std::vector<char > *buffer) override ; bool ReadFileToBuffer (std::vector<uint8_t > *buffer) override ; size_t GetFileSize () override ; private : UD_FileReader (FILE *file) : file_ (file) {} FILE *file_ = nullptr ; static bool registered_in_factory_; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 std::unique_ptr<FileReaderInterface> UD_FileReader::Open ( const std::string &file_name) { if (file_name.empty ()) { return nullptr ; } FILE *raw_file_ptr = fopen (file_name.c_str (), "rb" ); if (raw_file_ptr == nullptr ) { return nullptr ; } std::unique_ptr<FileReaderInterface> file (new (std::nothrow) UD_FileReader(raw_file_ptr)) ; if (file == nullptr ) { UDWARNING ("Out of memory" ); fclose (raw_file_ptr); return nullptr ; } return file; } bool UD_FileReader::ReadFileToBuffer (std::vector<char > *buffer) { if (buffer == nullptr ) { return false ; } buffer->clear (); const size_t file_size = GetFileSize (); if (file_size == 0 ) { UDWARNING ("Unable to obtain file size or file empty" ); return false ; } buffer->resize (file_size); return fread (buffer->data (), 1 , file_size, file_) == file_size; } bool UD_FileReader::ReadFileToBuffer (std::vector<uint8_t > *buffer) { if (buffer == nullptr ) { return false ; } buffer->clear (); const size_t file_size = GetFileSize (); if (file_size == 0 ) { UDWARNING ("Unable to obtain file size or file empty" ); return false ; } buffer->resize (file_size); return fread (buffer->data (), 1 , file_size, file_) == file_size; }
核心代码就是上面的open()
和ReadFileToBuffer()
然后在模块启动时注册
1 2 3 4 5 6 void FUnrealDracoModule::StartupModule () { draco::FileReaderFactory::RegisterReader (draco::UD_FileReader::Open); draco::FileWriterFactory::RegisterWriter (draco::UD_FileWriter::Open); }
这样就能正确从文件获取Mesh
数据了
测试
max随意创建一个模型, 导出为obj格式文件TT1.obj
,随意放于目录C:\Users\Administrator\Desktop\1\
内
执行如下节点
源文件大小25kb, 生成drc
文件大小为 2kb, 反向解压出来文件TT1_New.obj
放入max测试
4边面变为3边面, 平滑组感觉有些许不同, 其余正常