three.js 纹理数据源详解:TextureSource(Source)类的设计动机、上传机制与序列化
2026/9/8 22:57:40 网站建设 项目流程

three.js 纹理数据源详解:TextureSource(Source)类的设计动机、上传机制与序列化

【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js

本文基于 three.js 仓库中的 Source API 文档 及其源码实现,系统讲解纹理数据源类(自 r186 起更名为TextureSource)的核心设计:它如何将纹理"数据定义"与"纹理定义"解耦,datadataReadyneedsUpdateversion各属性如何驱动 WebGL 纹理上传流程,以及getSizetoJSON两种方法的具体行为。读完本文,你将能够独立使用TextureSource让多个Texture实例共享同一份图像数据,并理解Texture#needsUpdate触发 GPU 上传的完整调用链。

1. 什么是纹理数据源:数据与纹理解耦

Source(现名TextureSource)表示一张纹理的数据源(data source)。其核心目的,用 API 文档 的原话说:

The main purpose of this class is to decouple the data definition from the texture definition so the same data can be used with multiple texture instances.

把"数据定义"与"纹理定义"解耦,使同一份数据可以被多个纹理实例复用。这在实践中非常常见:同一张法线图可能被两个材质以不同wrapSrepeatmagFilter使用;若各自持有独立数据副本,不仅浪费内存,数据更新时还需同步多次。通过共享一个TextureSource,GPU 端只需维护一份纹理对象,多个Texture实例只是挂在同一数据源上的不同"视图配置"。

从源码结构看,TextureTextureSource的关系在 src/textures/Texture.js 中直接体现:

this.source = new TextureSource( image );

new Texture( image )时,构造函数会把传入的图像封装成一个TextureSource作为texture.sourceTextureimage属性实际上是一个代理属性,读写都转发到 source:

// src/textures/Texture.js get image() { return this.source.data; } set image( value ) { this.source.data = value; }

因此texture.image = newImagetexture.source.data = newImage等价;而texture.image只是读取source.data

2. 命名沿革:Source 更名为 TextureSource

当前版本中,实际使用的类是TextureSource。原Source类自 r186 起被标记为弃用(deprecated),保留为TextureSource的子类用于向后兼容。在 src/textures/TextureSource.js 中可以看到这一设计:

/** * @deprecated since r186. Use {@link TextureSource} instead. `Source` has been renamed to `TextureSource`. */ class Source extends TextureSource { constructor( data = null ) { warnOnce( 'Source: "Source" has been renamed to "TextureSource". Please update your code to use "THREE.TextureSource" instead.' ); // @deprecated, r186 super( data ); this.isSource = true; } }

两点迁移要点:

  • 新代码应使用TextureSource;使用Source会触发一次warnOnce控制台警告(仅首次),实例上仍保留isSource标志供旧代码做类型测试;
  • 类型测试标志从isSource更名为isTextureSource,新代码应检查source.isTextureSource === true

3. 构造函数

new TextureSource( data : any )

构造一个新的纹理数据源。

参数:

  • data:纹理的数据定义,可以是HTMLImageElementHTMLCanvasElementHTMLVideoElementImageBitmapVideoFrame、带width/height的普通对象,或立方体贴图所需的图像数组。默认为null

构造函数实现位于 src/textures/TextureSource.js,逐字段初始化如下(与 API 文档的属性清单一一对应):

constructor( data = null ) { // 类型测试标志 this.isTextureSource = true; // 自增 ID(模块级计数器 _sourceId) Object.defineProperty( this, 'id', { value: _sourceId ++ } ); // 全局唯一 UUID this.uuid = generateUUID(); // 纹理数据定义 this.data = data; // 数据是否已就绪(默认 true) this.dataReady = true; // 版本号(默认 0) this.version = 0; }

注意id是通过Object.defineProperty以只读方式挂载的模块级自增计数(_sourceId从 0 开始),主要用于内部对象属性映射与调试标识。

4. 属性详解

.data : any

纹理的数据定义。渲染器最终上传到 GPU 的内容就是source.data。在 src/renderers/webgl/WebGLTextures.js 的uploadTexture流程中,上传前会先对texture.image(即source.data)执行resizeImageverifyColorSpace预处理。

.dataReady : boolean

默认true。该属性仅在needsUpdate被设为true时才有意义,用于精细控制纹理数据的处理方式:

dataReadyfalse时,引擎执行内存分配(如必要),但把数据实际传输到 GPU 内存。

源码级证据在 src/renderers/webgl/WebGLTextures.js:

const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); const dataReady = source.dataReady;

随后在各级 mipmap 上传分支中(例如 src/renderers/webgl/WebGLTextures.js):

if ( useTexStorage ) { if ( dataReady ) { state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); } } else { state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); }

典型场景是流式纹理或渐进加载:先置dataReady = false让引擎按数据尺寸分配 GPU 显存(texStorage2D),待真实像素数据(如网络加载的 mipmap 层)就位后再置回true并触发上传,避免渲染出空白纹理。

.id : number (readonly)

数据源 ID,模块级自增计数器,只读。

.isSource : boolean (readonly)/.isTextureSource : boolean (readonly)

类型测试标志,均为true。当前版本的主标志是isTextureSourceisSource仅由弃用的Source子类设置(见第 2 节)。

.needsUpdate : boolean

这是一个只写 setter(src/textures/TextureSource.js):

set needsUpdate( value ) { if ( value === true ) this.version ++; }

设为true时引擎为纹理分配内存(如必要),并在该 source 下次被使用时触发实际的 GPU 上传;设为false不会递减version。它本身不存储布尔值,而是通过递增version传递"需要更新"的信号。

.uuid : string (readonly)

数据源的 UUID,由generateUUID()生成。序列化(第 7 节)与ObjectLoader反序列化都以此为图像缓存的键。

.version : number (readonly)

0开始,记录needsUpdate被设为true的次数。渲染器正是靠比较version与上次记录的版本来决定是否重新上传,见 src/renderers/webgl/WebGLTextures.js:

if ( source.version !== sourceProperties.__version || forceUpload === true ) { // 执行纹理上传流程 }

上传完成后(src/renderers/webgl/WebGLTextures.js):

sourceProperties.__version = source.version;

由此形成"设置 → 检测差异 → 上传 → 记录版本"的幂等更新闭环。

5. 与 Texture 的联动:Texture#needsUpdate的传播

Texture自己也有一个needsUpdatesetter(src/textures/Texture.js):

set needsUpdate( value ) { if ( value === true ) { this.version ++; this.source.needsUpdate = true; } }

texture.needsUpdate = true同时做两件事:递增texture.version,并级联触发source.needsUpdate = true(进而递增source.version)。由于多个Texture可以共享同一个source,任一纹理声明更新,所有共享该数据源的纹理在下一帧渲染时都会看到更新后的 GPU 内容——这正是数据解耦带来的复用收益。Texturewidth/height/depth访问器同样直接委托给source.getSize(src/textures/Texture.js)。

6. 方法:getSize( target )

.getSize( target : Vector2 | Vector3 ) : Vector2 | Vector3

将数据源尺寸写入并返回给定的目标向量。实现位于 src/textures/TextureSource.js,按数据类型分支:

getSize( target ) { const data = this.data; if ( ( typeof HTMLVideoElement !== 'undefined' ) && ( data instanceof HTMLVideoElement ) ) { target.set( data.videoWidth, data.videoHeight, 0 ); } else if ( ( typeof VideoFrame !== 'undefined' ) && ( data instanceof VideoFrame ) ) { target.set( data.displayWidth, data.displayHeight, 0 ); } else if ( data !== null ) { target.set( data.width, data.height, data.depth || 0 ); } else { target.set( 0, 0, 0 ); } return target; }

要点:

  • 视频帧源(HTMLVideoElement/VideoFrame)使用videoWidth/videoHeight(或displayWidth/displayHeight),深度恒为 0;
  • 普通数据源直接读取width/height/depth(无depth时取 0);
  • datanull时返回(0, 0, 0)
  • 结果写入调用方提供的target(复用向量,避免频繁分配),并返回同一对象,支持链式调用。

7. 方法:toJSON( meta )序列化

.toJSON( meta : Object | string ) : Object

将数据源序列化为 JSON。meta是可选的序列化元信息对象(由Object3D.toJSON在序列化场景时传递);当metaundefined或字符串时,视为序列化根对象(root object)。

实现位于 src/textures/TextureSource.js,行为可归纳为三步:

  1. 去重:非根对象时,若meta.images[ this.uuid ]已存在则直接返回缓存项——同一TextureSource被多个纹理引用时,images表中只会出现一份记录;
  2. 输出结构{ uuid, url },其中urlserializeImage填充:
    • data为数组(立方体贴图)时,url是逐项序列化后的数组;
    • 否则url为单个序列化结果;
    • datanullurl保持空字符串;
  3. 缓存登记:非根对象时把输出登记进meta.images[ this.uuid ],供后续纹理的toJSON引用(Texture#toJSON输出中即包含image: this.source.toJSON( meta ).uuid,见 src/textures/Texture.js)。

serializeImage函数(src/textures/TextureSource.js)区分三类输入:

数据形式序列化结果
HTMLImageElement/HTMLCanvasElement/ImageBitmapImageUtils.getDataURL( image ),即 data URL 字符串
DataTexture的图像对象(带.data{ data: Array.from( image.data ), width, height, type: image.data.constructor.name }
其他不可识别数据输出警告Texture: Unable to serialize Texture.并返回空对象{}

反序列化端在 src/loaders/ObjectLoader.js 中消费这些记录:立方体贴图分支执行images[ image.uuid ] = new TextureSource( imageArray ),普通图像分支执行images[ image.uuid ] = new TextureSource( deserializedImage ),纹理对象随后通过 uuid 找回对应 source。API 文档中toJSON的 "See: ObjectLoader#parse" 即指这一对序列化/反序列化闭环。

8. 实战示例:多纹理共享同一数据源

import * as THREE from 'three'; const loader = new THREE.TextureLoader(); loader.load( 'textures/brick-wall.jpg', ( image ) => { // 手动构造数据源,供多个纹理共享 const source = new THREE.TextureSource( image ); const diffuseMap = new THREE.Texture( source ); diffuseMap.colorSpace = THREE.SRGBColorSpace; diffuseMap.wrapS = diffuseMap.wrapT = THREE.RepeatWrapping; diffuseMap.repeat.set( 4, 2 ); // 同一份 GPU 数据,不同采样参数 const normalMap = new THREE.Texture( source ); normalMap.wrapS = normalMap.wrapT = THREE.RepeatWrapping; normalMap.repeat.set( 4, 2 ); normalMap.magFilter = THREE.NearestFilter; material.map = diffuseMap; material.normalMap = normalMap; // 数据更新后只需触发一次,共享 source 的所有纹理同步生效 // (Texture#needsUpdate 会级联到 source.needsUpdate) diffuseMap.needsUpdate = true; } );

注意两个 API 细节:

  • new THREE.Texture( source )传入的 source 若是已有实例,Texture构造函数会直接复用(this.source = source.source,见 src/textures/Texture.js 附近逻辑),不会二次封装;
  • 使用旧类名new THREE.Source( image )虽仍可用,但会打印一次弃用警告,新代码请一律使用TextureSource

9. 关键文件索引

内容路径
TextureSource/ 弃用Source实现src/textures/TextureSource.js
Texture与 source 的绑定、needsUpdate传播src/textures/Texture.js
GPU 上传主流程(version对比、dataReady控制)src/renderers/webgl/WebGLTextures.js
反序列化重建TextureSourcesrc/loaders/ObjectLoader.js
原始 API 文档docs/pages/Source.html.md

10. 小结

  • TextureSource是纹理数据与纹理定义的解耦层:一个 source 可被多个Texture共享,GPU 只上传一次;
  • needsUpdate不存值,只通过递增version向渲染器发信号;渲染器以source.version !== sourceProperties.__version判断是否需要重新上传;
  • dataReady = false允许"只分配显存、不传数据",是流式/渐进纹理加载的关键开关;
  • toJSON/ObjectLoaderuuid为键完成 source 的去重序列化与重建,保证场景序列化时共享关系不丢失;
  • r186 起请使用TextureSourceisTextureSource,旧名Source/isSource仅为兼容保留。

【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询