three.js WebGPUTextureUtils 详解:用 decompress 将压缩纹理解压为 CanvasTexture 的完整指南
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
WebGPUTextureUtils 是 three.js 提供的一个附加(addon)工具模块,专门用于配合WebGPURenderer将CompressedTexture(压缩纹理)解压为可在 CPU 侧直接读写的CanvasTexture。本文基于该模块的官方文档与仓库源码,完整讲解其导入方式、decompress方法签名与参数语义、底层实现原理,以及它在GLTFExporter、USDZExporter等导出流程中的实际用法,帮助你正确地在 WebGPU 渲染管线中处理压缩纹理的解压与回读。
模块定位与适用场景
压缩纹理(如 KTX2、DDS 等格式)在 GPU 上以 GPU 专用格式存储,内存占用低、加载快,但其像素数据无法直接被 CPU 读取或用于图像导出。WebGPUTextureUtils正是为这一需求而生:它把 GPU 上的压缩纹理"重绘"到一张离屏画布上,再包装成标准的CanvasTexture返回,从而让纹理数据可以用于导出、二次处理或 CPU 侧检查。
从 examples/jsm/utils/WebGPUTextureUtils.js 的文件头注释可以看到它的定位:
/** * @module WebGPUTextureUtils * @three_import import * as WebGPUTextureUtils from 'three/addons/utils/WebGPUTextureUtils.js'; */该模块只能与WebGPURenderer配合使用;如果项目中使用的是WebGLRenderer,则应改用同目录下的 WebGLTextureUtils(对应源码 examples/jsm/utils/WebGLTextureUtils.js)。两个模块在 API 上高度对称,但内部实现路径完全不同。
安装与导入
WebGPUTextureUtils是 three.js 的附加模块(addon),不会打包进核心库,必须显式导入。仓库的 addons 目录即 examples/jsm,常规导入方式如下:
import * as WebGPUTextureUtils from 'three/addons/utils/WebGPUTextureUtils.js';这里使用命名空间导入,是因为模块对外只暴露一个decompress函数,导入后通过WebGPUTextureUtils.decompress()调用。若需了解 addons 的整体引入机制,可参考仓库根目录的 README.md 中关于 examples/jsm 的说明。
decompress 方法签名与参数详解
原文档定义的唯一静态方法是decompress,其完整签名如下:
decompress( blitTexture : CompressedTexture, maxTextureSize : number, renderer : WebGPURenderer ) : Promise.<CanvasTexture>这是一个async方法,返回一个解析为解压后纹理的 Promise。三个参数说明如下:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
blitTexture | CompressedTexture | 必填 | 待解压的压缩纹理 |
maxTextureSize | number | Infinity | 解压后纹理的最大边长(像素),用于限制输出尺寸 |
renderer | WebGPURenderer | null | 渲染器实例引用;为null时方法内部会自动创建并管理一个临时的WebGPURenderer |
返回值:一个Promise,resolve 后得到解压完成的CanvasTexture。
参数行为细节
blitTexture:任何CompressedTexture实例均可,通常来自KTX2Loader、DDSLoader等压缩纹理加载器。方法会读取其image.width/image.height作为输出基准尺寸,并沿用其采样与环绕设置(详见下文"源码实现原理")。maxTextureSize:默认Infinity。当传入有限值时,输出尺寸为Math.min( texture.image.width, maxTextureSize ),可用来控制解压后的纹理占用,避免超大纹理(如 8192² 以上的 HDR/环境贴图)解压出过大的位图。renderer:默认null。传入自己的渲染器可复用已有的 GPU 上下文;不传时方法内部会new WebGPURenderer()并await renderer.init(),解压完成后自动dispose()该临时渲染器并释放引用。
源码实现原理:一次离屏 "blit" 渲染
阅读 examples/jsm/utils/WebGPUTextureUtils.js 的完整实现,可以还原出decompress的内部执行流程:
export async function decompress( blitTexture, maxTextureSize = Infinity, renderer = null ) { if ( renderer === null ) { renderer = _renderer = new WebGPURenderer(); await renderer.init(); } const material = new NodeMaterial(); material.fragmentNode = texture( blitTexture, uv().flipY() ); const width = Math.min( blitTexture.image.width, maxTextureSize ); const height = Math.min( blitTexture.image.height, maxTextureSize ); const currentOutputColorSpace = renderer.outputColorSpace; renderer.setSize( width, height ); renderer.outputColorSpace = blitTexture.colorSpace; _quadMesh.material = material; _quadMesh.render( renderer ); renderer.outputColorSpace = currentOutputColorSpace; const canvas = document.createElement( 'canvas' ); const context = canvas.getContext( '2d' ); canvas.width = width; canvas.height = height; context.drawImage( renderer.domElement, 0, 0, width, height ); const readableTexture = new CanvasTexture( canvas ); readableTexture.minFilter = blitTexture.minFilter; readableTexture.magFilter = blitTexture.magFilter; readableTexture.wrapS = blitTexture.wrapS; readableTexture.wrapT = blitTexture.wrapT; readableTexture.colorSpace = blitTexture.colorSpace; readableTexture.name = blitTexture.name; if ( _renderer !== null ) { _renderer.dispose(); _renderer = null; } return readableTexture; }实现可拆解为以下几个关键环节:
渲染器准备:若未传入渲染器,模块级变量
_renderer会保存一个新建的WebGPURenderer并等待其init()完成,保证 WebGPU 设备可用。TSL 全屏采样:方法通过模块级单例
QuadMesh(/*@__PURE__*/ new QuadMesh())配合NodeMaterial完成一次全屏 blit。材质片元节点使用 TSL 表达式texture( blitTexture, uv().flipY() )——texture与uv均来自three/tsl——其中flipY()将 UV 翻转,以匹配 WebGPU 纹理坐标与 Canvas 的 Y 轴方向约定。尺寸与色彩空间处理:输出尺寸取原纹理尺寸与
maxTextureSize的较小值;渲染前临时把renderer.outputColorSpace设为源纹理的colorSpace,渲染结束后恢复原值。这一步保证解压结果的颜色空间与源纹理一致,避免色彩偏差。像素回读:
context.drawImage( renderer.domElement, 0, 0, width, height )把渲染结果绘制到 2D Canvas 上,将 GPU 侧的像素数据拷贝为 CPU 可读的位图。纹理属性继承:新建的
CanvasTexture会继承源纹理的minFilter、magFilter、wrapS、wrapT、colorSpace与name,确保解压后的纹理在后续使用中保持与原始压缩纹理一致的采样与包裹行为。资源清理:若使用了内部创建的临时渲染器,方法结束时立即
dispose()并置空,避免 GPU 资源泄漏。
从模块级缓存const _quadMesh = /*@__PURE__*/ new QuadMesh()可以看出,全屏四边形在模块加载时只创建一次,多次调用decompress会复用同一网格,仅切换材质,这是对性能友好的设计。
与 WebGLTextureUtils 的对比
WebGLTextureUtils(源码 examples/jsm/utils/WebGLTextureUtils.js)提供同名函数decompress,但面向 WebGL 渲染管线,二者的差异值得注意:
| 维度 | WebGPUTextureUtils | WebGLTextureUtils |
|---|---|---|
| 依赖渲染器 | WebGPURenderer | WebGLRenderer |
| 返回值 | Promise<CanvasTexture>(异步) | CanvasTexture(同步) |
| 内部实现 | TSLNodeMaterial+QuadMesh | ShaderMaterial+ 全屏Mesh(PlaneGeometry(2,2,1,1)) |
| 默认创建渲染器 | new WebGPURenderer()并await init() | new WebGLRenderer({ antialias: false }) |
| 清理方式 | dispose() | forceContextLoss()后dispose() |
在 WebGL 版本中,采样着色器还通过fullscreenQuadMaterial.defines.IS_SRGB(当texture.colorSpace == SRGBColorSpace时置位)在片元着色器内执行 sRGB 的sRGBTransferOETF转换;而 WebGPU 版本则依赖renderer.outputColorSpace的临时切换。两者的最终目标一致:把压缩纹理变成可读的CanvasTexture。选择哪个模块,只取决于你的渲染器类型。
实战:在 GLTFExporter 与 USDZExporter 中解压纹理
decompress最常见的实际用途是配合导出器使用。压缩纹理无法直接被 glTF/USDZ 等格式引用,导出前必须解压成普通纹理。从源码可以看到两个导出器都预留了纹理工具注入点:
- GLTFExporter 通过
setTextureUtils( utils )注入,内部在导出纹理贴图(如map、normalMap、metalnessMap、roughnessMap)时调用this.textureUtils.decompress( texture, maxTextureSize )(见 examples/jsm/exporters/GLTFExporter.js 的decompressTextureAsync方法,该方法还支持options.maxTextureSize限制导出纹理尺寸)。 - USDZExporter 同样内置
textureUtils属性,在需要写出纹理时执行await this.textureUtils.decompress( texture )(见 examples/jsm/exporters/USDZExporter.js)。
两个导出器的注释均明确说明:用 WebGL 渲染器就注入WebGLTextureUtils,用 WebGPU 渲染器就注入WebGPUTextureUtils。典型用法示例:
import { WebGPURenderer } from 'three/webgpu'; import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js'; import * as WebGPUTextureUtils from 'three/addons/utils/WebGPUTextureUtils.js'; const renderer = new WebGPURenderer(); await renderer.init(); const exporter = new GLTFExporter(); exporter.setTextureUtils( WebGPUTextureUtils ); // 注入解压工具 // 场景中若包含 KTX2/DDS 等压缩纹理,导出时会自动先解压 exporter.parse( scene, ( gltf ) => { // 处理导出结果... }, ( error ) => { console.error( error ); }, { maxTextureSize: 2048 } );直接调用decompress的场景则相对少,通常出现在需要"读取压缩纹理像素"的自定义逻辑中:
import { WebGPURenderer } from 'three/webgpu'; import * as WebGPUTextureUtils from 'three/addons/utils/WebGPUTextureUtils.js'; const renderer = new WebGPURenderer(); await renderer.init(); // ktx2Texture 为 KTX2Loader 加载得到的 CompressedTexture const readableTexture = await WebGPUTextureUtils.decompress( ktx2Texture, Infinity, renderer ); // readableTexture.image 即为可被 drawImage / getImageData 读取的 canvas注意事项与限制
- 渲染器类型必须匹配:
WebGPUTextureUtils只接受WebGPURenderer。若在 WebGL 项目中使用会得到错误结果,应改用 WebGLTextureUtils。 - 依赖 TSL 与 NodeMaterial:WebGPU 版本基于
three/tsl的texture、uv表达式构建材质,因此需要项目中已启用 WebGPU 渲染器及其 TSL 支持(从three/webgpu入口导入即可)。 - 异步行为:
decompress是 async 函数,且当未传入渲染器时内部会等待renderer.init()完成,因此必须用await或.then()获取结果。 - 性能考量:解压操作涉及一次离屏渲染与像素回读(
drawImage),属于较重操作,应避免在每帧内反复调用;批量导出时建议复用同一个渲染器实例传入renderer参数,而不是让方法反复创建/销毁临时渲染器。 - 尺寸限制:通过
maxTextureSize控制输出分辨率,可显著降低大纹理解压时的内存与导出体积开销,默认Infinity表示不限制。
小结
WebGPUTextureUtils.decompress是 three.js WebGPU 生态中连接"GPU 压缩纹理"与"CPU 可读纹理"的关键桥梁。它用一次 TSL 全屏 blit 渲染 + Canvas 回读的方式,将CompressedTexture转换为继承原纹理采样与色彩属性的CanvasTexture,并被GLTFExporter、USDZExporter等导出工具直接依赖。掌握了它的签名、参数语义与实现原理,你就可以在 WebGPU 渲染管线下自如地处理压缩纹理的读取、导出与二次处理。
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考