首页
/ three.js LineDashedNodeMaterial 指南:虚线节点材质的属性覆盖、TSL 接入与着色器变体原理

three.js LineDashedNodeMaterial 指南:虚线节点材质的属性覆盖、TSL 接入与着色器变体原理

2026-09-07 23:32:10作者:俞予舒Fleming

LineDashedNodeMaterial 是 three.js 中 LineDashedMaterial(经典虚线线段材质)的 NodeMaterial 版本,它把虚线渲染所需的 dash 尺寸、gap 尺寸、缩放与偏移量全部暴露为可编程节点属性,可直接融入 TSL(Three Shading Language)着色流程。本文以该类的 官方 API 文档 为骨架,结合其在 src/materials/nodes/LineDashedNodeMaterial.js 中的完整实现与相关节点源码,讲解其构造方式、每个属性的语义与默认值、setupVariants() 背后真实的虚线剔除逻辑,并给出可运行的实战示例,帮助读者在使用 WebGL/WebGPU 渲染器绘制虚线几何体时灵活实现动态虚线、程序化虚线等效果。

一、类定位:继承链与它在 three.js 中的角色

从文档开头的继承链可以看出:

EventDispatcher → Material → NodeMaterial → LineDashedNodeMaterial

即它继承自 NodeMaterial,而 NodeMaterial 是 three.js 节点体系的核心材质基类。因此 LineDashedNodeMaterial 拥有节点材质的一切能力:colorNodeopacityNode 等可编程属性、通过 .setup()/.setupVariants() 参与着色器编译等,同时也必须满足普通线段材质的使用前提(例如几何体需要携带逐顶点距离数据)。

在实现层面,源码用一个纯函数生成的经典材质实例作为默认值模板

const _defaultValues = /*@__PURE__*/ new LineDashedMaterial();
// src/materials/nodes/LineDashedNodeMaterial.js 第 9 行

构造函数通过 this.setDefaultValues( _defaultValues )LineDashedMaterial 的默认参数scale = 1dashSize = 3gapSize = 1)继承到节点材质上,再经由 this.setValues( parameters ) 应用用户传入的构造参数。这样既保证了与传统材质参数写法完全兼容(colortransparentopacity 等直接可用),又在此之上叠加了专属的节点属性。

二、构造函数与 parameters

new LineDashedNodeMaterial( parameters : Object )

构造一个新的虚线节点材质。parameters 是可选的配置对象,其内容可以是材质继承链上任意可写属性(包括 NodeMaterialMaterial 的属性),取值规则与经典材质一致(颜色可用任何 Color.set() 接受的类型)。

典型用法与传统材质几乎无差别:

const material = new THREE.LineDashedNodeMaterial( {
	color: 0xffffff,
	transparent: true,
	scale: 1,
	dashSize: 3,
	gapSize: 1,
} );

// 虚线依赖逐顶点 lineDistance,需要先计算线段距离
const geometry = new THREE.BufferGeometry().setFromPoints( points );
geometry.computeLineDistances();
const line = new THREE.Line( geometry, material );

仓库示例 examples/webgpu_lines_fat.html 中正是用构造参数方式创建了 LineDashedNodeMaterial,并配合 scale: 2, dashSize: 1, gapSize: 1computeLineDistances() 使用。

三、属性全景:经典属性 + 六个可编程节点属性

经典属性(来自 LineDashedMaterial 默认值)

属性 类型 默认值 含义
.scale number 1 虚线整体缩放系数,可理解为“世界单位 ↔ 线段距离”的换算比例
.dashSize number 3 单个虚线段(实线笔画)的长度
.gapSize number 1 相邻虚线段之间空白间隔的长度
.dashOffset number 0 虚线起始位置的偏移量

上述默认值定义于 LineDashedMaterial 构造函数,并通过默认值模板被节点材质继承。

专属节点属性(文档核心内容)

这六个属性中,dashOffset 为普通数字,其余四个 *Node 属性是本材质区别于经典材质的核心——它们允许用节点来完全覆盖(overwrite)对应数字属性的推导值

.dashOffset : number

虚线起始偏移量,默认 0。它是一条普通数值属性;当你不显式设置 offsetNode 时,MaterialNode源码实现 中会判断该值是否存在:

node = ( material.dashOffset ) ? this.getFloat( scope ) : float( 0 );

.dashOffset 为 0 时直接使用常量 float(0),为真值时才读取材质属性,从而避免无意义的常量分支。

.offsetNode : Node<float>(默认 null

虚线偏移量的节点版本。默认虚线偏移量来自 .dashOffset 属性;设置该节点后会完全覆盖 .dashOffset 的推导值。若你不想整体覆盖、只想在既有值基础上做修改,应改用 TSL 访问器 materialLineDashOffset(见下文)。

.dashScaleNode : Node<float>(默认 null

虚线缩放系数的节点版本。默认推导自 .scale 属性;设置后以节点结果覆盖默认 scale。覆盖失败——即不打算替换而是想增量修改——时应使用 materialLineScale

.dashSizeNode : Node<float>(默认 null

虚线笔画的节点版本,覆盖默认的 .dashSize 推导值;增量修改请用 materialLineDashSize

.gapSizeNode : Node<float>(默认 null

虚线间隔的节点版本,覆盖默认的 .gapSize 推导值;增量修改请用 materialLineGapSize

.isLineDashedNodeMaterial : boolean(只读)

类型判断标志,固定为 true,用于在渲染管线或业务代码中快速识别本材质类型(与 isLineDashedMaterial 在同名经典材质中的角色一致)。

四个 *Node 属性在源码中均被定义为 null 初始值(见 构造函数),语义非常清晰:为 null 即“回退到经典数字属性”,为非 null 即“以节点结果接管”。 这种“数字属性兜底 + 节点覆盖”的双轨设计,保证了老代码无需改动即可获得节点化能力。

四、方法 setupVariants( builder : NodeBuilder ) 与虚线剔除的源码原理

setupVariants() 在材质编译阶段负责搭建虚线专属的着色器变量与剔除逻辑。它覆盖了 NodeMaterial.setupVariants,节点构建器 builder 在 WebGL/WebGPU 后端编译时都会调用它。源码完整逻辑如下(src/materials/nodes/LineDashedNodeMaterial.js):

setupVariants( /* builder */ ) {
	const offsetNode = this.offsetNode ? float( this.offsetNode ) : materialLineDashOffset;
	const dashScaleNode = this.dashScaleNode ? float( this.dashScaleNode ) : materialLineScale;
	const dashSizeNode = this.dashSizeNode ? float( this.dashSizeNode ) : materialLineDashSize;
	const gapSizeNode = this.gapSizeNode ? float( this.gapSizeNode ) : materialLineGapSize;

	dashSize.assign( dashSizeNode );
	gapSize.assign( gapSizeNode );

	const vLineDistance = varying( attribute( 'lineDistance' ).mul( dashScaleNode ) );
	const vLineDistanceOffset = offsetNode ? vLineDistance.add( offsetNode ) : vLineDistance;

	vLineDistanceOffset.mod( dashSize.add( gapSize ) ).greaterThan( dashSize ).discard();
}

这段代码揭示了虚线的完整工作机制:

  1. 四路取值回退:每个节点属性若为 null,就落到对应的 TSL 材质访问器(materialLineDashOffset / materialLineScale / materialLineDashSize / materialLineGapSize),这些访问器由 MaterialNode 静态作用域常量 定义并导出(src/nodes/accessors/MaterialNode.js),最终读取材质上同名数字属性。从而“节点优先、属性兜底”在编译期即可确定。
  2. 写入着色器全局变量dashSizegapSize 是 three.js 在 PropertyNode 中导出的 TSL 全局变量,即 shader 中的同名变量;.assign() 将解析好的节点值写进去,供后续片元计算使用。
  3. 距离插值lineDistance 是几何体上由 computeLineDistances() 生成的逐顶点距离属性(顶点着色器中按视角插值,因此用 varying() 声明),乘上 dashScaleNode 后得到“考虑了整体缩放的世界距离”。
  4. 可选的偏移:若存在 offsetNode(无论来自 .offsetNode 还是默认的 materialLineDashOffset 常量 float(0)),则把插值距离整体平移。
  5. 核心剔除公式(lineDistance % (dashSize + gapSize)) > dashSizediscard()。这意味着一个虚线周期长度为 dashSize + gapSize,只有落在前 dashSize 范围内的片元才被保留——实线笔画之外的部分被直接丢弃,产生虚线观感。

同一套“周期取模 + discard”思路也被更复杂的宽线段节点材质 Line2NodeMaterial 采用,可见它是 three.js 节点化虚线渲染的通用手法。

五、关键配套 TSL 访问器:增量修改而非覆盖

上一节提到四个配套 TSL 访问器,它们在 src/nodes/accessors/MaterialNode.js 中被定义,同时从 src/Three.TSL.js 顶层导出,使用时直接 import:

import { materialLineScale, materialLineDashSize, materialLineGapSize, materialLineDashOffset } from 'three/tsl';

它们的差别需要重点区分:

  • 设置 .dashSizeNode.gapSizeNode.dashScaleNode.offsetNode 属于整体替换(overwrite)——材质原本的数值属性被忽略;
  • 引用 materialLineDashSizematerialLineGapSizematerialLineScalematerialLineDashOffset 则把当前材质属性值当作可读信号引入节点图,你可以在其基础上继续运算,属于增量修改(modify)。

例如“在不改材质 dashSize 数值的前提下,让虚线笔画随时间波动”,正确写法是:

import * as THREE from 'three';
import { materialLineDashSize, sin, timerLocal } from 'three/tsl';

const material = new THREE.LineDashedNodeMaterial( { dashSize: 3, gapSize: 2, scale: 1 } );

// 增量:在材质自带 dashSize(3) 的基础上叠加正弦波动
material.dashSizeNode = materialLineDashSize.mul( sin( timerLocal() ).mul( 0.5 ).add( 1 ) );

如果这里错误地直接写 material.dashSizeNode = someNode,则材质上的 dashSize: 3 将被彻底覆盖,这就是文档反复强调“不想覆盖而是修改就用 materialLine* 系列”的原因。

六、实战示例:从静态虚线到动态 TSL 虚线

1. 静态虚线(兼容传统写法)

import * as THREE from 'three';

const points = [
	new THREE.Vector3( - 10, 0, 0 ),
	new THREE.Vector3( 10, 0, 0 ),
];
const geometry = new THREE.BufferGeometry().setFromPoints( points );
geometry.computeLineDistances(); // 必须:生成 lineDistance 属性

const material = new THREE.LineDashedNodeMaterial( {
	color: 0x00ffff,
	transparent: true,
	scale: 2,
	dashSize: 2,
	gapSize: 1,
} );

const line = new THREE.Line( geometry, material );
scene.add( line );

要点:虚线材质(无论是经典版还是节点版)依赖顶点着色器中的 lineDistance 属性,因此创建 Line 后务必调用 geometry.computeLineDistances(),否则所有片元距离为 0,整条线会被当成一个周期而显示异常。

2. 节点驱动的动态虚线

利用 *Node 属性与 TSL,可以将任何计算源接入虚线参数,实现动画或程序化纹理一样的动态效果:

import * as THREE from 'three';
import { float, uv, materialLineDashSize, materialLineGapSize } from 'three/tsl';

const material = new THREE.LineDashedNodeMaterial();
material.dashSizeNode = materialLineDashSize.mul( 2 );        // 加长笔画
material.gapSizeNode = materialLineGapSize;                   // 保持默认 gap
material.offsetNode = uv().x.mul( 4 );                        // 让偏移随 UV 变化,形成流动感
material.dashScaleNode = float( 1.5 );                        // 固定整体缩放

这种方式下,setupVariants() 编译出的 shader 会直接使用你传入的节点表达式替换默认的 dashSizegapSize 与偏移量,虚线外观完全由 TSL 图决定。

七、使用建议与注意事项

  • 虚线参数均为长度单位dashSize/gapSize 的观感会随相机距离与 scale 变化,调整时建议同时考虑 .scale,避免在世界空间和屏幕空间换算上出现偏差。
  • 半透明虚线记得开 transparentLineDashedMaterial 默认不透明,与经典材质一致,需要透明度时必须设置 transparent: trueopacity
  • 类型检测:运行时用 material.isLineDashedNodeMaterial === true 判断即可,无需 instanceof 或字符串比对。
  • 只覆盖不修改 vs. 增量修改:需要完全接管某参数时设置对应的 *Node 属性;需要继承材质数值继续计算时使用 materialLine* 访问器(materialLineScalematerialLineDashSizematerialLineGapSizematerialLineDashOffset)。这是最容易出错、也最值得记住的设计要点。
  • 渲染后端通用LineDashedNodeMaterial 基于 NodeMaterial,同时支持 WebGL 与 WebGPU 渲染管线;只要几何体带有 lineDistance 属性即可正常工作(仓库中 webgpu_lines_fat.html 即为 WebGPU 侧的实践参考)。

相关资源

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 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
531
594
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
516
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388