首页
/ three.js Data3DTexture:用原始 TypedArray 数据构建三维纹理(体积纹理)完整指南

three.js Data3DTexture:用原始 TypedArray 数据构建三维纹理(体积纹理)完整指南

2026-09-06 17:41:28作者:殷蕙予

在 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 前会沿垂直轴翻转。基类 TextureflipY 默认为 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。基类默认为 trueData3DTexture 覆写为 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:一个体素覆盖多于一个像素(放大采样)时的采样方式。基类默认 LinearFilterData3DTexture 覆写为 NearestFilter
  • minFilter:一个体素覆盖少于一个像素(缩小采样)时的采样方式。基类默认 LinearMipmapLinearFilterData3DTexture 覆写为 NearestFilter

两者取值均为六种采样器常量(定义于 src/constants.js):NearestFilter | NearestMipmapNearestFilter | NearestMipmapLinearFilter | LinearFilter | LinearMipmapNearestFilter | LinearMipmapLinearFilter

为什么数据纹理默认最近邻?体素数据(尤其是量化后的密度场)做线性插值会在体素边界产生不属于任何体素的中间值。官方体积渲染示例中显式改回线性以换取平滑的等值面过渡:

texture.minFilter = texture.magFilter = THREE.LinearFilter;

3.6 .unpackAlignment(默认 1,覆写基类的 4)

指定内存中每行像素起始位置的对齐要求,合法值为 1(字节对齐)、248,对应 GL 的 UNPACK_ALIGNMENT。基类 Texture 默认 4Texture.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 | MirroredRepeatWrappingwrapRData3DTexture 独有的属性,基类 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 其余继承属性

除上述覆写项外,formattypecolorSpaceanisotropynormalizedrepeat(配合 RepeatWrapping 平铺)、offset 等全部继承自 Texture,用法与二维数据纹理一致。更新语义同样继承:修改 image.data 后需 texture.needsUpdate = true(内部会递增 version 并标记 source,见 Texture.js#L754-L763),不再需要时调用 texture.dispose() 释放 GPU 资源。

四、数据如何上传到 GPU:源码级流程

WebGLRenderer 上传体纹理的完整路径在 src/renderers/webgl/WebGLTextures.jsuploadTexture() 中:

  1. 目标选择L900-L905):
let textureType = _gl.TEXTURE_2D;

if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) textureType = _gl.TEXTURE_2D_ARRAY;
if ( texture.isData3DTexture ) textureType = _gl.TEXTURE_3D;
  1. pixelStore 设置L926-L932):flipYpremultiplyAlphaunpackAlignment 在此统一下发,这解释了上一节各覆写默认值为何直接决定解包行为。

  2. 三维上传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)路径同样识别 isData3DTextureWebGLRenderer.js#L3353),WebGPU 后端由 WebGPUTextureUtils 提供等价支持,因此 Data3DTexture 在 WebGL 与 WebGPU 两个渲染栈下都可用。

五、实战:用 NRRD 医学数据做体积渲染

官方示例 examples/webgl_texture3d.htmlData3DTexture 最完整的参考实现:用 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.htmlwebgpu_volume_perlin.htmlwebgpu_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.jsWGSLNodeBuilder.js 的引用),因此自定义材质中判断"传入的是不是体纹理"时应使用该标志,而不是 instanceof

七、使用注意事项与最佳实践

  1. 尺寸与 mipmap:默认不生成 mipmap 且默认最近邻采样。若需要 LinearMipmapLinearFilter,应确保尺寸为 2 的幂或提供手工 mipmap,否则会触发 GL 警告或采样退化。
  2. format/type 必须与 TypedArray 匹配Float32ArrayFloatTypeUint8ArrayUnsignedByteType,字节数不一致会导致解包错位甚至上传失败(这是体数据最常见的 bug 来源)。
  3. 行对齐width × 每体素字节数 不是 4 的倍数时,务必保持 unpackAlignment = 1
  4. W 方向环绕:跨 Z 边界循环采样需要 wrapR = RepeatWrapping 并设置 repeat;缺省的 ClampToEdgeWrapping 会把越界坐标钳到首/末体素。
  5. 内存与释放:体纹理显存占用为 width × height × depth × 通道字节数 × mipmap 层数,三维增长是立方级的(128³ 单通道即 2 MB 起),场景切换或对象销毁时应调用 texture.dispose()
  6. 拷贝语义copy()/clone() 共享同一个 TextureSource(即 image 数据引用),需要独立数据时应手动复制 image.data 并更新 needsUpdate
  7. 轴向约定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)定义体素网格,flipYgenerateMipmapsmagFilterminFilterunpackAlignment 的默认值全部针对"原始数据不可乱采样、行对齐不可假设"做了保守覆写,wrapR 则补齐了基类缺失的 W 方向环绕。配合 RedFormat/FloatType 等 format+type 组合、needsUpdate 上传语义,以及 WebGLTextures.jstexStorage3D/texSubImage3D 的上传路径,它足以支撑医学影像、体积云、火焰等一切"射线步进 + 体素采样"的渲染需求。进一步阅读可对照 examples/webgl_texture3d.htmlexamples/webgl_texture3d_partialupdate.html 两个官方示例与 src/textures/Data3DTexture.js 的完整源码。

登录后查看全文
热门项目推荐
相关项目推荐