three.js DDSLoader 详解:S3TC 压缩纹理的加载、解析原理与实战用法
本文基于 three.js 官方 API 文档 DDSLoader 页面 展开,系统讲解 DDSLoader 的使用方法、API 签名,并结合开源仓库中的实现源码 examples/jsm/loaders/DDSLoader.js、父类 src/loaders/CompressedTextureLoader.js 以及官方示例 examples/webgl_loader_texture_dds.html,说明 DDS(S3TC)压缩纹理从 HTTP 下载到 GPU 纹理对象之间的完整数据流。读完本文,你将能够正确加载 DXT1/DXT3/DXT5/BC6H 等压缩纹理、理解其头部解析细节,并知道该用哪些过滤器参数获得最佳渲染效果。
1. DDSLoader 是什么
DDSLoader 是 three.js 中用于加载 S3TC(Squishy 3D Compressed Textures,即 DirectDraw Surface 容器格式)压缩纹理的加载器。压缩纹理在 GPU 侧以块压缩形式存储与采样,相比未压缩的 RGBA 数据可显著减少显存占用与带宽消耗(DXT1 可将 64 bit/像素 压缩到 4 bit/像素)。
DDSLoader 位于 Addons 生态中,继承关系为:
Inheritance:
Loader→CompressedTextureLoader→DDSLoader
其中 Loader 是所有加载器的基类,CompressedTextureLoader 是加载 S3TC、ASTC、ETC 等压缩纹理格式的抽象基类(参见 CompressedTextureLoader 文档),它负责通过 FileLoader 以 arraybuffer 响应类型下载二进制文件,再调用子类实现的 parse() 方法完成解析。
2. 安装与导入
DDSLoader 是 addon,必须显式导入,而不是从 three 主包中引入:
import { DDSLoader } from 'three/addons/loaders/DDSLoader.js';
在仓库中该模块定义于 examples/jsm/loaders/DDSLoader.js,并统一由 examples/jsm/Addons.js 再导出(export * from './loaders/DDSLoader.js')。如果使用 import map,需将 three/addons/ 映射到 examples/jsm/ 目录,官方示例的写法如下(见 examples/webgl_loader_texture_dds.html):
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
3. 构造函数
new DDSLoader( manager : LoadingManager )
构造一个新的 DDS 加载器。
- manager:可选的
LoadingManager实例。所有 Loader 系类都接受一个加载管理器参数,用于统一跟踪onLoad/onError/onProgress回调与资源计数。不传时内部会使用默认的单例管理器。
从源码看,DDSLoader 的构造函数本身不做任何额外初始化,直接透传给父类(examples/jsm/loaders/DDSLoader.js#L32-L36):
constructor( manager ) {
super( manager );
}
4. 核心方法 .parse( buffer, loadMipmaps )
.parse( buffer : ArrayBuffer, loadMipmaps : boolean ) : CompressedTextureLoader~TexData
解析给定的 S3TC 纹理数据,覆盖父类 CompressedTextureLoader#parse。
- buffer:原始纹理数据的
ArrayBuffer(通常由FileLoader以arraybuffer响应类型下载得到); - loadMipmaps:是否加载 mipmap 链;
- 返回值:一个表示解析结果的
TexData对象,其结构在 src/loaders/CompressedTextureLoader.js#L154-L165 中定义:
TexData {
width, height, // 基础 mipmap 的宽高
isCubemap, // 是否为立方体贴图
mipmapCount, // mipmap 数量
mipmaps, // 每个层级的 { data, width, height }
format // three.js 的纹理格式常量
}
4.1 头部解析与支持的格式
parse() 按微软 DDS 文件规范逐字段解析头部。头部是一个 31 个 int32 的固定结构(magic 校验为 DDS_MAGIC = 0x20534444,即 "DDS "),magic 不匹配会输出 Invalid magic number in DDS header 错误并返回空结果(examples/jsm/loaders/DDSLoader.js#L200-L209)。
根据 FourCC 字段,parse() 支持以下格式(examples/jsm/loaders/DDSLoader.js#L220-L308):
| FourCC / 扩展格式 | blockBytes(每 4×4 块字节数) | 映射到 three.js 格式常量 |
|---|---|---|
| DXT1 | 8 | RGB_S3TC_DXT1_Format |
| DXT3 | 16 | RGBA_S3TC_DXT3_Format |
| DXT5 | 16 | RGBA_S3TC_DXT5_Format |
| ETC1 | 8 | RGB_ETC1_Format |
| DX10 → BC6H_UF16(95) | 16 | RGB_BPTC_UNSIGNED_Format |
| DX10 → BC6H_SF16(96) | 16 | RGB_BPTC_SIGNED_Format |
| 未压缩 32-bit ARGB(位掩码校验通过) | 64 | RGBAFormat |
| 未压缩 24-bit RGB(位掩码校验通过) | 64 | RGBAFormat |
几个值得注意的实现细节:
- DX10 扩展头:当 FourCC 为
DX10时,真实格式藏在紧跟标准头之后、长度为 5 个 int32 的扩展头中(dxgiFormat字段)。当前实现只支持BC6H_UF16/BC6H_SF16两种半浮点格式,其他 DXGI 值会报Unsupported DXGI_FORMAT code错误(examples/jsm/loaders/DDSLoader.js#L246-L278)。这解释了仓库示例纹理中disturb_dx10_bc6h_*_*.dds的用途——BC6H 适合存储高动态范围的 HDR 类数据。 - 未压缩分支:若 FourCC 不是压缩码,则退化为检查
RGBBitCount与 R/G/B/A 位掩码。32 位 ARGB 与 24 位 RGB 都被按RGBAFormat处理,blockBytes = 64即 64 bit/像素 的未压缩尺寸。 - 通道重排:DDS 中的未压缩 ARGB 数据在内存里是 BGRA 排列,
loadARGBMip()会逐像素交换为 RGBA;loadRGBMip()则在补上 255 不透明 alpha 后同样输出 RGBA(examples/jsm/loaders/DDSLoader.js#L109-L162)。而压缩格式的块数据不做任何重排,直接以Uint8Array视图引用原 buffer 的对应区间,避免额外拷贝。 - mipmap 尺寸计算:每一级 mipmap 的字节数按块压缩规则计算为
ceil(width/4) × ceil(height/4) × blockBytes,随后宽高减半(最小为 1)进入下一级,直到mipmapCount层全部提取完毕(examples/jsm/loaders/DDSLoader.js#L337-L377)。 - 立方体贴图校验:读取
caps2字段中的DDSCAPS2_CUBEMAP标志判断是否为立方体贴图,且要求 6 个面(POSITIVE/NEGATIVE X/Y/Z)标志全部置位,否则报Incomplete cubemap faces错误(examples/jsm/loaders/DDSLoader.js#L318-L332)。
4.2 loadMipmaps 参数如何生效
loadMipmaps 决定 mipmapCount 的取值:只有当文件头声明了 DDSD_MIPMAPCOUNT 标志且 loadMipmaps !== false 时才采用文件中的完整 mipmap 链,否则强制为 1(仅基础层级)(examples/jsm/loaders/DDSLoader.js#L310-L316)。
5. .load():由父类完成的下载与纹理装配
DDSLoader 没有重写 load(),直接复用 src/loaders/CompressedTextureLoader.js#L41-L150 中的实现,其调用链为:
DDSLoader.load(url)
→ FileLoader( responseType: 'arraybuffer' ) 下载原始字节
→ scope.parse( buffer, true ) 解析为 TexData
→ 写入 CompressedTexture(image / mipmaps / format / minFilter)
→ texture.needsUpdate = true
→ onLoad( texture )
两条关键路径:
- 单 URL(含单文件立方体贴图):若
parse结果isCubemap为真,父类会把mipmaps数组按mipmapCount均分,拆成 6 个 face 再挂到texture.image上;否则直接写texture.image.width/height与texture.mipmaps(src/loaders/CompressedTextureLoader.js#L100-L131)。 - URL 数组(6 个文件组成立方体贴图):并行下载 6 个文件,全部完成后一次性装配,并仅在
mipmapCount === 1时将texture.minFilter设为LinearFilter——这是必要的,因为没有 mipmap 链时 Mipmap 过滤方式会产生未定义行为(src/loaders/CompressedTextureLoader.js#L72-L82)。
load() 的回调签名遵循标准 Loader 约定:load( url, onLoad, onProgress, onError ),返回值是一个可以立即赋给材质的 CompressedTexture,加载完成后纹理会自动出现在场景中(needsUpdate = true 触发 GPU 上传)。
6. 实战示例
官方最小示例(与文档 DDSLoader 页面 一致):
const loader = new DDSLoader();
const map = loader.load( 'textures/compressed/disturb_dxt1_nomip.dds' );
map.colorSpace = THREE.SRGBColorSpace; // 仅适用于颜色纹理
两点实践要点:
- colorSpace:
SRGBColorSpace只对颜色纹理(如 albedo)设置;法线贴图等数据纹理不应设置。仓库中的示例纹理位于 examples/textures/compressed/(如disturb_dxt1_mip.dds、hepatica_dxt3_mip.dds、explosion_dxt5_mip.dds、Mountains.dds等)。 - 无 mipmap 纹理:对于
nomip文件,需手动指定minFilter/magFilter为LinearFilter,避免使用 Mipmap 过滤。
官方完整示例 examples/webgl_loader_texture_dds.html 覆盖了 DXT1/DXT3/DXT5/ARGB/BC6H/24 位未压缩与单文件立方体贴图等全部场景,其中对三种 DXT 变体的选型建议值得直接沿用(examples/webgl_loader_texture_dds.html#L50-L56):
/*
This is how compressed textures are supposed to be used:
DXT1 - RGB - opaque textures
DXT3 - RGBA - transparent textures with sharp alpha transitions
DXT5 - RGBA - transparent textures with full alpha range
*/
完整加载与配置模式(摘自同一示例):
const loader = new DDSLoader();
// DXT1,无 mipmap:手动指定线性过滤
const map1 = loader.load( 'textures/compressed/disturb_dxt1_nomip.dds' );
map1.minFilter = map1.magFilter = THREE.LinearFilter;
map1.anisotropy = 4;
map1.colorSpace = THREE.SRGBColorSpace;
// DXT1,带 mipmap:可保留默认 Mipmap 过滤
const map2 = loader.load( 'textures/compressed/disturb_dxt1_mip.dds' );
map2.anisotropy = 4;
map2.colorSpace = THREE.SRGBColorSpace;
// 透明纹理:DXT3 用 alphaTest,DXT5 用完整 alpha + 混合
const map3 = loader.load( 'textures/compressed/hepatica_dxt3_mip.dds' );
const map4 = loader.load( 'textures/compressed/explosion_dxt5_mip.dds' );
const material3 = new THREE.MeshBasicMaterial( { map: map3, alphaTest: 0.5, side: THREE.DoubleSide } );
const material4 = new THREE.MeshBasicMaterial( { map: map4, side: THREE.DoubleSide,
blending: THREE.AdditiveBlending, depthTest: false, transparent: true } );
// 单文件立方体贴图:在 onLoad 中设置 mapping
const cubemap = loader.load( 'textures/compressed/Mountains.dds', function ( texture ) {
texture.magFilter = THREE.LinearFilter;
texture.minFilter = THREE.LinearFilter;
texture.mapping = THREE.CubeReflectionMapping;
texture.colorSpace = THREE.SRGBColorSpace;
material1.needsUpdate = true; // 材质已引用纹理时,需再次触发更新
} );
注意 BC6H(HDR 类)纹理不应设置 colorSpace,示例中 map7–map10 即未设置。
7. 关键源码索引
| 内容 | 路径 |
|---|---|
DDSLoader 实现(头部解析、格式映射、mipmap 提取) |
examples/jsm/loaders/DDSLoader.js |
load() 下载/装配逻辑与 TexData 类型定义 |
src/loaders/CompressedTextureLoader.js |
格式常量定义(如 RGB_S3TC_DXT1_Format = 33776 等) |
src/constants.js |
| 官方交互示例 | examples/webgl_loader_texture_dds.html |
| Addons 再导出 | examples/jsm/Addons.js |
| 官方 API 文档源文件 | docs/pages/DDSLoader.html.md |
8. 小结与选型建议
DDSLoader通过覆盖parse()把 DDS 二进制解析为TexData,再由父类CompressedTextureLoader.load()装配为CompressedTexture,整套流程无需手动处理FileLoader;- 支持 DXT1/DXT3/DXT5、ETC1、BC6H(DX10 扩展头)以及 32 位 ARGB / 24 位 RGB 未压缩 DDS;不支持的 FourCC 或 DXGI 格式会在控制台输出明确的错误信息并返回空
TexData; - 使用时的三个易错点:单文件立方体贴图需设置
CubeReflectionMapping;nomip 纹理需手动关闭 Mipmap 过滤(示例中已自动降级为LinearFilter);colorSpace仅给颜色纹理设置; - 若目标是移动端或需要 ASTC 等更新格式,仓库中另有 KTXLoader.js 与 KTX2Loader.js 可作为替代方案,它们同样遵循
CompressedTextureLoader的parse()契约。
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 StartedRust0624
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