首页
/ Three.js KTX2Loader 实战指南:KTX 2.0 纹理容器解析与 Basis Universal GPU 纹理转码

Three.js KTX2Loader 实战指南:KTX 2.0 纹理容器解析与 Basis Universal GPU 纹理转码

2026-09-07 18:38:41作者:牧宁李

本指南围绕 Three.js 仓库中的 docs/pages/KTX2Loader.html.md 展开,系统讲解 KTX2Loader(一个用于加载 KTX 2.0 GPU 纹理容器的加载器)的完整用法、API 语义与底层转码机制。读完本文将掌握:如何在 WebGL/WebGPU 渲染器下配置与调用 KTX2Loader,如何让同一份 .ktx2 资源在异构 GPU 上自动转码为目标压缩格式,以及加载器内部"容器解析 → Worker 转码 → 纹理对象生成"的完整调用链。

KTX2Loader 是什么:KTX 2.0 容器与 Basis Universal

KTX2Loader 是 three.js 提供的 KTX 2.0 GPU 纹理容器加载器,继承自 Loader。KTX 2.0 是 Khronos 定义的通用 GPU 纹理容器格式,可以容纳多种 GPU 纹理编码。当前加载器的核心能力集中在 Basis Universal GPU 纹理上:这类纹理可以被快速转码为种类繁多的 GPU 硬件压缩格式,从而在"磁盘/网络体积小"与"GPU 原生解压快"之间取得平衡。文档也明确说明其能力边界:虽然 KTX 2.0 允许放置其它厂商硬件专用格式,但本加载器目前不会解析这些非 Basis 格式的硬件压缩内容(不过从源码看,它已支持直接读取一批未压缩与标准块压缩的 raw 格式,详见后文)。

使用 KTX2Loader 依赖 WebAssembly(用于运行 Basis Universal 转码器),因此不支持 WASM 的旧浏览器无法使用该加载器。所需的 WASM 转码器与 JS 包装文件位于 examples/jsm/libs/basis 目录中。

文档在 References 中指向的外部资料包括:Khronos KTX 规范、KHR Data Format(DFD)规范、以及 BasisU HDR(UASTC HDR)纹理规格——这些是理解 KTX2 头结构与色彩信息编码的权威背景。

快速上手:加载第一张 .ktx2 纹理

文档给出的最简用法如下:

const loader = new KTX2Loader();
loader.setTranscoderPath( 'examples/jsm/libs/basis/' );
loader.detectSupport( renderer );
const texture = loader.loadAsync( 'diffuse.ktx2' );

三个步骤缺一不可,含义分别是:

  1. new KTX2Loader() 创建加载器实例;
  2. setTranscoderPath() 指向存放 basis_transcoder.jsbasis_transcoder.wasm 的目录;
  3. detectSupport( renderer ) 探测当前设备支持的 GPU 压缩格式,据此决定转码输出格式;文档明确要求 必须在加载纹理之前调用——若遗漏,load()parse() 会直接抛错(见 KTX2Loader.js#L409-L413)。

仓库中的官方示例 examples/webgl_loader_texture_ktx2.html 给出了更贴近实战的完整流程:先通过 .setPath('textures/ktx2/') 设定资源根路径,再链式调用 .detectSupport(renderer),随后在循环中 await loader.loadAsync(path),并把返回的纹理赋给 MeshBasicMaterialmap 属性。该示例所用到的测试资源(如 2d_etc1s.ktx22d_uastc.ktx22d_astc4x4.ktx22d_bc7.ktx22d_rgba8.ktx2 等)就存放在 examples/textures/ktx2/ 目录中,是实验本加载器最直接的素材。

导入与运行依赖

KTX2Loader 属于 addon 模块,必须显式导入:

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

除 three.js 核心外,它运行时还需要以下文件配合(全部已随仓库提供):

依赖文件 作用
examples/jsm/libs/basis/basis_transcoder.wasm WebAssembly 版 Basis Universal 转码器二进制
examples/jsm/libs/basis/basis_transcoder.js 转码器的 JS 包装与加载入口
examples/jsm/libs/ktx-parse.module.js 解析 KTX2 容器头与 DFD(Data Format Descriptor)的模块
examples/jsm/libs/zstddec.module.js Zstd 超压缩解码器(仅在处理 Zstd 超压缩的原生 raw 纹理时使用)

从源码看,加载器默认通过 new URL('../libs/basis/basis_transcoder.wasm', import.meta.url) 与同名的 .js 包装来自动定位这两个转码文件(见 KTX2Loader.js),因此并不需要手动 setTranscoderPath,除非你想把转码文件部署到 CDN 或自定义路径。examples/jsm/libs/basis/README.md 对这两个文件及其 Apache License 2.0 许可也有说明。

构造函数与加载器配置 API

new KTX2Loader( manager : LoadingManager )

构造一个新的 KTX2 加载器。manager 是可选参数,传入 LoadingManager 后可统一管理加载进度事件。构造时加载器内部会创建一个 WorkerPool(来自 examples/jsm/utils/WorkerPool.js)用于后续并行转码,并初始化 transcoderPathtranscoderBinarytranscoderPendingworkerSourceURLworkerConfig 等内部字段(KTX2Loader.js)。

.setTranscoderPath( path : string ) : KTX2Loader

设置转码器(解码器)的加载路径。文档明确指出:默认情况下 WASM 转码器和 JS 包装会从 examples/jsm/libs/basis 目录加载,因此该方法主要用于"从 CDN 等自定义位置加载解码器"的场景。设置后,加载器会以该路径为基址去请求 basis_transcoder.jsbasis_transcoder.wasmKTX2Loader.js)。返回 this,可链式调用。

.setWorkerLimit( workerLimit : number ) : KTX2Loader

设置本实例最多可分配的 Web Worker 数量,透传给内部的 WorkerPool.setWorkerLimit()KTX2Loader.js)。转码任务通过 Worker 池并发执行,增大该值可提升多纹理批量转码的吞吐,但会占用更多内存与线程资源。返回 this

GPU 能力探测:detectSupport 的工作原理

.detectSupport( renderer : WebGPURenderer | WebGLRenderer ) : KTX2Loader 用于探测硬件对各类压缩纹理格式的支持情况,从而确定转码输出目标。它对两类渲染器走了两条不同的探测路径(KTX2Loader.js):

  • WebGPU 渲染器:调用 renderer.hasFeature(...) 查询 texture-compression-astctexture-compression-etc2texture-compression-s3tctexture-compression-bctexture-compression-pvrtctexture-compression-etc1 等特性;其中 astcHDRSupported 在 WebGPU 上被固定为 false(源码注释关联了 WebGPU 规范中尚未落实 ASTC HDR 的议题)。
  • WebGL 渲染器:通过 renderer.extensions 检查 WEBGL_compressed_texture_astc(并读取其 profile 判断是否支持 HDR)、WEBGL_compressed_texture_etcWEBGL_compressed_texture_s3tcEXT_texture_compression_bptcWEBGL_compressed_texture_pvrtc(含 WebKit 前缀变体)等扩展。

源码中还包含一个值得注意的 Linux/Mesa 特判KTX2Loader.js):在 Linux(且非 Android)上若同时暴露 ASTC、ETC2、BPTC、S3TC,说明这些扩展很可能是 Mesa 驱动的软件模拟产物——直接在驱动内做软件解压会造成主线程性能问题,因此加载器会主动关闭 ASTC、ETC1、ETC2 支持,改用其余真实硬件格式转码。

方法内部会把探测结果封装进 workerConfig 对象,供后续 Worker 初始化时读取(每个新 Worker 创建时都会通过 postMessage 携带该配置,见 KTX2Loader.js)。返回 this

弃用提示.detectSupportAsync( renderer : WebGPURenderer ) : Promise 是该方法曾经的异步版本,现已标记为 Deprecated(r181 起)。源码中的实现只是先 await renderer.init() 再转发给 detectSupport(),并打印弃用警告(KTX2Loader.js)。文档建议的新写法是:直接调用 detectSupport(),并在创建渲染器时 await renderer.init()

核心加载流程:load → parse → Worker 转码

.load( url, onLoad, onProgress, onError )

从给定 URL 开始加载,并把解析好的 KTX2 纹理传给 onLoad() 回调。url 可以是普通路径/URL,也可以是 data URI。该方法基于内部 FileLoader(响应类型设为 arraybuffer)请求文件,拿到字节后交给 parse() 处理(KTX2Loader.js),同时继承了 LoaderpathcrossOriginwithCredentialsrequestHeader 等设置。

.parse( buffer : ArrayBuffer, onLoad, onError ) : Promise

解析给定 ArrayBuffer 中的 KTX2 数据,并在解析/转码完成后通过 onLoad 回调交出纹理。源码里用一个 WeakMap 作为任务缓存(_taskCache):同一个 buffer 若已被提交转码,会直接复用其进行中的 Promise,避免已转移(transferred)的 buffer 被重复转移(KTX2Loader.js)。

内部调用链

parse() 最终进入私有的 _createTexture()KTX2Loader.js),其流程可概括为:

  1. 用 ktx-parse 的 read() 解析容器头,得到 vkFormat、DFD、各级 mipmap 等元数据;
  2. 判断纹理是否为 Basis UASTC HDR:判定条件是 vkFormat === VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT 且 DFD 的 colorModel 为 0xA7(UASTC HDR 本质是 ASTC 的子集,可高效转成 BC6H);
  3. 判断 needsTranscoder:仅当 vkFormatVK_FORMAT_UNDEFINED(即 Basis 编码),或 HDR 纹理但设备不支持 ASTC HDR 时才需要转码;否则走"raw 直载"路径 createRawTexture()(无需转码,见下文);
  4. 需要转码时,先 init() 懒加载转码器,再把 buffer 通过 WorkerPool 发到 Web Worker 中执行 transcode,结果交给 _createTextureFrom() 组装成最终纹理对象。

在 Worker 内部(KTX2Loader.js),逻辑是:init 消息驱动 WASM 模块初始化(调用 BASIS()initializeBasis(),并检查 KTX2File API 是否存在);随后对每条 transcode 消息执行 transcode():创建 BasisModule.KTX2File → 校验文件有效性 → 通过 isUASTC()/isETC1S()/isHDR() 判定 Basis 编码类型 → 读取宽高、层数、级数、面数、是否含 Alpha、DFD 标志 → 选择转码目标格式 → startTranscoding() → 按 face/mip/layer 三层循环逐级 transcodeImage() 输出像素数据。其中还会对非 4 倍数尺寸给出警告(ETC1S/UASTC 建议使用 4 的倍数尺寸),并在无 mipmap 时特殊处理非 4 倍数维度(对应 issue mrdoob/three.js#25908,见 KTX2Loader.js)。

转码目标格式的选择策略

加载器内建了一份按 Basis 编码类型分组的格式优先级表 FORMAT_OPTIONSKTX2Loader.js),排序基准是"高画质 > 低画质 > 未压缩"。为不同 Basis 格式排出的首选转码目标大致如下:

设备能力 ETC1S 优先级 UASTC 优先级 输出(含 Alpha 时) 说明
ASTC 备用 1(最高) RGBA_ASTC_4x4 UASTC 首选
BPTC 3 2 RGBA_BPTC(BC7_M5)
DXT/S3TC 4 5 DXT1 / DXT5(含 Alpha)
ETC2 1(ETC1S 首选) 3 ETC2 / ETC2+EAC
ETC1 2 4 RGB_ETC1 仅不含 Alpha 时可选
PVRTC 5 6 PVRTC 4bpp 要求宽高为 2 的幂
BC6H RGB_BPTC_UNSIGNED(HalfFloat) 仅 UASTC HDR
未压缩回退 100 100 RGBAFormat / RGBA_HALF 任何设备都可用

getTranscoderFormat()KTX2Loader.js)会依次遍历当前 Basis 编码的候选列表,跳过"设备不支持、无 Alpha 版本的转码目标、非 2 次幂尺寸下的 PVRTC"等条目,选中第一个可用的 { transcoderFormat, engineFormat, engineType }。整套映射依赖三个静态常量表:KTX2Loader.BasisFormat(ETC1S/UASTC/UASTC_HDR)、KTX2Loader.TranscoderFormat(约 20 种 Basis 转码器内部格式,如 BC1/BC3/BC7/ETC2/ASTC_4x4/BC6H/RGBA32 等)、以及 KTX2Loader.EngineFormat/EngineType(映射到 three.js 的 *_Format 常量与 UnsignedByteType/HalfFloatType 等,见 KTX2Loader.js)。

非 Basis 原生格式与 "raw" 直载路径

并非所有 KTX2 文件都需要转码。若容器头中的 vkFormat 不是 VK_FORMAT_UNDEFINED 且设备可直接支持,加载器会走 createRawTexture() 路径(KTX2Loader.js)把像素数据原样解包为 three.js 纹理:

  • FORMAT_MAPKTX2Loader.js)把 Vulkan 格式号映射为 three.js format 常量,覆盖未压缩格式(R32G32B32A32_SFLOAT、R16G16B16A16、R8G8B8A8、R9G9B9E5、R11G11B10 等)以及一批块压缩格式(ETC2/EAC、ASTC 4x4/6x6、BC1-BC7、PVRTC1);
  • TYPE_MAPKTX2Loader.js)对应给出 FloatType/HalfFloatType/UnsignedShortType/UnsignedByteType 等数据类型;
  • 若容器的 supercompressionScheme 为 Zstd(KHR_SUPERCOMPRESSION_ZSTD),则用 ZSTDDecoder 先解压各 mip 级数据再封装(Zstd 解码器实例以模块级 _zstd 惰性单例缓存);
  • 未压缩格式生成 DataTexture(或带深度的 Data3DTexture),块压缩格式生成 CompressedTexture;mipmap 数量等于 0 时表示"运行时生成",此时会开启 generateMipmaps

最终组装的纹理对象统一设置采样过滤(有完整 mipmap 链用 LinearMipmapLinearFilter,否则 LinearFilter/NearestFilter),并把 needsUpdate 置为 true。因此该加载器实际覆盖的范围比文档标题描述更宽——例如示例 examples/webgl_loader_texture_ktx2.html 中的 2d_rgba8.ktx22d_rgb9e5_linear.ktx2 等未压缩文件加载后就是 DataTexture,而 2d_astc4x4.ktx22d_bc1~bc7.ktx2 等加载后则是 CompressedTexture

元数据解析:色彩空间、Alpha 预乘与立方体/数组纹理

加载器非常注重把容器内的元数据还原到 three.js 纹理属性上:

  • 色彩空间parseColorSpace()KTX2Loader.js)依据 DFD 中的 color primaries 与 transfer function 推断:BT709+sRGB → SRGBColorSpace,BT709+linear → LinearSRGBColorSpace,DisplayP3 对应 DisplayP3ColorSpace/LinearDisplayP3ColorSpace,primaries 未指明则回退 NoColorSpace
  • Alpha 预乘:根据 DFD 标志 KHR_DF_FLAG_ALPHA_PREMULTIPLIED 设置纹理的 premultiplyAlpha
  • 纹理维度faceCount === 6 时构造 CompressedCubeTexturelayerCount > 1 时构造 CompressedArrayTexture;普通 2D 纹理构造 CompressedTextureKTX2Loader.js)。

生命周期管理:dispose 与多实例注意

当加载器不再需要时应调用 .dispose()KTX2Loader.js),它会释放 WorkerPool 中所有 Worker,并通过 URL.revokeObjectURL 回收为 Worker 脚本创建的 Blob URL。此外源码维护一个模块级计数器 _activeLoaders同时存在多个活跃的 KTX2Loader 实例时(每个实例都会各自加载转码器并占用 Worker),控制台会输出性能警告,建议"复用一个单例实例,或在旧实例上调用 dispose()"(KTX2Loader.js)。

官方示例与建议

仓库内置了两份可直接运行的 KTX2 纹理示例:

配套测试资源位于 examples/textures/ktx2/。若把 KTX2 用作 glTF 等模型的纹理,也可参考 examples/webgl_loader_gltf_compressed.htmlexamples/webgpu_loader_gltf_compressed.html 中把 KTX2Loader 注册给 GLTFLoader 的整合方式。

使用建议小结:在加载前务必调用 detectSupport();生产环境建议把加载器做成单例并手动管理 dispose();ETC1S/UASTC 纹理优先使用 4 倍数尺寸并携带完整 mipmap 链;若依赖默认路径,请确保 basis_transcoder.js/.wasmktx-parsezstddec 等资源可随应用正常部署与跨域访问。

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