Three.js KTX2Loader 实战指南:KTX 2.0 纹理容器解析与 Basis Universal GPU 纹理转码
本指南围绕 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' );
三个步骤缺一不可,含义分别是:
new KTX2Loader()创建加载器实例;setTranscoderPath()指向存放basis_transcoder.js与basis_transcoder.wasm的目录;detectSupport( renderer )探测当前设备支持的 GPU 压缩格式,据此决定转码输出格式;文档明确要求 必须在加载纹理之前调用——若遗漏,load()与parse()会直接抛错(见 KTX2Loader.js 与 #L409-L413)。
仓库中的官方示例 examples/webgl_loader_texture_ktx2.html 给出了更贴近实战的完整流程:先通过 .setPath('textures/ktx2/') 设定资源根路径,再链式调用 .detectSupport(renderer),随后在循环中 await loader.loadAsync(path),并把返回的纹理赋给 MeshBasicMaterial 的 map 属性。该示例所用到的测试资源(如 2d_etc1s.ktx2、2d_uastc.ktx2、2d_astc4x4.ktx2、2d_bc7.ktx2、2d_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)用于后续并行转码,并初始化 transcoderPath、transcoderBinary、transcoderPending、workerSourceURL、workerConfig 等内部字段(KTX2Loader.js)。
.setTranscoderPath( path : string ) : KTX2Loader
设置转码器(解码器)的加载路径。文档明确指出:默认情况下 WASM 转码器和 JS 包装会从 examples/jsm/libs/basis 目录加载,因此该方法主要用于"从 CDN 等自定义位置加载解码器"的场景。设置后,加载器会以该路径为基址去请求 basis_transcoder.js 与 basis_transcoder.wasm(KTX2Loader.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-astc、texture-compression-etc2、texture-compression-s3tc、texture-compression-bc、texture-compression-pvrtc、texture-compression-etc1等特性;其中astcHDRSupported在 WebGPU 上被固定为false(源码注释关联了 WebGPU 规范中尚未落实 ASTC HDR 的议题)。 - WebGL 渲染器:通过
renderer.extensions检查WEBGL_compressed_texture_astc(并读取其 profile 判断是否支持 HDR)、WEBGL_compressed_texture_etc、WEBGL_compressed_texture_s3tc、EXT_texture_compression_bptc、WEBGL_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),同时继承了 Loader 的 path、crossOrigin、withCredentials、requestHeader 等设置。
.parse( buffer : ArrayBuffer, onLoad, onError ) : Promise
解析给定 ArrayBuffer 中的 KTX2 数据,并在解析/转码完成后通过 onLoad 回调交出纹理。源码里用一个 WeakMap 作为任务缓存(_taskCache):同一个 buffer 若已被提交转码,会直接复用其进行中的 Promise,避免已转移(transferred)的 buffer 被重复转移(KTX2Loader.js)。
内部调用链
parse() 最终进入私有的 _createTexture()(KTX2Loader.js),其流程可概括为:
- 用 ktx-parse 的
read()解析容器头,得到vkFormat、DFD、各级 mipmap 等元数据; - 判断纹理是否为 Basis UASTC HDR:判定条件是
vkFormat === VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT且 DFD 的 colorModel 为0xA7(UASTC HDR 本质是 ASTC 的子集,可高效转成 BC6H); - 判断
needsTranscoder:仅当vkFormat为VK_FORMAT_UNDEFINED(即 Basis 编码),或 HDR 纹理但设备不支持 ASTC HDR 时才需要转码;否则走"raw 直载"路径createRawTexture()(无需转码,见下文); - 需要转码时,先
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_OPTIONS(KTX2Loader.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_MAP(KTX2Loader.js)把 Vulkan 格式号映射为 three.js format 常量,覆盖未压缩格式(R32G32B32A32_SFLOAT、R16G16B16A16、R8G8B8A8、R9G9B9E5、R11G11B10 等)以及一批块压缩格式(ETC2/EAC、ASTC 4x4/6x6、BC1-BC7、PVRTC1);TYPE_MAP(KTX2Loader.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.ktx2、2d_rgb9e5_linear.ktx2 等未压缩文件加载后就是 DataTexture,而 2d_astc4x4.ktx2、2d_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时构造CompressedCubeTexture;layerCount > 1时构造CompressedArrayTexture;普通 2D 纹理构造CompressedTexture(KTX2Loader.js)。
生命周期管理:dispose 与多实例注意
当加载器不再需要时应调用 .dispose()(KTX2Loader.js),它会释放 WorkerPool 中所有 Worker,并通过 URL.revokeObjectURL 回收为 Worker 脚本创建的 Blob URL。此外源码维护一个模块级计数器 _activeLoaders:同时存在多个活跃的 KTX2Loader 实例时(每个实例都会各自加载转码器并占用 Worker),控制台会输出性能警告,建议"复用一个单例实例,或在旧实例上调用 dispose()"(KTX2Loader.js)。
官方示例与建议
仓库内置了两份可直接运行的 KTX2 纹理示例:
- WebGL:examples/webgl_loader_texture_ktx2.html,分三组演示未压缩格式(加载为
DataTexture)、压缩格式(加载为CompressedTexture,需 GPU 原生支持)与 Basis Universal 格式(2d_etc1s.ktx2/2d_uastc.ktx2/2d_uastc_hdr4x4.ktx2,加载后可在任意设备上使用,内存占用更低); - WebGPU:examples/webgpu_loader_texture_ktx2.html。
配套测试资源位于 examples/textures/ktx2/。若把 KTX2 用作 glTF 等模型的纹理,也可参考 examples/webgl_loader_gltf_compressed.html 与 examples/webgpu_loader_gltf_compressed.html 中把 KTX2Loader 注册给 GLTFLoader 的整合方式。
使用建议小结:在加载前务必调用 detectSupport();生产环境建议把加载器做成单例并手动管理 dispose();ETC1S/UASTC 纹理优先使用 4 倍数尺寸并携带完整 mipmap 链;若依赖默认路径,请确保 basis_transcoder.js/.wasm、ktx-parse、zstddec 等资源可随应用正常部署与跨域访问。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00