首页
/ three.js DDSLoader 详解:S3TC 压缩纹理的加载、解析原理与实战用法

three.js DDSLoader 详解:S3TC 压缩纹理的加载、解析原理与实战用法

2026-09-06 17:30:04作者:卓炯娓

本文基于 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: LoaderCompressedTextureLoaderDDSLoader

其中 Loader 是所有加载器的基类,CompressedTextureLoader 是加载 S3TC、ASTC、ETC 等压缩纹理格式的抽象基类(参见 CompressedTextureLoader 文档),它负责通过 FileLoaderarraybuffer 响应类型下载二进制文件,再调用子类实现的 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(通常由 FileLoaderarraybuffer 响应类型下载得到);
  • 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

几个值得注意的实现细节:

  1. 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 类数据。
  2. 未压缩分支:若 FourCC 不是压缩码,则退化为检查 RGBBitCount 与 R/G/B/A 位掩码。32 位 ARGB 与 24 位 RGB 都被按 RGBAFormat 处理,blockBytes = 64 即 64 bit/像素 的未压缩尺寸。
  3. 通道重排:DDS 中的未压缩 ARGB 数据在内存里是 BGRA 排列,loadARGBMip() 会逐像素交换为 RGBA;loadRGBMip() 则在补上 255 不透明 alpha 后同样输出 RGBA(examples/jsm/loaders/DDSLoader.js#L109-L162)。而压缩格式的块数据不做任何重排,直接以 Uint8Array 视图引用原 buffer 的对应区间,避免额外拷贝。
  4. mipmap 尺寸计算:每一级 mipmap 的字节数按块压缩规则计算为 ceil(width/4) × ceil(height/4) × blockBytes,随后宽高减半(最小为 1)进入下一级,直到 mipmapCount 层全部提取完毕(examples/jsm/loaders/DDSLoader.js#L337-L377)。
  5. 立方体贴图校验:读取 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/heighttexture.mipmapssrc/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; // 仅适用于颜色纹理

两点实践要点:

  • colorSpaceSRGBColorSpace 只对颜色纹理(如 albedo)设置;法线贴图等数据纹理不应设置。仓库中的示例纹理位于 examples/textures/compressed/(如 disturb_dxt1_mip.ddshepatica_dxt3_mip.ddsexplosion_dxt5_mip.ddsMountains.dds 等)。
  • 无 mipmap 纹理:对于 nomip 文件,需手动指定 minFilter/magFilterLinearFilter,避免使用 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,示例中 map7map10 即未设置。

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.jsKTX2Loader.js 可作为替代方案,它们同样遵循 CompressedTextureLoaderparse() 契约。
登录后查看全文
热门项目推荐
相关项目推荐