首页
/ three.js CanvasTexture 深度解析:从 HTML Canvas 创建动态 3D 纹理

three.js CanvasTexture 深度解析:从 HTML Canvas 创建动态 3D 纹理

2026-09-06 13:34:01作者:钟日瑜

本篇指南以 three.js 官方文档 CanvasTexture 页面为核心,系统讲解该类的继承关系、完整构造参数、needsUpdate 自动上传机制,并结合仓库源码 src/textures/CanvasTexture.js 与官方示例 examples/webgl_materials_texture_canvas.html,带你掌握如何在 Web 场景中把可交互的 2D 画布实时变为 3D 物体的纹理。读完后,你将能够独立创建动态 Canvas 纹理、正确驱动 GPU 更新,并理解其底层实现。

three.js 官方 Canvas 纹理示例运行效果:用户在白色画布上绘制,笔迹实时贴到旋转的立方体表面

CanvasTexture 是什么:继承关系与定位

CanvasTexture 用于从 HTML canvas 元素创建纹理。它与所有纹理共用的基类 Texture 几乎一致,唯一的区别是:由于 canvas 元素可以直接被 WebGL 用作渲染源,构造函数会立即将 Texture#needsUpdate 置为 true,触发一次 GPU 纹理上传。

继承链如下(来自官方文档 docs/pages/CanvasTexture.html.md):

EventDispatcher → Texture → CanvasTexture

从源码 src/textures/CanvasTexture.js 可以看到,整个类只有 45 行,实现极其精简:

class CanvasTexture extends Texture {

	constructor( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {

		super( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );

		/**
		 * This flag can be used for type testing.
		 *
		 * @type {boolean}
		 * @readonly
		 * @default true
		 */
		this.isCanvasTexture = true;

		this.needsUpdate = true;

	}

}

也就是说,CanvasTexture 的全部"魔法"只有两点:一是把 canvas 透传给 Texture 基类的 image 参数;二是在构造时强制标记纹理为待更新状态,使首帧渲染前纹理数据就能同步到 GPU。单元测试 test/unit/src/textures/CanvasTexture.tests.js 也验证了 CanvasTexture extends from TextureisCanvasTexture 恒为 true 这两条继承契约。

构造参数详解

官方文档定义的构造函数签名为:

new CanvasTexture(
	canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy
)

以下参数说明完整继承自官方文档,并结合 src/textures/Texture.js 基类构造函数中的实际默认值进行了核对:

参数 含义 默认值 说明
canvas HTML canvas 元素 无默认值 纹理数据来源,直接作为 WebGL 的纹理上传源
mapping 纹理映射方式 Texture.DEFAULT_MAPPING(即 UVMapping 静态常量定义于 Texture.js#L801
wrapS U 方向(水平)环绕方式 ClampToEdgeWrapping 可选 RepeatWrapping / MirroredRepeatWrapping
wrapT V 方向(垂直)环绕方式 ClampToEdgeWrapping 同上
magFilter 放大过滤方式(texel 覆盖多于一个像素时) LinearFilter 可选 NearestFilter
minFilter 缩小过滤方式(texel 覆盖少于一个像素时) LinearMipmapLinearFilter 依赖 mipmap
format 像素格式 RGBAFormat
type 数据类型 UnsignedByteType
anisotropy 各向异性过滤采样数 Texture.DEFAULT_ANISOTROPY(即 1 静态常量定义于 Texture.js#L810

这些常量(ClampToEdgeWrappingLinearFilterRGBAFormat 等)均定义在 src/constants.js 中,导入后直接作为枚举值传入即可。

几个值得注意的默认值语义:

  • ClampToEdgeWrapping:UV 超出 [0,1] 范围时钳制到边缘像素,而不会平铺。若想让 Canvas 纹理平铺,需显式设置 RepeatWrapping 并配合 repeat 属性。
  • LinearMipmapLinearFilter:默认最小化过滤使用三线性 mipmap,因此默认 generateMipmaps = true,渲染器会在上传时自动生成 mipmap 链;要求 canvas 宽高为 2 的幂以获得最佳兼容性(该约定是 WebGL1 时代的经验,仓库文档 src/textures/Texture.js 中对 anisotropy 的注释提到:值越大,倾斜视角下比基础 mipmap 更清晰,代价是更多纹理采样)。
  • anisotropy = 1:即关闭各向异性增强。对斜视角下的 Canvas 纹理(如文字、线稿),可从 renderer.capabilities.getMaxAnisotropy() 取得设备上限后调高该值。

isCanvasTexture 属性

文档列出的唯一专属属性:

  • .isCanvasTexture : boolean (readonly) —— 用于运行时类型判断的标志位,默认 true

其用途典型场景是:在遍历材质中混用的多种纹理时做类型分派,例如:

if ( material.map && material.map.isCanvasTexture ) {
	// 只有 Canvas 纹理才需要在 2D 上下书画完后重传
}

与基类的 isTexture 标志形成两级判断:isCanvasTexturetrue 必然意味着 isTexture 也为 true,反之不然。

needsUpdate 机制:CanvasTexture 的核心工作流

理解 CanvasTexture 的关键,在于理解 needsUpdate。基类 src/textures/Texture.js#L754-L763 中它被实现为只写的 setter:

set needsUpdate( value ) {

	if ( value === true ) {

		this.version ++;
		this.source.needsUpdate = true;

	}

}

其注释明确写道:将其设为 true 表示"引擎在下一次渲染时必须更新该纹理",这会触发纹理上传到 GPU,并确保纹理参数配置正确。两个动作的含义是:

  1. this.version ++:纹理版本号递增,渲染器检测到版本号变化即判定纹理数据已变更;
  2. this.source.needsUpdate = true:底层 TextureSource(封装实际的图像数据)同样被标记为待上传。

这解释了 CanvasTexture 的完整生命周期:

  1. 构造时this.needsUpdate = true 自动触发首次上传,无需手动干预——这正是它与 new Texture(canvas) 的本质区别;
  2. 运行时修改画布:Canvas 是"活"的 2D 上下文,用 canvas.getContext('2d') 重新绘制后,GPU 上的旧纹理不会自动同步,必须再次设置 texture.needsUpdate = true
  3. 数据源绑定Textureimage 属性通过 getter/setter 委托给 this.source.data(见 Texture.js#L414-L424),因此你拿到的就是同一个 canvas 引用,后续在 2D 上下文里的任何绘制都直接作用于纹理源。

实战:官方交互绘图示例逐行剖析

仓库自带一个非常直观的官方示例 examples/webgl_materials_texture_canvas.html:页面上叠加一个 128×128 的 <canvas>,用户在上面用指针画线,笔迹实时贴到一个旋转立方体的六个面上。

1. 页面结构

<canvas id="drawing-canvas" height="128" width="128"></canvas>

2. 初始化场景与材质

material = new THREE.MeshBasicMaterial();
mesh = new THREE.Mesh( new THREE.BoxGeometry( 200, 200, 200 ), material );

3. 建立画布并把 Canvas 纹理挂到材质

function setupCanvasDrawing() {

	// get canvas and context
	const drawingCanvas = document.getElementById( 'drawing-canvas' );
	const drawingContext = drawingCanvas.getContext( '2d' );

	// draw white background
	drawingContext.fillStyle = '#FFFFFF';
	drawingContext.fillRect( 0, 0, 128, 128 );

	// set canvas as material.map (this could be done to any map, bump, displacement etc.)
	material.map = new THREE.CanvasTexture( drawingCanvas );

	// ... 添加 pointerdown / pointermove / pointerup / pointerleave 监听

}

注意源码注释中的关键提示:"this could be done to any map, bump, displacement etc." —— CanvasTexture 不仅可用作 material.map,同样可以作为 bumpMapdisplacementMap 等任意纹理槽的数据源。

4. 绘图并触发纹理重传(核心中的核心)

function draw( drawContext, x, y ) {

	drawContext.moveTo( drawStartPos.x, drawStartPos.y );
	drawContext.strokeStyle = '#000000';
	drawContext.lineTo( x, y );
	drawContext.stroke();
	// reset drawing start position to current position.
	drawStartPos.set( x, y );
	// need to flag the map as needing updating.
	material.map.needsUpdate = true;

}

每一步 stroke() 之后立即执行 material.map.needsUpdate = true,对应前文所述的 version++source.needsUpdate = true 流程,下一次 renderer.render() 就会把最新的画布位图重新上传到 GPU。如果遗漏这一步,你会看到笔迹"画了但不上屏"这一最常见的 CanvasTexture 踩坑现象。

5. 渲染循环

function animate() {

	mesh.rotation.x += 0.01;
	mesh.rotation.y += 0.01;

	renderer.render( scene, camera );

}

该示例采用 importmap 方式引入 three 模块(../build/three.module.js),可以直接复制改造为自己的工程起点。

进阶要点与注意事项

  • 纹理尺寸/格式不可变Texture 基类注释(Texture.js#L26-L28)明确说明,纹理首次上传后,其尺寸、格式、类型不能更改;如需变更,应调用 dispose() 释放并新建一个纹理实例。对于 Canvas 纹理,这意味着动态修改 canvas 的 width/height 前需要先 dispose()
  • 资源释放dispose()Texture.js#L647-L657)会派发 dispose 事件,渲染器据此释放 GPU 资源;离开页面或移除材质时应调用。
  • UV 变换offset / repeat / rotation / center 等 UV 变换属性全部继承自 Texture,作用于 Canvas 纹理同样生效;transformUv() 中对 ClampToEdgeWrapping 的钳位逻辑(Texture.js#L680-L683)解释了默认包裹行为。
  • 与 TextureLoader 的分工CanvasTexture 面向"程序生成/实时绘制"的场景(文字贴图、动态图表、粒子纹理、Lottie 动画等);而 examples/webgl_loader_texture_lottie.html 一类示例展示了把动画帧画到 canvas 再经纹理输出的典型用法模式。仓库中共有 30 余个示例使用了 CanvasTexture,覆盖地形着色(webgl_geometry_terrain.html)、混合模式(webgl_materials_blending.html)、体积云(webgl_volume_cloud.html)等,可作为进阶参考。

小结

CanvasTexture 虽然源码不到 50 行,却把 two 个 2D 世界与 WebGL 3D 世界打通:构造函数中的 this.needsUpdate = true 负责首次自动上传,而每次修改画布后手动置位 needsUpdate 则负责后续增量同步。结合 src/textures/Texture.js 的默认参数、官方文档页 的 API 约定与 examples/webgl_materials_texture_canvas.html 的完整交互实现,你可以快速在任何 three.js 工程中实现"画布即纹理"的动态贴图能力。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388