首页
/ three.js EXRLoader 实战指南:加载 OpenEXR HDR 纹理的压缩格式支持、类型控制与数据管线解析

three.js EXRLoader 实战指南:加载 OpenEXR HDR 纹理的压缩格式支持、类型控制与数据管线解析

2026-09-06 18:29:05作者:丁柯新Fawn

OpenEXR(.exr)是影视级 HDR 图像事实标准格式,常用于环境贴图与光照探针。本文以 three.js 仓库中的 EXRLoader 为线索,完整讲解其继承关系、API、可配置类型/格式参数及底层解码流程,并通过仓库内真实示例(webgl_loader_texture_exrwebgl_materials_envmaps_exr)给出可直接运行的接入方案。读完本文,你将掌握如何在 three.js 中正确加载各种压缩方式的 EXR 纹理、控制输出为半浮点/浮点数据并用于 HDR 环境映射。

EXRLoader 是什么

EXRLoader 是 three.js 中负责加载 OpenEXR 纹理格式的加载器,完整源码位于 examples/jsm/loaders/EXRLoader.js。它是加载二进制纹理格式的抽象基类 DataTextureLoader 的派生类,官方文档标注的继承链为:

Loader → DataTextureLoader → EXRLoader

src/loaders/DataTextureLoader.js 中可看到,DataTextureLoader 本身就是为 RGBE、EXR、TGA 这类二进制纹理格式设计的:它通过 FileLoaderarraybuffer 响应类型拉取文件,并约定派生类必须实现 parse() 方法(文件头注释 Derived classes have to implement the parse() method),随后把解析结果写入一个 DataTexture 并设置 needsUpdate = true

压缩格式支持范围

EXRLoader.js 的类注释与官方 API 文档,EXRLoader 目前支持以下压缩方式的解码:

  • 无压缩(uncompressed)
  • ZIP / ZIPS(DEFLATE 压缩,基于 fflateunzlibSync
  • RLE(游程编码)
  • PIZ(基于小波变换 + Huffman 的压缩)
  • B44 / B44A(有损压缩,面向 GPU 纹理)
  • DWA / DWAB(基于 DCT 的压缩)

读入方向支持 UnsignedByte、HalfFloat(半浮点)与 Float(浮点)三种数据类型的 DataTexture。需要说明:官方 API 文档中 .type 属性的可取值标注为 HalfFloatType | FloatType(默认 HalfFloatType),而源码类注释同时提到可读出 UnsignedByte 数据,实际以你使用的具体版本 API 标注为准。

引入方式(Addon 显式导入)

EXRLoader 属于 examples 目录下的 addon 模块,不包含在 three.js 核心构建产物中,必须显式导入。官方推荐写法:

import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';

该模块同时被登记在 examples/jsm/Addons.js 的 addons 清单中,因此 three/addons/... 的别名映射路径是有效的。在仓库内使用 importmap 的示例中,对应的别名配置为 "three/addons/": "./jsm/",即实际文件位于 examples/jsm/loaders/EXRLoader.js。如不使用构建工具与 importmap,也可直接以模块路径引用该文件。

快速上手

基础异步加载

官方文档给出的最小示例:

const loader = new EXRLoader();
const texture = await loader.loadAsync( 'textures/memorial.exr' );

loadAsyncLoader 基类基于 load() 封装的 Promise 版本,成功后即可把返回的 DataTexture 赋给材质的 mapenvMap 或场景背景。仓库中也有对应的实际示例纹理 examples/textures/memorial.exrexamples/textures/piz_compressed.exr

链式配置后再加载

setDataTypesetOutputFormatsetPart 三个方法都返回加载器自身,支持链式调用:

const loader = new EXRLoader()
	.setDataType( THREE.FloatType )      // 以 32 位浮点输出
	.setOutputFormat( THREE.RGBAFormat );// 输出 RGBA
const texture = await loader.loadAsync( 'textures/piz_compressed.exr' );

只解析内存中的 ArrayBuffer

parse( buffer ) 直接接收 ArrayBuffer,适合已经拿到的文件字节(如拖拽文件或从服务端获取的二进制数据),返回符合 DataTextureLoader~TexData 约定的对象;若希望直接生成纹理而不走网络加载,还可调用基类 DataTextureLoader 提供的 createDataTexture( buffer )(见 src/loaders/DataTextureLoader.js)。

构造函数与参数

new EXRLoader( manager : LoadingManager )

构造一个 EXR 加载器。参数 managerLoadingManager(关联文档内部链接对应页面),用于统一管理进度、onStart/onProgress/onError 事件,可省略。该参数最终透传给父类 Loader / DataTextureLoader 构造器(源码 EXRLoader.js)。

属性详解

.type : HalfFloatType | FloatType

纹理的数据类型。默认 HalfFloatType(半浮点),即每个通道 16 位浮点。源码中在构造函数内显式初始化为 this.type = HalfFloatTypeEXRLoader.js)。半浮点在带宽与内存上比全浮点省一半,同时足以表达高动态范围,是 HDR 纹理的常用选择;对精度敏感的场景(例如需要精确的数值运算)可通过 .type = THREE.FloatType 切换为 32 位浮点。

.outputFormat : RGBAFormat | RGFormat | RedFormat

纹理输出格式,默认 RGBAFormat。源码默认值为 this.outputFormat = RGBAFormatEXRLoader.js)。三通道图像会被归一化为四通道 RGBA(见下文“RGBA 输出”),从而规避部分设备上的软件模拟开销。若你的 EXR 为灰度(只有 Y 通道)等场景,可尝试设置为 RGFormatRedFormat 减少通道数。

.part : number

多分区(multi-part)EXR 文件中要加载的分区索引,默认 0。源码默认 this.part = 0EXRLoader.js)。真正解析时会对该值做边界收敛:partIndex = Math.max( 0, Math.min( this.part, EXRHeaders.length - 1 ) )EXRLoader.js),即使传入越界索引也会安全地落在有效分区范围内。

方法详解

.parse( buffer : ArrayBuffer ) : DataTextureLoader~TexData

解析给定的 EXR 二进制数据,重写(Overrides)自 DataTextureLoaderparse() 抽象约定。返回对象包含以下字段(见 EXRLoader.js):

字段
header 当前分区的 EXR 头信息(含压缩类型、数据窗口、通道列表等)
width / height 由数据窗口 dataWindow.xMax - xMin + 1 等计算出的像素尺寸
data 解码后的字节数组(TypedArray 底层 buffer)
format 解码器按输出格式决定的纹理格式
colorSpace 默认线性颜色空间 LinearSRGBColorSpace
type 即当前 .type
minFilter / magFilter 均为 LinearFilter
generateMipmaps false(EXR 图像常常是非 2 的幂次即 NPOT,故默认不生成 mipmap)
flipY false

基类 DataTextureLoader.js_applyTexData() 随后会把上述字段逐一应用到 DataTexture 上:填充 image.width/height/data,默认 wrapS/wrapTClampToEdgeWrapping,并把 colorSpaceformattypeminFilter/magFilter 等同步到纹理对象,最后置 needsUpdate = true

.setDataType( value : HalfFloatType | FloatType ) : EXRLoader

设置纹理数据类型,写入 this.type 并返回 this 供链式调用(源码见 EXRLoader.js)。与直接赋值 .type = value 等价。

.setOutputFormat( value : RGBAFormat | RGFormat | RedFormat ) : EXRLoader

设置输出格式,写入 this.outputFormat 并返回 thisEXRLoader.js)。默认 RGBAFormat

.setPart( value : number ) : EXRLoader

对多分区文件设置要加载的分区索引,写入 this.part 并返回 thisEXRLoader.js)。分区编号从 0 开始。

仓库实例:把 EXR 加载到场景中

基础示例:EXR 贴图显示

完整可运行的实现见 examples/webgl_loader_texture_exr.html。其核心片段:

import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';

new EXRLoader()
	.load( 'textures/memorial.exr', function ( texture, textureData ) {

		// memorial.exr 是 NPOT 尺寸
		const material = new THREE.MeshBasicMaterial( { map: texture } );

		const quad = new THREE.PlaneGeometry( 1.5 * textureData.width / textureData.height, 1.5 );
		const mesh = new THREE.Mesh( quad, material );
		scene.add( mesh );

	} );

几点可验证的细节:

  • load() 的回调形参是 ( texture, textureData ),其中 textureDataparse() 的返回对象,可通过 textureData.width/height 反推四边形宽高比,这正是示例第 77 行的用法;
  • 示例注释(第 70-73 行)明确指出 EXRLoader 会设置这些默认值:texture.generateMipmaps = falsetexture.minFilter = LinearFiltertexture.magFilter = LinearFilter——与 parse() 返回值中的 generateMipmaps: falseLinearFilter 完全对应;
  • HDR 画面需要开启色调映射才能正确显示,示例使用 renderer.toneMapping = THREE.ReinhardToneMapping 并提供曝光参数 GUI 调节。

环境映射示例:EXR 作为 HDR 环境贴图

examples/webgl_materials_envmaps_exr.html,展示了 EXR 的典型高级用法——加载等距柱状投影(equirectangular)HDR 全景,配合 PMREMGenerator 预滤波生成用于 PBR 反射的环境立方纹理:

import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';
import { PMREMGenerator } from 'three/addons/...'; // 视实际版本而定

const pmremGenerator = new THREE.PMREMGenerator( renderer );

new EXRLoader().load( 'textures/piz_compressed.exr', function ( texture ) {

	texture.mapping = THREE.EquirectangularReflectionMapping;
	exrCubeRenderTarget = pmremGenerator.fromEquirectangular( texture );
	exrBackground = texture;

} );

注意这里使用的素材恰是 piz_compressed.exr——PIZ 压缩在工业界广泛用于合成器输出,正好检验加载器的 PIZ 解码路径。

深入:EXR 解码管线(源码级)

parse() 到像素数据的完整流程可分为五步,全部位于 examples/jsm/loaders/EXRLoader.js

1. 解析头部并校验格式

parseHeader( bufferDataView, buffer, offset ) 读取 EXR 头,得到按分区组织的 EXRHeaders 数组(支持 2.0 规范中的 multi-part)。随后按 this.part 选取要解码的分区。

2. 处理多分区 / 深度数据的偏移表

源码 EXRLoader.js:当头部 multiPartdeepFormat 为真时,对目标分区记录 chunk 偏移表 _chunkOffsets,对其余分区则仅用 parseInt64 跳过偏移表,保证读取位置正确。

3. 按压缩类型装配解码器

setupDecoder() 依据头部 compression 字段做 switch 分发(EXRLoader.js):

头部压缩字段 解码函数 blockHeight(每块行数)
NO_COMPRESSION uncompressRAW 1
RLE_COMPRESSION uncompressRLE 1
ZIPS_COMPRESSION uncompressZIP 1
ZIP_COMPRESSION uncompressZIP 16
PIZ_COMPRESSION uncompressPIZ 32
PXR24_COMPRESSION uncompressPXR 16
B44_COMPRESSION / B44A_COMPRESSION uncompressB44 32
DWAA_COMPRESSION uncompressDWA 32
DWAB_COMPRESSION uncompressDWA 256
其他 抛出 THREE.EXRLoader: ... is unsupported

其中 uncompressZIP 调用 fflateunzlibSync(文件顶部 import { unzlibSync } from '../libs/fflate.module.js')完成 DEFLATE 解压。注意源码还额外实现了 PXR24_COMPRESSION,而官方文档的支持列表(uncompressed、ZIP(S)、RLE、PIZ、B44/A、DWA/B)未把 PXR24 列入,属于实现层面比文档列举更多的细节。

4. 校验通道组合,确定输出通道数

源码 EXRLoader.js 会检查输入通道名并决定解码策略:

  • 存在 YRYBY(亮度 + 色差)→ 按 4 通道输出并标记 yCbCr = true,后续做 YCbCr → RGB 颜色转换;
  • 存在 RGB → 按 4 通道输出(RGB);
  • 仅存在 Y(灰度/luminance)→ 按 1 通道输出;
  • 否则抛出 file contains unsupported data channels.

5. 按输出格式做通道排布与色彩转换

.outputFormat 设置最终的 formatdecodeChannels 映射(EXRLoader.js):

  • RGBAFormat(默认):RGBA 四通道,缺省 A 通道时填充 fillAlpha = true,解码后若原图只有 RGB,则通过字节后处理把 RGB 复制为 RGBA 或填充 Alpha=1(见 shouldExpand 逻辑,EXRLoader.js),颜色空间为 LinearSRGBColorSpace
  • RGFormat / RedFormat:输出两通道/单通道,相应地只把 R(或 Y)写入对应位置;
  • Y/Cb/Cr 图像的第二遍转换:当 EXRDecoder.yCbCr 为真时,利用 Rec.709 系数就地完成亮度色差到 RGB 的换算:R = (1 + RY) * YB = (1 + BY) * YG = (Y - R*0.2126 - B*0.0722) / 0.7152EXRLoader.js),且对负值做 Math.max( 0, ... ) 截断;HalfFloat 路径会先用 decodeFloat16 还原数值再经 DataUtils.toHalfFloat 写回半浮点。

解码完成后即构造前面列举的 TexData 返回对象,交由基类完成 DataTexture 的配置。

使用注意事项小结

  • 类型精度与内存:默认 HalfFloatType 是 HDR 加载的均衡之选;改用 FloatType 会成倍增加纹理内存,仅在数值精度需求明确时使用。
  • 不生成 mipmap:因为 EXR 多为 NPOT 图像,加载器默认 generateMipmaps = false、过滤为 LinearFilter,直接用于贴图时缩放渲染可能出现闪烁/锯齿,可按需自行对纹理做尺寸规整后另行开启 mipmap。
  • 颜色空间:解析结果的颜色空间被标记为线性 LinearSRGBColorSpace,HDR 环境光/贴图应按线性数据参与光照计算,输出到屏幕前再交给渲染器的色调映射与色彩管理。
  • 多分区文件:EXR 2.0 的多分区(multi-part,如含独立深度/AOV 分区的合成文件)需通过 setPart() 指定目标分区。
  • 格式一致性setDataType / setOutputFormat 影响纹理的 typeformat,需要与材质/渲染目标所需的像素布局匹配,例如 RGBA 半浮点对绝大多数 PBR 材质与 PMREM 流程是通用配置。

延伸阅读

登录后查看全文
热门项目推荐
相关项目推荐