three.js PointUVNode 详解:基于 gl_PointCoord 的点精灵纹理采样坐标节点
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直接引入。
同时它与 AONode、AttributeNode、UVNode 等一样被归类为 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;
其用途与 isNode、isMaterial 等 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 内置材质中粒子贴图的采样路径高度同源,可互相印证:
- 在 points.glsl.js 顶点着色器 中,
#ifdef USE_POINTS_UV时会把顶点uv经uvTransform变换后输出到varying vUv; - 在 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.html、webgpu_skinning_points.html、webgpu_instance_points.html)虽然使用 PointsNodeMaterial,但这些示例面向的是粒子/点云效果而非纹理化点精灵,属于与上述约束一致的使用边界。在跨后端编写可移植 TSL 时,应将依赖 pointUV 的贴图化点精灵路径视为 WebGL 专属逻辑。
实战:在基于节点的点材质中给点精灵上纹理
PointUVNode 通常配合 PointsNodeMaterial(继承自 SpriteNodeMaterial → NodeMaterial)使用。典型思路是把 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.z(USE_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 快捷对象
pointUV(nodeImmutable单例,见 src/nodes/accessors/PointUVNode.js,通过 TSL.js 与 Three.TSL.js 汇出); - 仅限 WebGL 后端:WebGPU 点图元恒为 1 像素,无法用于贴图化点精灵,这是设计层面的硬性约束;
- 使用时应搭配 WebGL 渲染器、合适的
gl_PointSize控制与纹理环绕/uv 变换处理。
需要深入研究时,可直接阅读 PointUVNode 源码、其父类 Node、入口类 PointsNodeMaterial,以及展示点精灵贴图语义的内置着色器 chunk map_particle_fragment.glsl.js 与 points.glsl.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 StartedRust0627
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