three.js Data3DTexture:用原始 TypedArray 数据构建三维纹理(体积纹理)完整指南
在 three.js 中,Data3DTexture 是把一维原始数据(TypedArray)按"宽 × 高 × 深"切分成三维体素的纹理类,对应 WebGL 2 的 TEXTURE_3D 目标。它是体积渲染(医学影像、云雾、流体密度场)的核心数据载体。读完本文,你将掌握 Data3DTexture 的完整构造参数、每个属性的默认值及其相对基类 Texture 的覆写行为,并了解它在 WebGL/WebGPU 渲染器内部的上传路径与官方示例中的实战用法(本文基于仓库当前版本 0.185.0)。
一、继承关系与核心定位
Data3DTexture 的继承链为:
EventDispatcher → Texture → Data3DTexture
实现位于 src/textures/Data3DTexture.js,类注释明确其职责:
Creates a three-dimensional texture from raw data, with parameters to divide it into width, height, and depth.
与 DataTexture(二维)的区别在于:Data3DTexture 的数据是一个真正的三维体(volume),GPU 端采样时使用 texture(uvw) 而非 texture(uv)。仓库中最典型的用途是把三维标量场(如 NRRD 医学数据、Perlin 噪声云)上传为体纹理,再用自定义 ShaderMaterial 做 MIP/等值面(isosurface)渲染,对应示例 examples/webgl_texture3d.html(标题即 "volume rendering example")。
二、构造函数:new Data3DTexture(data, width, height, depth)
签名与默认值如下(与文档 docs/pages/Data3DTexture.html.md 及源码 Data3DTexture.js#L20 完全一致):
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
data |
TypedArray | null |
体素的缓冲数据,一维线性排列 |
width |
number | 1 |
纹理宽度(X 方向体素数) |
height |
number | 1 |
纹理高度(Y 方向体素数) |
depth |
number | 1 |
纹理深度(Z 方向体素数) |
构造时源码做了三件关键的事:
// src/textures/Data3DTexture.js(节选)
constructor( data = null, width = 1, height = 1, depth = 1 ) {
// 注释说明:后续会引入 .setXXX() 方法,当前用户仍可直接赋值
// 例如 new THREE.Data3DTexture( data, width, height, depth );
// texture.anisotropy = 16;
// See #14839
super( null ); // 1. 基类 Texture 以 null 图像初始化
this.isData3DTexture = true; // 2. 类型标识,供渲染器做分发
this.image = { data, width, height, depth }; // 3. 以四元组封装体数据
// ... 覆写 filter / wrapping / mipmaps 等默认值
}
数据排布约定:data 是按 Z 平面 → Y 行 → X 列优先排列的一维数组,总长度至少为 width × height × depth × 每体素通道数(通道数由 format 决定,如 RedFormat 为 1、RGBAFormat 为 4)。官方示例中 128³ 的 Uint8Array 云数据正是如此生成(examples/webgl_texture3d_partialupdate.html):
const data = new Uint8Array( size * size * size );
for ( let z = 0; z < size; z ++ ) {
for ( let y = 0; y < size; y ++ ) {
for ( let x = 0; x < size; x ++ ) {
// 逐体素写入标量(噪声值 × 球面衰减系数)
data[ i ] = ( 128 + 128 * perlin.noise( x * scale / 1.5, y * scale, z * scale / 1.5 ) ) * fadingFactor;
i ++;
}
}
}
return new THREE.Data3DTexture( data, size, size, size );
注意官方示例 examples/webgl_texture3d.html 中的注释也印证了这一点:// Texture to hold the volume. We have scalars, so we put our data in the red channel.——标量场数据放在红色通道,配合 RedFormat + FloatType 使用。
三、属性详解:与基类 Texture 默认值的差异
Data3DTexture 在构造器中覆写了一组属性,目的是让"原始数据体纹理"的默认行为比基类 Texture 更安全(数据纹理不能随意 mipmap、行对齐必须可控)。下表逐项对照:
3.1 .flipY(默认 false,覆写基类的 true)
设为 true 时,纹理在上传 GPU 前会沿垂直轴翻转。基类 Texture 的 flipY 默认为 true(见 Texture.js#L281),因为普通图像数据(HTML 图像、Canvas)的 Y 轴方向与 GL 相反;但三维体数据(如医学影像的 Z 向上约定)通常是按行主序直接写入的,翻转反而会破坏轴向语义,因此 Data3DTexture 强制覆写为 false。上传时该值经 state.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ) 生效,见 WebGLTextures.js#L926。
3.2 .generateMipmaps(默认 false,覆写基类的 true)
是否允许渲染器为该纹理生成 mipmap。基类默认为 true,Data3DTexture 覆写为 false:体素数据往往不是 2 的幂尺寸,且 mipmap 生成会成倍消耗显存,所以默认关闭。若需要平滑采样,应自行生成 mipmap 级别并填入 mipmaps 数组,再把 minFilter 设为 mipmap 采样器。
3.3 .image(Object)
体纹理的图像定义,结构固定为 { data, width, height, depth },其中 data 是 TypedArray。在基类中 image 是通过 getter/setter 代理到内部 TextureSource 的属性(Texture.js#L414-L424),Data3DTexture 构造时先 super( null ) 再整体赋入这个对象,因此运行时可直接读 texture.image.data / .width / .height / .depth,也可通过 texture.width / texture.height / texture.depth 三个 getter 获取尺寸(基类统一实现,Texture.js#L385-L407)。
3.4 .isData3DTexture(readonly,默认 true)
类型判别标志。渲染管线靠它选择上传路径:WebGL 上传入口处 if ( texture.isData3DTexture ) textureType = _gl.TEXTURE_3D;(WebGLTextures.js#L905),WebGPU 后端同样以此分发(WebGPUTextureUtils.js#L674)。这也是 TSL 节点系统识别三维坐标(vec3f)的依据(WGSLNodeBuilder.js#L478)。
3.5 .magFilter / .minFilter(默认 NearestFilter,覆写基类默认值)
magFilter:一个体素覆盖多于一个像素(放大采样)时的采样方式。基类默认LinearFilter,Data3DTexture覆写为NearestFilter。minFilter:一个体素覆盖少于一个像素(缩小采样)时的采样方式。基类默认LinearMipmapLinearFilter,Data3DTexture覆写为NearestFilter。
两者取值均为六种采样器常量(定义于 src/constants.js):NearestFilter | NearestMipmapNearestFilter | NearestMipmapLinearFilter | LinearFilter | LinearMipmapNearestFilter | LinearMipmapLinearFilter。
为什么数据纹理默认最近邻?体素数据(尤其是量化后的密度场)做线性插值会在体素边界产生不属于任何体素的中间值。官方体积渲染示例中显式改回线性以换取平滑的等值面过渡:
texture.minFilter = texture.magFilter = THREE.LinearFilter;
3.6 .unpackAlignment(默认 1,覆写基类的 4)
指定内存中每行像素起始位置的对齐要求,合法值为 1(字节对齐)、2、4、8,对应 GL 的 UNPACK_ALIGNMENT。基类 Texture 默认 4(Texture.js#L291),而 Data3DTexture 覆写为 1:当 width × 每通道字节数 不能被 4 整除时(例如宽度为 3、单通道 float),对齐要求 4 会导致解包错位;设为 1 是最安全的通用选择。上传路径中它经 state.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ) 下发(WebGLTextures.js#L932)。官方示例中也显式写出 texture.unpackAlignment = 1; 作为防御性配置。
3.7 .wrapR(默认 ClampToEdgeWrapping)
定义纹理在**深度方向(W 分量)**的环绕方式,对应 UVW 映射中的 W 轴;水平/垂直方向仍由基类的 wrapS / wrapT 控制。取值为 RepeatWrapping | ClampToEdgeWrapping | MirroredRepeatWrapping。wrapR 是 Data3DTexture 独有的属性,基类 copy() 只拷贝 wrapS/wrapT,因此子类专门重写了拷贝逻辑(Data3DTexture.js#L116-L124):
copy( source ) {
super.copy( source ); // 基类拷贝 name、source、filter、wrapS/wrapT 等
this.wrapR = source.wrapR; // 补充拷贝 W 方向环绕
return this;
}
这意味着对 Data3DTexture 调用 clone() / copy() 后,wrapR 也会被正确继承。
3.8 其余继承属性
除上述覆写项外,format、type、colorSpace、anisotropy、normalized、repeat(配合 RepeatWrapping 平铺)、offset 等全部继承自 Texture,用法与二维数据纹理一致。更新语义同样继承:修改 image.data 后需 texture.needsUpdate = true(内部会递增 version 并标记 source,见 Texture.js#L754-L763),不再需要时调用 texture.dispose() 释放 GPU 资源。
四、数据如何上传到 GPU:源码级流程
WebGLRenderer 上传体纹理的完整路径在 src/renderers/webgl/WebGLTextures.js 的 uploadTexture() 中:
- 目标选择(L900-L905):
let textureType = _gl.TEXTURE_2D;
if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) textureType = _gl.TEXTURE_2D_ARRAY;
if ( texture.isData3DTexture ) textureType = _gl.TEXTURE_3D;
-
pixelStore 设置(L926-L932):
flipY、premultiplyAlpha、unpackAlignment在此统一下发,这解释了上一节各覆写默认值为何直接决定解包行为。 -
三维上传(L1210-L1230):
} else if ( texture.isData3DTexture ) {
if ( useTexStorage ) {
if ( allocateMemory ) {
state.texStorage3D( _gl.TEXTURE_3D, levels, glInternalFormat, image.width, image.height, image.depth );
}
if ( dataReady ) {
state.texSubImage3D( _gl.TEXTURE_3D, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data );
}
} else {
state.texImage3D( _gl.TEXTURE_3D, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data );
}
}
可以看到:首次上传时先 texStorage3D 预留显存(mipmap 层数由 levels 决定,默认无 mipmap 时为 1 层),再 texSubImage3D 写入数据;不支持 texStorage3D 的旧环境退化为一次性 texImage3D。内部格式(如 R32F)由 format + type 组合推导——示例注释里 "THREEJS will select R32F (33326) based on the THREE.RedFormat and THREE.FloatType" 说的就是这一步。
此外,从源码结构看,WebGLRenderer 的读/拷贝回(blit)路径同样识别 isData3DTexture(WebGLRenderer.js#L3353),WebGPU 后端由 WebGPUTextureUtils 提供等价支持,因此 Data3DTexture 在 WebGL 与 WebGPU 两个渲染栈下都可用。
五、实战:用 NRRD 医学数据做体积渲染
官方示例 examples/webgl_texture3d.html 是 Data3DTexture 最完整的参考实现:用 NRRDLoader 加载 stent 数据集,放入 RedFormat + FloatType 的 3D 纹理,再用 VolumeRenderShader1 在包围盒背面片元上沿视线步进采样,支持 MIP 与等值面两种渲染风格。核心片段:
import { NRRDLoader } from 'three/addons/loaders/NRRDLoader.js';
import { VolumeRenderShader1 } from 'three/addons/shaders/VolumeShader.js';
new NRRDLoader().load( 'models/nrrd/stent.nrrd', function ( volume ) {
// 标量数据放入红色通道;RedFormat + FloatType 会映射到 GPU 内部格式 R32F
const texture = new THREE.Data3DTexture( volume.data, volume.xLength, volume.yLength, volume.zLength );
texture.format = THREE.RedFormat;
texture.type = THREE.FloatType;
texture.minFilter = texture.magFilter = THREE.LinearFilter;
texture.unpackAlignment = 1;
texture.needsUpdate = true; // 首次上传必需
// 自定义体积 ShaderMaterial:u_data 为体纹理,u_size 为体素尺寸
uniforms[ 'u_data' ].value = texture;
uniforms[ 'u_size' ].value.set( volume.xLength, volume.yLength, volume.zLength );
material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: shader.vertexShader,
fragmentShader: shader.fragmentShader,
side: THREE.BackSide // 体积 Shader 以背面作为射线起点
} );
const geometry = new THREE.BoxGeometry( volume.xLength, volume.yLength, volume.zLength );
geometry.translate( volume.xLength / 2 - 0.5, volume.yLength / 2 - 0.5, volume.zLength / 2 - 0.5 );
scene.add( new THREE.Mesh( geometry, material ) );
} );
要点归纳:
- format/type 决定 GPU 内部格式:单通道浮点数据用
RedFormat + FloatType,整数量化数据则可用RedFormat + UnsignedByteType等; unpackAlignment = 1是数据纹理的保险配置;needsUpdate = true触发首次上传,之后每修改一次image.data也要置一次;- 体素空间对齐世界空间:
BoxGeometry平移到体中心,Shader 内以localPosition反推 UVW 采样坐标。
另一个示例 examples/webgl_texture3d_partialupdate.html 演示了动态更新:以 128³ 的 Uint8Array 生成 Perlin 云体纹理,并在动画循环中逐帧刷新数据,适合作为流体/烟雾类实时体数据的参考骨架。此外仓库中 webgl_volume_cloud.html、webgpu_volume_perlin.html、webgpu_volume_fire.html 等示例都围绕"3D 纹理 + 射线步进"这一模式展开,可对照阅读。
六、测试与类型系统佐证
单元测试 test/unit/src/textures/Data3DTexture.tests.js 验证了三条契约,可作为最小使用断言:
// 1. 继承关系
new Data3DTexture() instanceof Texture === true
// 2. 可实例化
const object = new Data3DTexture();
// 3. 类型标志
object.isData3DTexture === true
isData3DTexture 在类型系统里同样有对应:渲染器与节点编译器均以它为判据选择三维路径(前文 WebGLTextures.js 与 WGSLNodeBuilder.js 的引用),因此自定义材质中判断"传入的是不是体纹理"时应使用该标志,而不是 instanceof。
七、使用注意事项与最佳实践
- 尺寸与 mipmap:默认不生成 mipmap 且默认最近邻采样。若需要
LinearMipmapLinearFilter,应确保尺寸为 2 的幂或提供手工 mipmap,否则会触发 GL 警告或采样退化。 - format/type 必须与 TypedArray 匹配:
Float32Array配FloatType、Uint8Array配UnsignedByteType,字节数不一致会导致解包错位甚至上传失败(这是体数据最常见的 bug 来源)。 - 行对齐:
width × 每体素字节数不是 4 的倍数时,务必保持unpackAlignment = 1。 - W 方向环绕:跨 Z 边界循环采样需要
wrapR = RepeatWrapping并设置repeat;缺省的ClampToEdgeWrapping会把越界坐标钳到首/末体素。 - 内存与释放:体纹理显存占用为
width × height × depth × 通道字节数 × mipmap 层数,三维增长是立方级的(128³ 单通道即 2 MB 起),场景切换或对象销毁时应调用texture.dispose()。 - 拷贝语义:
copy()/clone()共享同一个TextureSource(即image数据引用),需要独立数据时应手动复制image.data并更新needsUpdate。 - 轴向约定:
flipY = false且体数据通常 Z 向上(示例中camera.up.set( 0, 0, 1 ); // In our data, z is up),在把外部格式(NRRD、体积 PNG 序列等)写入data时务必核对轴向,避免镜像。
八、小结
Data3DTexture 是 three.js 中面向体数据的纹理原语:构造参数(data/width/height/depth)定义体素网格,flipY、generateMipmaps、magFilter、minFilter、unpackAlignment 的默认值全部针对"原始数据不可乱采样、行对齐不可假设"做了保守覆写,wrapR 则补齐了基类缺失的 W 方向环绕。配合 RedFormat/FloatType 等 format+type 组合、needsUpdate 上传语义,以及 WebGLTextures.js 中 texStorage3D/texSubImage3D 的上传路径,它足以支撑医学影像、体积云、火焰等一切"射线步进 + 体素采样"的渲染需求。进一步阅读可对照 examples/webgl_texture3d.html、examples/webgl_texture3d_partialupdate.html 两个官方示例与 src/textures/Data3DTexture.js 的完整源码。
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