news 2026/9/7 10:00:15

three.js DRACOExporter:将 Mesh 与点云导出为 Draco 压缩 .drc 文件的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
three.js DRACOExporter:将 Mesh 与点云导出为 Draco 压缩 .drc 文件的完整指南

three.js DRACOExporter:将 Mesh 与点云导出为 Draco 压缩 .drc 文件的完整指南

【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js

本文基于 three.js 仓库中的 DRACOExporter 官方文档 与 源码实现 展开,讲解如何使用该 Addon 把MeshPoints对象导出为 Draco 压缩的.drc二进制数据。读完本文,你将掌握parseAsync()的完整调用方式、全部 7 个导出选项(decodeSpeedencodeSpeedencoderMethodquantizationexportUvsexportNormalsexportColor)的默认值与底层作用,以及从源码层面理解编码器如何组织顶点、面索引与量化的整个流程。

一、Draco 是什么,DRACOExporter 能做什么

DRACOExporter 是 three.js 提供的一个导出器(exporter),用于借助 Google 开源的 Draco 库压缩 3D 网格与点云几何体。压缩后的几何数据可以显著减小体积,代价是客户端需要付出额外的解码时间。

关于独立 Draco 文件,文档中明确了它的“能”与“不能”:

  • 独立.drc文件包含:顶点位置(POSITION)、法线(NORMAL)、颜色(COLOR)以及其他顶点属性;
  • 独立.drc文件不包含:材质(materials)、贴图(textures)、动画(animation)、节点层级(node hierarchies)。

这一点决定了.drc的定位:它只保存“几何数据”,是纯粹的几何压缩容器。如果需要导出带材质、动画的完整场景并使用 Draco 压缩,正确做法是把 Draco 几何体嵌入 glTF 文件中(three.js 的GLTFExporter也支持此流程),而不是依赖DRACOExporter本身。

从源码头部注释看,DRACOExporter.js 第 5~27 行 的类文档与官方文档完全一致,并给出了最小用法示例:

const exporter = new DRACOExporter(); const data = await exporter.parseAsync( mesh, options );

二、前置条件:必须先加载 Draco 编码器

DRACOExporter依赖一个全局脚本DracoEncoderModule(Draco 的 WebAssembly/JS 编码器构建产物)。这是使用该导出器的硬性前置条件,也是很多初学者踩的坑。

官方示例 misc_exporter_draco.html 的做法是在页面中先引入编码器(以 1.5.7 版本为例):

<script src="https://cdn.jsdelivr.net/gh/google/draco@1.5.7/javascript/draco_encoder.js"></script>

编辑器 editor/index.html 同样以相同方式引入了该全局脚本,说明这是仓库内所有使用DRACOExporter的场景的统一做法。

源码中对此有显式校验——第 53~57 行:

if ( typeof DracoEncoderModule === 'undefined' ) { throw new Error( 'THREE.DRACOExporter: required the draco_encoder to work.' ); }

也就是说,如果忘记加载编码器,parseAsync()会直接抛出THREE.DRACOExporter: required the draco_encoder to work.错误,而不是静默失败。另外,源码 第 63~65 行 还处理了新旧编码器构建的兼容问题:

let dracoEncoder = DracoEncoderModule(); // older encoder builds expose the module synchronously, newer builds return a promise if ( dracoEncoder.Encoder === undefined ) dracoEncoder = await dracoEncoder;

旧版编码器同步暴露模块,新版返回 Promise,parseAsync对两者都做了适配,因此无论加载哪个版本构建都能工作。

三、导入方式与构造函数

DRACOExporter属于 three.js 的 Addon,需要显式导入(而非从three主包引入):

import { DRACOExporter } from 'three/addons/exporters/DRACOExporter.js';

构造函数无任何参数:

const exporter = new DRACOExporter();

实例化后即可反复调用parseAsync()导出多个对象,编码器实例是在每次parseAsync()内部创建并销毁的(见后文源码分析),导出器本身是无状态的。

四、核心 API:parseAsync( object, options )

4.1 方法签名

.parseAsync( object : Mesh | Points, options : DRACOExporter~Options ) : Promise.<Int8Array> (async)
参数说明
object要导出的对象,只支持MeshPoints两种类型,其他类型会抛出THREE.DRACOExporter: Unsupported object type.错误
options导出选项,可省略,缺省时使用全部默认值
返回值一个 Promise,resolve 的结果是Int8Array,即完整的.drc二进制内容

注意旧 API 的状态:文档标注.parse()已被弃用,源码中它现在只是一个抛错的占位实现(第 240~244 行):

parse() { throw new Error( 'THREE.DRACOExporter: parse() has been replaced by parseAsync().' ); }

如果你在旧教程中看到exporter.parse( mesh, callback )的写法,迁移到新版本的唯一方式就是换成await exporter.parseAsync( mesh, options )

4.2 Options 参数完整参考

options对象支持以下 7 个字段,缺省值来自 源码第 43~51 行 的Object.assign

参数类型默认值说明
decodeSpeednumber5提示编码器如何针对解码速度调优。取值 0~10,0 表示解码最快但压缩质量(压缩率)最差,10 反之
encodeSpeednumber5提示编码器如何调优编码参数。0 表示编码最快但压缩质量最差,10 反之
encoderMethodnumber1(即MESH_EDGEBREAKER_ENCODING编码方式:0为顺序编码(几乎不压缩),1为 Edgebreaker 编码。Edgebreaker 以确定的螺旋状方式遍历网格三角形,能提供该数据格式的大部分压缩收益
quantizationArray<number>[ 16, 8, 8, 8, 8 ](POSITION, NORMAL, COLOR, TEX_COORD, GENERIC)的顺序,指定 draco 文件中每类数据使用的量化位数(即精度)
exportUvsbooleantrue是否导出 UV(贴图坐标)
exportNormalsbooleantrue是否导出法线
exportColorbooleanfalse是否导出顶点颜色

几个值得注意的调优要点:

  • 速度参数方向:源码 第 169 行 注释写明“从 0(最慢速度,但最佳压缩)到 10(最快,但压缩最差)”,随后调用encoder.SetSpeedOptions( encodeSpeed, decodeSpeed )。所以追求文件体积最小应把两个速度值调小(如 0),追求编码/解码实时性则调大。
  • 量化位数与体积/精度权衡quantization数组对应 5 类属性。默认位置属性 16 位量化,其余 8 位——位置保留较高精度以保证形状保真,法线/颜色/UV 则用 8 位大幅压缩。源码 第 186~198 行 会对数组前 5 个有效元素逐个调用encoder.SetAttributeQuantization( i, bits ),数组中缺失的项(undefined)会被跳过,因此你只传[ 12, 8 ]这样的部分数组也是合法的。
  • Edgebreaker vs 顺序编码encoderMethod通过encoder.SetEncodingMethod()设置(第 178~182 行)。默认值就是 Edgebreaker,对三角面网格压缩收益最大;顺序编码基本不做压缩,一般只在特殊场景使用。

4.3 最小可运行示例

结合官方示例 misc_exporter_draco.html 的核心逻辑,一次完整的导出 + 保存流程如下:

<!-- 前置条件:先加载编码器全局脚本(同官方示例 misc_exporter_draco.html) -->
import * as THREE from 'three'; import { DRACOExporter } from 'three/addons/exporters/DRACOExporter.js'; // 创建场景与被导出的网格 const geometry = new THREE.TorusKnotGeometry( 0.75, 0.2, 200, 30 ); const material = new THREE.MeshPhongMaterial( { color: 0x00ff00 } ); const mesh = new THREE.Mesh( geometry, material ); // 执行导出 const exporter = new DRACOExporter(); async function exportFile() { const result = await exporter.parseAsync( mesh ); // 返回 Int8Array // 保存为文件 const link = document.createElement( 'a' ); link.href = URL.createObjectURL( new Blob( [ result ], { type: 'application/octet-stream' } ) ); link.download = 'file.drc'; link.click(); }

导出结果是Int8Array,官方示例把它包装成Blob触发浏览器下载,得到可直接被 Draco 工具链、DRACOLoader或 PLY/OBJ 转换工具消费的.drc文件。

五、源码级流程解析:parseAsync 内部发生了什么

阅读 parseAsync 完整实现 可以还原出完整的处理链路,这有助于理解各选项到底影响了什么。

5.1 按对象类型分两条流水线

parseAsync(object) ├─ object.isMesh === true │ → MeshBuilder + Mesh │ → AddFloatAttributeToMesh(POSITION) // 顶点位置(必导) │ → AddFacesToMesh( 面索引 ) // 有索引用索引,无索引则自动构造 │ → 可选: NORMAL / TEX_COORD / COLOR 属性 └─ object.isPoints === true → PointCloudBuilder + PointCloud → AddFloatAttribute(POSITION) // 顶点位置(必导) → 可选: COLOR 属性

关键点:

  • 位置属性始终导出,且是唯一不受选项开关控制的属性;法线、UV、颜色分别在exportNormalsexportUvsexportColortrue且几何体确实存在对应 attribute 时才会加入(第 99~135 行)。
  • 无索引几何体的自动兜底:如果Meshgeometry.getIndex()null,源码 第 85~97 行 会现场生成一个顺序索引数组(顶点数超过 65535 时自动选用Uint32Array,否则Uint16Array)再交给AddFacesToMesh。这意味着即使你直接拿new THREE.PlaneGeometry()之类默认就带索引的几何体、或手动去除了索引,导出也不会失败。
  • 非 Mesh / Points 直接抛错(第 159~163 行),例如GroupSprite都不支持。

5.2 编码、量化的落点

// [第 171~198 行] const encodeSpeed = ( options.encodeSpeed !== undefined ) ? options.encodeSpeed : 5; const decodeSpeed = ( options.decodeSpeed !== undefined ) ? options.decodeSpeed : 5; encoder.SetSpeedOptions( encodeSpeed, decodeSpeed ); if ( options.encoderMethod !== undefined ) { encoder.SetEncodingMethod( options.encoderMethod ); } for ( let i = 0; i < 5; i ++ ) { if ( options.quantization[ i ] !== undefined ) { encoder.SetAttributeQuantization( i, options.quantization[ i ] ); } }

三类参数最终都映射到 Draco C++ 编码器经 emscripten 封装出的 API:速度 →SetSpeedOptions,编码方式 →SetEncodingMethod,量化 →SetAttributeQuantization(按属性类型 0~4 逐个设置,数组按(POSITION, NORMAL, COLOR, TEX_COORD, GENERIC)顺序对应这些类型下标)。

5.3 顶点颜色有一个容易被忽略的 sRGB 转换

exportColor: true时,顶点颜色不是直接复制原始 Float32 数据,而是先经过 createVertexColorSRGBArray:

function createVertexColorSRGBArray( attribute ) { // While .drc files do not specify colorspace, the only 'official' tooling // is PLY and OBJ converters, which use sRGB. We'll assume sRGB is expected // for .drc files, but note that Draco buffers embedded in glTF files will // be Linear-sRGB instead. _color.fromBufferAttribute( attribute, i ); ColorManagement.workingToColorSpace( _color, SRGBColorSpace ); ... }

原因在注释里写得很清楚:.drc格式本身不声明色彩空间,而官方生态(PLY/OBJ 转换器)默认按 sRGB 处理,因此 three.js 会主动把 working color space 的颜色转换到 sRGB 再写入;反之,Draco 缓冲区嵌入 glTF 时则使用 Linear-sRGB。如果你在往返测试中发现顶点颜色“偏亮/偏暗”,这里就是差异来源。

5.4 输出拷贝与资源释放

编码完成后(第 200~233 行):

const encodedData = new dracoEncoder.DracoInt8Array(); if ( object.isMesh === true ) { length = encoder.EncodeMeshToDracoBuffer( dracoObject, encodedData ); } else { length = encoder.EncodePointCloudToDracoBuffer( dracoObject, true, encodedData ); }
  • 返回长度为 0 时视为编码失败,抛出THREE.DRACOExporter: Draco encoding failed.
  • 随后把 WASM 侧的DracoInt8Array逐字节拷贝到一个全新的Int8Arraynew ArrayBuffer( length ))中再返回——这样调用方拿到的就是纯 JS 侧内存,与 emscripten 堆解耦;
  • 最后显式destroydracoObjectencodedDataencoderbuilder四个 WASM 对象,防止长时间导出多个模型时的内存泄漏。

5.5 类上的一组常量

除了文档列出的两个编码方式常量,源码还在类上定义了完整的属性类型枚举(第 283~317 行):

常量用途
MESH_EDGEBREAKER_ENCODING1Edgebreaker 编码(默认),对应options.encoderMethod
MESH_SEQUENTIAL_ENCODING0顺序编码,几乎不压缩
POINT_CLOUD0几何类型枚举
TRIANGULAR_MESH1几何类型枚举
POSITION/NORMAL/COLOR/TEX_COORD/GENERIC0~4属性类型,即quantization数组下标顺序
INVALID-1无效属性

其中MESH_EDGEBREAKER_ENCODINGMESH_SEQUENTIAL_ENCODING是文档明确列出的“Properties”;后几组是内部流程使用的类型编号,理解quantization数组时正好可以对照。

六、官方示例:misc_exporter_draco

仓库自带一个可交互演示 misc_exporter_draco.html(截图即文首配图),完整演示了本文的全部要点:

  1. 页面顶部加载draco_encoder.js全局脚本(第 18 行);
  2. 通过 import map 把three/addons/映射到仓库的./jsm/目录(第 20~27 行),因此该示例可以直接在源码仓库中静态托管运行;
  3. TorusKnotGeometry( 0.75, 0.2, 200, 30 )建了一个带光照与阴影的网格场景(第 85~90 行);
  4. 通过 lil-gui 提供 “Export DRC” 按钮,点击后调用await exporter.parseAsync( mesh )并把结果保存为file.drc(第 134~139 行)。

注意该示例没有传options,即完全使用默认参数导出;若需调整压缩率/精度,按第四节表格传参即可。

七、仓库内其他使用场景:three.js 编辑器

DRACOExporter在 three.js 自带的场景编辑器里也是一等公民。editor/js/Menubar.File.js 第 239~267 行 实现了 “File → Export → DRC” 菜单项,其用法值得参考——它展示了按几何体实际内容自适应选项的写法:

const options = { decodeSpeed: 5, encodeSpeed: 5, encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING, quantization: [ 16, 8, 8, 8, 8 ], exportUvs: true, exportNormals: true, exportColor: object.geometry.hasAttribute( 'color' ) }; const result = await exporter.parseAsync( object, options ); saveArrayBuffer( result, 'model.drc' );

可以看到编辑器只在选中对象是 Mesh 时才允许导出(否则弹出 noMeshSelected 提示),并且把exportColor动态设置为“几何体是否真的带 color 属性”——比无脑true更严谨,因为源码中颜色导出在colors === undefined时本来就会静默跳过,但显式控制可以让选项语义与几何体状态一致。编辑器页面 editor/index.html 也已预加载了编码器脚本。

八、使用限制与注意事项

  1. 对象类型受限:仅支持MeshPoints,传Group/Sprite等会抛错;导出的也是单个对象,不支持整场景遍历。
  2. .drc不含场景语义:无材质、贴图、动画、层级。需要完整场景 + Draco 压缩时,应走 glTF 路线(Draco 嵌入 glTF),而不是用本导出器。
  3. 编码器版本:仓库示例锁定 draco 1.5.7 的draco_encoder.js,且该脚本必须作为全局脚本先于parseAsync()加载;源码同时兼容同步/异步两种模块暴露方式。
  4. 往返精度:位置默认 16 位量化、其余 8 位,导出后再加载回 three.js 的几何体会存在轻微数值偏差;若你的应用对位置精度敏感(如 CAD 类),可上调quantization[ 0 ]到 17~20(Draco 上限 24 位)。
  5. 旧代码迁移parse()已弃用且直接抛错,一律改用parseAsync()并配合await
  6. 色彩空间:顶点颜色导出时会转换到 sRGB(见 5.3 节),往返对比颜色时需注意。

九、小结

DRACOExporter的 API 面很小——一个无参构造函数、一个parseAsync()、七个选项,但它把“three.js 内存中的几何体 → Draco 压缩二进制”这条链路封装得非常干净:自动处理索引兜底、WASM 编码器新旧版本兼容、量化参数逐属性下发、sRGB 颜色转换与 WASM 资源释放。默认配置(Edgebreaker + 速度 5 +[16,8,8,8,8]量化)适合大多数网格压缩场景;需要更极致的压缩率时,调低encodeSpeed/decodeSpeed并提高量化位数即可。官方示例 misc_exporter_draco.html 与 editor/js/Menubar.File.js 中的编辑器实现,是两个可以直接对照抄写的实战范本。

【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 9:59:34

Python实战:用AT指令与语音Modem实现自动接听电话机器人

之前刷到一个很火的“给100个野人打电话”整活视频&#xff0c;评论区都在玩梗&#xff0c;我却在想&#xff1a;如果反过来&#xff0c;一天之内真的有一百通陌生来电打进来&#xff0c;一个人手动接听、记录号码、统计时长&#xff0c;不仅累&#xff0c;而且大概率会漏掉关键…

作者头像 李华
网站建设 2026/9/7 9:54:41

壹牛NFT数藏系统全开源:部署实战与二次开发指南

简介&#xff1a;一套面向数字藏品与NFT平台开发者的全开源数藏系统源码&#xff0c;基于H5与APP双端设计&#xff0c;适合快速搭建数字艺术藏品展示、交易及盲盒玩法等场景。该系统为最新迭代版本&#xff0c;新增用户找回密码、短信注册实名认证、后台主图配置等功能&#xf…

作者头像 李华
网站建设 2026/9/7 9:49:40

C#实现国密算法SM2/SM3/SM4实战指南与踩坑总结

简介&#xff1a;面向需要在C#项目中集成国产密码算法的.NET开发者&#xff0c;这份资源实现了SM2非对称加密、SM3密码杂凑、SM4分组密码这三套国密算法&#xff0c;并提供完整的Winform界面示例&#xff0c;可直接用于政务系统、金融接口、企业内部数据加密等合规场景&#xf…

作者头像 李华
网站建设 2026/9/7 9:48:23

GENESIS2000菜单全解析:从入门到脚本自动化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 9:48:03

IEC61850与变电站程序化操作:原理、流程与工程落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华