首页
/ three.js PointUVNode 详解:基于 gl_PointCoord 的点精灵纹理采样坐标节点

three.js PointUVNode 详解:基于 gl_PointCoord 的点精灵纹理采样坐标节点

2026-09-07 09:08:37作者:邵娇湘

PointUVNode 是 three.js TSL(Three Shading Language)节点体系中专用于表示点(points)UV 坐标的访问器节点,它为基于 GL_POINTS 渲染的"点精灵(point sprite)"提供了在片元阶段采样纹理所需的 vec2 坐标。本文围绕 docs/pages/PointUVNode.html.md 展开,结合 源码实现 讲解其构造、唯一属性、TSL 快捷对象、底层 GLSL 语义以及 WebGL/WebGPU 后端限制,帮助你使用 pointUV 在自定义点材质中为每个点渲染贴图或圆点形状,并理解它与内置 PointsMaterial 贴图路径的对应关系。

PointUVNode 在 TSL 节点体系中的位置

在 three.js 的 Node/TSL 架构中,所有着色器逻辑都以节点对象表达。PointUVNode 的继承链为:

EventDispatcher → Node → PointUVNode

这一点在文档页头部明确标注。它继承自核心的 Node 基类,因此具备节点图构建、序列化(如时间线/缓存)、与其它节点连接参与生成着色器代码等能力。类声明位于 src/nodes/accessors/PointUVNode.js,其静态 type 返回字符串 'PointUVNode'

它通过两个集中导出通道对外暴露:

  • 节点树导出:在 src/nodes/Nodes.js 中以 export { default as PointUVNode } from './accessors/PointUVNode.js' 形式导出类本身;
  • TSL 函数导出:在 src/nodes/TSL.js 中将该 accessor 模块整体 export *,TSL 命名空间随后由 src/Three.TSL.js 汇出,开发者可从 three/tsl 直接引入。

同时它与 AONodeAttributeNodeUVNode 等一样被归类为 accessors(访问器)目录下的"内置只读值",代表某类管线内建变量在节点世界中的投影。

节点职责:表示点的 UV 坐标

文档对该节点的定位是一句话:"A node for representing the uv coordinates of points"。其核心语义是:

  • 面向使用 WebGLPoints/Points 渲染的点图元;
  • 输出类型为 vec2(构造器内 super( 'vec2' ) 完成类型声明);
  • 值并不来自顶点属性,而是来自点精灵片元内建变量 gl_PointCoord

底层 GLSL 生成逻辑

真正体现其实现意图的是 generate() 方法:

generate( /*builder*/ ) {
	return 'vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y )';
}

在 WebGL 中,当渲染模式为 GL_POINTS 时,每个点会被光栅化成一个以该点为中心的方形区域(大小由 gl_PointSize 决定),片元阶段内置变量 gl_PointCoord 表示片元在该点方形区域内的归一化坐标,取值范围 [0, 1]。由于纹理坐标 v 轴向上、而 gl_PointCoord.y 向下增长,因此在采样 map 前需要翻转 y 轴——这正是 1.0 - gl_PointCoord.y 的来源。

从源码结构可以推断:PointUVNode 实际上把 WebGL 内置点精灵机制封装成了可在 TSL 中直接引用的节点,使基于节点的材质不必手写 GLSL 字符串即可接入点纹理采样。

构造器与属性

new PointUVNode()

构造一个新的 PointUVNode 实例。它不需要任何参数,类型固定为 vec2。与节点树的通用实例化方式一致,一般通过直接 new PointUVNode() 使用;更多场景下则直接使用下方 TSL 快捷对象。

.isPointUVNode : boolean(只读)

用于类型测试的标记位,默认值为 true,在构造器中完成赋值:

this.isPointUVNode = true;

其用途与 isNodeisMaterial 等 three.js 全局"鸭子类型"标记一致——当你在外部拿到一个未知节点对象,需要判断它是否具备"点 UV 坐标"语义时,可通过 node.isPointUVNode === true 快速判别,而不必使用 instanceof(后者在多个 three.js 副本或多版本混用时可能失效)。

TSL 快捷对象:pointUV

除了类本身,PointUVNode 还以"只读不可变单例节点"的形式暴露了一个 TSL 对象:

export const pointUV = /*@__PURE__*/ nodeImmutable( PointUVNode );

nodeImmutable 会为该类缓存并返回唯一实例,因此任何位置引用 pointUV 得到的都是同一节点,保证节点图缓存命中与内存效率;同时实例被标记为不可变,避免被意外修改污染共享状态。

docs/pages/TSL.html.md 的 TSL 对象总表中,它被描述为 .pointUV : PointUVNode (constant)——"TSL object that represents the uv coordinates of points",是标准 TSL 推荐用法。实际代码中可直接以 import { pointUV } from 'three/tsl' 引入。

与内置 PointsMaterial 贴图管线的同源性

pointUV 并非孤立的 API——它与 WebGL 内置材质中粒子贴图的采样路径高度同源,可互相印证:

  1. points.glsl.js 顶点着色器 中,#ifdef USE_POINTS_UV 时会把顶点 uvuvTransform 变换后输出到 varying vUv
  2. map_particle_fragment.glsl.js 片元片段 中,当没有 USE_POINTS_UV(即点精灵没有逐点 uv 属性)时,会直接使用:
vec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;

可以看到其核心坐标计算 ( gl_PointCoord.x, 1.0 - gl_PointCoord.y ) 与 PointUVNode 的 generate() 输出完全一致。也就是说:内置 PointsMaterial 在 WebGL 上给点贴纹理时,默认就是基于 gl_PointCoord 构造这个"虚拟 uv";而 pointUV 正是把这个行为提升为可在节点材质中自由复用/再加工的一等公民节点。

USE_POINTS_UV 宏由 WebGLProgram.js 依据 parameters.pointsUvs 注入,USE_MAP/USE_ALPHAMAP 宏控制最终是否执行 texture2D( map, uv ) / alphaMap 采样——阅读这些文件可以帮助理解 PointUVNode 与上述内置路径之间的边界:当使用基于节点的材质(如 NodeMaterial 通过 colorNode/diffuseColor 通道组装着色器)时,pointUV 即为你在节点层接续这一语义的入口。

WebGL/WebGPU 后端限制(重要约束)

文档明确强调:

Can only be used with a WebGL backend. In WebGPU, point primitives always have the size of one pixel and can thus can't be used as sprite-like objects that display textures.

这是使用 pointUV 前必须先理解的限制,它由后端图元能力决定:

  • WebGL 后端GL_POINTS 点精灵支持 gl_PointSize 缩放与 gl_PointCoord 插值,点可以膨胀为矩形区域并采样纹理,因而 pointUV 有效;
  • WebGPU 后端:点图元被光栅化为固定 1 像素,无法像精灵一样"放大 + 贴图",因此无法作为承载纹理采样的坐标来源,pointUV 不适用。

仓库中大量 WebGPU 点示例(如 webgpu_compute_points.htmlwebgpu_skinning_points.htmlwebgpu_instance_points.html)虽然使用 PointsNodeMaterial,但这些示例面向的是粒子/点云效果而非纹理化点精灵,属于与上述约束一致的使用边界。在跨后端编写可移植 TSL 时,应将依赖 pointUV 的贴图化点精灵路径视为 WebGL 专属逻辑。

实战:在基于节点的点材质中给点精灵上纹理

PointUVNode 通常配合 PointsNodeMaterial(继承自 SpriteNodeMaterialNodeMaterial)使用。典型思路是把 pointUV 作为采样坐标接入颜色通道节点,再通过 WebGL 渲染器输出到 Points 对象上。一个可运行的参考写法如下:

import * as THREE from 'three';
import { texture, pointUV } from 'three/tsl';

// 圆形点贴图(半透明圆盘)
const dotTexture = new THREE.TextureLoader().load( 'dot.png' );

const material = new THREE.PointsNodeMaterial();
material.colorNode = texture( dotTexture, pointUV ); // 以点内归一化坐标采样

const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
	'position',
	new THREE.Float32BufferAttribute( [ ...positions ], 3 )
);

const points = new THREE.Points( geometry, material );
scene.add( points );

几点实战提示:

  • 配合 gl_PointSize 控制可见尺寸:在 WebGL 中 gl_PointSize 决定点精灵的像素边长。内置着色器在 points.glsl.js 中通过 uniform float size 与透视衰减 scale / -mvPosition.zUSE_SIZEATTENUATION)计算它;在节点材质中可参考 TSL 的 pointWidth 等属性或为材质设置对应的 size 节点,否则 1 像素点无法呈现贴图细节。
  • uv 变换的一致性:内置路径会在采样前应用 uvTransform(纹理重复/偏移/旋转)。若你自行用 pointUV 采样,需要按需自行串联 uvTransform 类逻辑,或在着色器节点中显式处理纹理环绕参数。
  • 圆形裁剪的替代方案:许多点效果并不需要贴图资源,而是直接在片元阶段用 length( gl_PointCoord - vec2( 0.5 ) ) > 0.5 丢弃边缘来得到圆点(与 webgl_points_waves.html 等示例中手写 GLSL 的思路一致)。pointUV 提供的正是这类计算所需的归一化坐标。

如果你希望在更底层的自定义 ShaderMaterial 中验证同一语义,可对照 map_particle_fragment.glsl.js 的 GLSL 片段手工复制 uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) 这一表达式。

小结

  • PointUVNode 是 three.js 中表示"点 UV 坐标"的 vec2 访问器节点,继承链为 EventDispatcher → Node → PointUVNode
  • generate() 将节点翻译为 vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y ),与内置 PointsMaterial 粒子贴图路径 map_particle_fragment.glsl.js 中基于 gl_PointCoord 的采样逻辑同源;
  • 构造器 new PointUVNode() 无参数,只读标记 isPointUVNode === true 用于类型检测;
  • 正式使用推荐 TSL 快捷对象 pointUVnodeImmutable 单例,见 src/nodes/accessors/PointUVNode.js,通过 TSL.jsThree.TSL.js 汇出);
  • 仅限 WebGL 后端:WebGPU 点图元恒为 1 像素,无法用于贴图化点精灵,这是设计层面的硬性约束;
  • 使用时应搭配 WebGL 渲染器、合适的 gl_PointSize 控制与纹理环绕/uv 变换处理。

需要深入研究时,可直接阅读 PointUVNode 源码、其父类 Node、入口类 PointsNodeMaterial,以及展示点精灵贴图语义的内置着色器 chunk map_particle_fragment.glsl.jspoints.glsl.js

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