☰
three.js UltraHDRLoader 实战指南:用 Gain Map 技术加载 Ultra HDR 全景纹理
2026/10/24 22:59:54 网站建设 项目流程

three.js UltraHDRLoader 实战指南:用 Gain Map 技术加载 Ultra HDR 全景纹理

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

UltraHDR 是一种基于 JPEG 编码、携带 HDR Gain Map 的轻量级高动态范围图像格式。本文围绕 UltraHDRLoader 官方文档展开,结合 源码实现 与仓库内置示例,讲解其在 three.js 中的导入方式、API 用法、解析原理与适用范围。读完本文你将掌握如何在 Web 场景中把.hdr.jpg(Ultra HDR)文件加载为环境贴图或普通 HDR 纹理,并了解底层 SDR + Gain Map 重建 HDR 的完整流程。

UltraHDRLoader 是 three.js 的一个加载器扩展(addon),专门用于加载 Google/Android 阵营推动的 Ultra HDR 图像格式。它在内部同时解码同一文件中的SDR 基准图(primary/SDR image)与增益图(gain map),按增益元数据重建出超过普通 8-bit JPEG 动态范围的 HDR 像素,最终产出可直接赋给scene.background、scene.environment或材质贴图的DataTexture。

UltraHDR 图像格式速览

UltraHDR 并不是一种全新的压缩算法,而是把多份内容按固定结构装入一个扩展的 JPEG 容器中。根据 UltraHDRLoader 源码头注释,其文件布局可以归纳为:

[JPEG headers] // 必需的 JPEG 标记 [MPF 容器与 SDR/gain map 的元数据] // 描述两幅子图的存储位置 - XMP metadata(legacy 格式) - ISO 21496-1 metadata(现行标准格式) [可选元数据] [EXIF] [ICC Profile] [SDR image] // 普通可显示的 SDR 主图 [Gainmap image with metadata] // 增益图及其参数

文件内的各个区段由0xFFXX字节标记分隔,并带有描述字节(0xFFE0、0xFFE1、0xFFE2),而二进制图像存储区则以独特的 16 位描述符0xFFD8(JPEG SOI 标记)为前缀。解析器正是依据这套标记体系逐段扫描的。

特性支持矩阵

以 官方文档 为准,当前实现状态如下:

能力状态
JPEG headers(格式信号)必需,已支持
XMP 元数据(legacy 格式)已支持
ISO 21496-1 元数据(现行标准)已支持
XMP 校验未实现
EXIF profile未实现
ICC profile未实现
SDR 与 HDR 图像的二进制存储必需,已支持
Gainmap 元数据必需,已支持
非 JPEG 图像格式未实现
主图为 HDR 图像(BaseRenditionIsHDR)未实现

也就是说,UltraHDRLoader 面向的是“SDR 主图 + gain map”的标准 Android Ultra HDR JPEG;若文件携带主图即 HDR 的变体,目前会被拒绝。

快速上手:加载 Ultra HDR 环境贴图

官方文档的代码示例 给出了最典型的使用方式——把 Ultra HDR 图像用作球体贴图(equirectangular)并同时赋给场景的背景与环境光照:

const loader = new UltraHDRLoader(); const texture = await loader.loadAsync( 'textures/equirectangular/ice_planet_close.jpg' ); texture.mapping = THREE.EquirectangularReflectionMapping; scene.background = texture; scene.environment = texture;

loadAsync是 three.js 对load()的 Promise 封装;当纹理加载并解析完成后,URL 中返回的就是一个可用的DataTexture。上面的ice_planet_close.jpg正是仓库内置的 Ultra HDR 样本之一,位于 examples/textures/equirectangular。

导入方式(Addon)

UltraHDRLoader 与大多数纹理加载器不同,它不属于核心包,必须显式导入:

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

在仓库源码环境中,对应的实际文件为 examples/jsm/loaders/UltraHDRLoader.js,并已被统一收纳进 examples/jsm/Addons.js 的 addon 集合中对外导出。使用 ESM 时需要确保 import map 或打包器将three/addons/正确映射到上述 jsm 目录。

完整 API 参考

类继承自 three.js 的基础加载器Loader(仓库核心对应 src/loaders/Loader.js,文档页见 Loader)。

构造器:new UltraHDRLoader( manager : LoadingManager )

构造一个新的 Ultra HDR 加载器。manager为可选的 LoadingManager,用于统一跟踪与调度加载进度。构造器内会初始化属性.type,默认值为HalfFloatType(源码 L78-L90)。

属性:.type : HalfFloatType | FloatType

决定最终纹理像素的数据类型,默认是HalfFloatType。

  • HalfFloatType:像素存为Uint16Array(半精度浮点),内存占用减半,适合大分辨率环境图;
  • FloatType:像素存为Float32Array(单精度浮点),精度更高但内存翻倍。

方法:.setDataType( value : HalfFloatType | FloatType ) : UltraHDRLoader

设置纹理类型并返回加载器自身,便于链式调用。典型用法:

const loader = new UltraHDRLoader().setDataType( THREE.FloatType );

方法:.load( url : string, onLoad : function, onProgress : onProgressCallback, onError : onErrorCallback ) : DataTexture

从 URL 开始加载,加载完成后把得到的 Ultra HDR 纹理传给onLoad()回调。

  • url:要加载的文件路径或 URL,也可以传 data URI;
  • onLoad:加载过程结束(parse 完成)后执行,回调收到(texture, texData);
  • onProgress:加载过程中反复触发;
  • onError:发生错误时触发。

返回值:一个DataTexture(该方法重写了基类 Loader#load)。需要说明的是,load()会立即同步返回一个“空壳”纹理对象(宽高为 0),真正的图像数据在异步解析完成后才填充进texture.image,并把texture.needsUpdate置为true(见 源码 load 实现)。

方法:.parse( buffer : ArrayBuffer, onLoad : function )

解析给定的 Ultra HDR 原始数据(重写 Loader#parse)。

  • buffer:原始二进制数据(ArrayBuffer);
  • onLoad:解析完成后收到{ width, height, data, format: RGBAFormat, type }形式的纹理数据对象(源码 L326-L332)。

注意.parse()直接调用并不适合生产链路;日常使用推荐走loadAsync()/load()。.parse()内部在缺少version元数据时会抛出THREE.UltraHDRLoader: Not a valid UltraHDR image,而无法同时定位到 SDR 主图与 gain map 时会抛出Could not parse UltraHDR images(源码 L310-L346)。

仓库内置实战示例

仓库在 examples/webgl_loader_texture_ultrahdr.html 中提供了可运行的完整示例,它在 torus knot(环形结)网格上把 Ultra HDR 全景图用作 PBR 环境光照:

import { UltraHDRLoader } from 'three/addons/loaders/UltraHDRLoader.js'; loader = new UltraHDRLoader(); loader.setDataType( THREE.FloatType ); const loadEnvironment = function ( resolution = '2k', type = 'HalfFloatType' ) { loader.setDataType( THREE[ type ] ); loader.load( `textures/equirectangular/spruit_sunrise_${resolution}.hdr.jpg`, function ( texture ) { texture.mapping = THREE.EquirectangularReflectionMapping; texture.needsUpdate = true; scene.background = texture; scene.environment = texture; } ); }; loadEnvironment( params.resolution, params.type );

示例页面还通过 lil-gui 提供了resolution(2k/4k)与type(HalfFloatType/FloatType)的动态切换,用于对比两种像素精度的视觉与性能差异。渲染器侧则配合ACESFilmicToneMapping与可调的toneMappingExposure输出最终画面。

仓库内置的 Ultra HDR 样本文件还包括:

  • spruit_sunrise_2k.hdr.jpg 与spruit_sunrise_4k.hdr.jpg(示例主用资源)
  • moonless_golf_2k.hdr.jpg、royal_esplanade_2k.hdr.jpg
  • ice_planet_close.jpg(文档示例资源)

现有 HDR/EXR 纹理可通过官方文档提及的 gainmap-creator 在线转换工具(将.hdr文件转为携带 gain map 的.hdr.jpg)得到;仓库中的上述*.hdr.jpg正是此类转换产物,可与同目录下的原始*.hdr文件对照理解。

底层原理:源码级解析管线

围绕 examples/jsm/loaders/UltraHDRLoader.js,其工作可拆解为 4 个阶段,理解这些能帮你更好地排查兼容性问题。

1. JPEG 分段扫描

parse()开头会以0xFF标记为线索做逐字节的“JPEG 段感知扫描”(源码 L131-L190):

  • 0xFFD8(SOI)无长度字段,直接跳过 2 字节;
  • 0xFFE0(APP0)、0xFFE1(APP1)、0xFFE2(APP2)读取大端 16 位长度并完整入队;
  • 0xC0–0xFE区间的其它带长度字段标记同样跳过;
  • 0xFFD9(EOI)与0xD0–0xD7(RST)复位标记无长度字段。

扫描得到的分段会被分类处理:APP0 仅作头信号;APP1 交给 XMP 解析;APP2 交给 MPF/ISO 21496-1 解析。

2. 元数据解析

加载器支持两套并存的 gain map 元数据标准:

  • XMP(legacy):_parseXMPMetadata()(源码 L538-L590)用DOMParser解析 XML,读取Container:Directory(仅做存在性校验)或rdf:Description上的hdrgm:Version、GainMapMin、GainMapMax、Gamma、OffsetSDR、OffsetHDR、HDRCapacityMin/Max等属性;
  • ISO 21496-1(现行标准):_parseISOMetadata()(源码 L357-L468)先探测 APP2 段内的"urn:iso:std:iso:ts:21496:-1\0"命名空间,再按 flags 位判断backwardDirection与useCommonDenominator,分别走“公共分母”或“独立分子/分母”两种定点数分支,读出全部 HDR 重建参数。

MPF 段则解析主图(SDR)与 gain map 的 size/offset,定位两幅 JPEG 子图在文件中的绝对偏移(源码 L242-L304)。

3. 子图解码与对齐

拿到两个二进制子图后,_applyGainmapToSDR()(源码 L613-L751)用createImageBitmap( new Blob( [ data ], { type: 'image/jpeg' } ) )并行解码,并做两项关键处理:

  • 宽高比校验:SDR 主图与 gain map 的宽高比不一致时直接报错;
  • 分辨率对齐:借助 Canvas 2D(colorSpace: 'srgb'上下文)将 gain map 缩放到 SDR 的像素分辨率,再以getImageData()读回原始 sRGB 像素。

4. HDR 重建公式

这是整个加载器的核心算法。重建遵循 Android HDR Image Format 规范中“用 gain map 生成适配 HDR 图像”的公式(源码 L679-L740):

  • 先由显示峰值推算出裁剪在[0,1]之间的权重因子weightFactor;
  • 对每个像素,由 gain map 值按gamma取幂得到logRecovery,再映射到[gainMapMin, gainMapMax]区间得到logBoost;
  • 应用hdrValue = ( sdrValue + offsetSDR ) * 2^( logBoost * weightFactor ) - offsetHDR;
  • 最后把结果经 sRGB→linear 转换并钳制到[0, 65504](半精度上限)。

其中 sRGB→linear 转换使用了预计算的 1024 项查找表SRGB_TO_LINEAR(源码 L33-L40),线性插值以1.8的指数修正近似误差;输出缓冲按.type写入Uint16Array(经DataUtils.toHalfFloat转半精度)或Float32Array。

生成的纹理对象

.load()在真正得到数据后创建/填充的DataTexture使用以下参数(源码 L482-L497):

  • format: RGBAFormat(重建后仍保留 alpha 通道布局)
  • type: 本实例 .type
  • 颜色空间LinearSRGBColorSpace(HDR 线性值,适配 PBR 光照计算)
  • generateMipmaps = true、flipY = true,过滤默认LinearFilter+LinearMipMapLinearFilter,环绕默认ClampToEdgeWrapping

因此加载结果可直接作为scene.environment用于基于图像的 PBR 光照(IBL),配合MeshStandardMaterial等物理材质可获得镜面反射与光泽感,如示例中的金属 torus knot 所示。

已知限制与注意事项

依据官方文档与源码,以下几点在使用前需要明确:

  1. 仅支持 JPEG 载体:非 JPEG 图像格式的 Ultra HDR 无法解析;
  2. 主图为 HDR 的变体未实现:若BaseRenditionIsHDR标记 SDR 主图本身已是 HDR,当前版本不会处理;
  3. 不解析 EXIF/ICC:色彩管理依赖内嵌的 sRGB 假设与 three.js 的颜色空间管线;
  4. 依赖浏览器能力:子图解码走createImageBitmap,Canvas 缩放需要willReadFrequently与 sRGB 颜色空间支持;
  5. 内存:FloatType会把每像素推到 16 字节(RGBA float),全景大图(如 4k)请按需选择HalfFloatType以平衡精度与显存。

该示例同时被纳入仓库的端到端冒烟测试集合,见 test/e2e/puppeteer.js#L12 中的webgl_loader_texture_ultrahdr(标记为需要约 1 分钟运行的用例),可作为验证加载器回归可靠性的参考。

小结

  • 用途:UltraHDRLoader 让 three.js 直接消费 Android/Google 生态的标准 Ultra HDR JPEG(.hdr.jpg),无需预处理为 EXR 或 RGBE。
  • API 关键点:Addon 显式导入、.setDataType()控制精度、loadAsync()返回可直接用作环境贴图的DataTexture。
  • 原理关键点:JPEG 分段扫描 → XMP/ISO 21496-1 gain map 元数据解析 → MPF 定位 SDR 与 gain map → sRGB 线性重建 + half/float 输出。
  • 局限关键点:仅 JPEG、仅 SDR 主图模式、不处理 EXIF/ICC,高动态场景建议使用HalfFloatType。

需要继续深入时,可依次阅读 UltraHDRLoader 官方文档页、源码实现、在线示例 HTML 以及 基础 Loader 文档,即可串联起从 API 到像素级重建的完整知识链。

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询