three.js LineMaterial 深度指南:任意宽度的线框风格线条材质
LineMaterial 是 three.js 中用于绘制“加粗”线框风格几何体的专用材质。与受限于 GPU 固定管线、宽度恒为 1 像素的 LineBasicMaterial 不同,它支持任意线宽,并能切换为以世界单位定义尺寸。本指南将以 examples/jsm/lines/LineMaterial.js 为主线,完整讲解其继承关系、构造参数、全部属性语义,并结合 Line2、LineSegments2 与官方示例剖析它的着色器实现与使用注意点,读完后你可以在 WebGL 渲染器中独立搭建可变线宽的线条场景(含虚线、顶点色、MSAA 抗锯齿)。
一、LineMaterial 是什么
LineMaterial 是绘制线框风格几何体(wireframe-style geometries)的材质。它的官方定义位于 LineMaterial.js:
- 继承链:
EventDispatcher → Material → ShaderMaterial → LineMaterial,本质上是一个自定义着色器的ShaderMaterial。 - 不可替代的能力:与
LineBasicMaterial不同,它支持任意线宽(arbitrary line widths),并允许用世界单位(world units)而不是屏幕空间像素单位来指定线宽。 - 配套对象:它必须配合扩展几何体 LineSegments2(成对顶点线段集合)与 Line2(由 LineSegments2 扩展而来的连续折线)使用。
从源码可见其构造方式非常直接——复用内置 ShaderLib['line'] 着色器并开启裁剪支持:
// examples/jsm/lines/LineMaterial.js
class LineMaterial extends ShaderMaterial {
constructor( parameters ) {
super( {
type: 'LineMaterial',
uniforms: UniformsUtils.clone( ShaderLib[ 'line' ].uniforms ),
vertexShader: ShaderLib[ 'line' ].vertexShader,
fragmentShader: ShaderLib[ 'line' ].fragmentShader,
clipping: true // required for clipping support
} );
this.isLineMaterial = true;
this.setValues( parameters );
}
}
渲染后端限制(WebGL / WebGPU)
LineMaterial 只能配合 WebGLRenderer 使用。在使用 WebGPURenderer 时,应改用基于节点系统(TSL)实现的 Line2NodeMaterial(源码位于 src/materials/nodes/Line2NodeMaterial.js)。仓库在 examples/jsm/lines/webgpu/ 目录下提供了 WebGPU 版的 Line2.js、LineSegments2.js、Wireframe.js,导入路径相应变为 three/addons/lines/webgpu/...。
二、导入方式
LineMaterial 属于 addon(附加模块),不在 three.js 核心构建中,必须显式导入(安装时通过 three/addons/ 别名或直接指向 examples/jsm/ 目录):
import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
配套使用:
import { Line2 } from 'three/addons/lines/Line2.js';
import { LineGeometry } from 'three/addons/lines/LineGeometry.js';
也可以参考官方示例 examples/webgl_lines_fat.html 顶部的 importmap 配置方式:将 "three/addons/" 映射到 "./jsm/"。
三、与 LineSegments2 / Line2 的协作关系
要理解 LineMaterial,必须先理解它消费的数据。它不与普通 BufferGeometry 直接配合,而是面向实例化线段几何体工作:
- LineSegmentsGeometry 继承自
InstancedBufferGeometry。每个线段由一对 instanceStart / instanceEnd(以及可选的实例级颜色、距离)描述,材质内部把每条线段绘制成一块带“端帽扩展”的实例化四边形(quad)再进行计算。 - 该几何体每段顶点数必须是 6 的倍数(每组
xyz xyz定义一条线段的起点与终点)。 - LineSegments2 继承自
Mesh,用于持有上述几何体与LineMaterial;Line2 再扩展 LineSegments2,把独立的线段首尾相接组成折线(多段线)。LineSegments2.computeLineDistances()会为虚线渲染计算累计长度属性instanceDistanceStart/End。
一个最简组合:
const geometry = new LineGeometry();
geometry.setPositions( positions ); // 折线点列
geometry.setColors( colors ); // 可选顶点色
const material = new LineMaterial( { linewidth: 5, vertexColors: true } );
const line = new Line2( geometry, material );
line.computeLineDistances(); // 若使用虚线,必须调用
scene.add( line );
四、构造函数
new LineMaterial( parameters : Object )
构造一个新的线段材质。
parameters:一个对象,可包含一个或多个用于定义材质外观的属性。材质的任何属性(包括继承自 ShaderMaterial / Material 的属性)都可以在此传入;颜色值可传任何 Color#set 接受的类型(如十六进制数、'#ff0000'、CSS 颜色名或 THREE.Color 实例)。
const matLine = new LineMaterial( {
color: 0xffffff,
linewidth: 5, // worldUnits=false 时单位为像素
vertexColors: true,
dashed: false,
alphaToCoverage: true,
} );
构造函数内部通过 this.setValues( parameters ) 应用参数,因此上述大多数属性最终会映射到材质内部自定义 uniforms 上(见下表)。
五、属性详解
以下所有属性均有源码中的对应 getter/setter。需要特别指出:很多属性并非单纯的数值存储,而是直接作用于着色器编译期的 defines,切换它们会触发 needsUpdate = true,从而要求重新编译着色器。
.alphaToCoverage : boolean
是否启用 alphaToCoverage。启用后,在开启 MSAA 多重采样的情况下可以显著改善线条边缘的抗锯齿质量。
- 默认:
false - 覆盖(Overrides):
ShaderMaterial#alphaToCoverage
源码中它对应 defines.USE_ALPHA_TO_COVERAGE(见 LineMaterial.js)。片段着色器里开启后会改用 smoothstep( 0.5 - fwidth(...), 0.5 + fwidth(...), ... ) 的软边缘计算,并配合 fwidth 让覆盖采样产生渐变。注意该特性在 GPU 上依赖 MSAA,需要在创建 WebGLRenderer 时设置 antialias: true(官方示例正是如此),硬件不支持时会退回普通(硬边 discard)路径。屏幕空间(非 worldUnits)模式下源码注释还提到:在条件分支内做导数(derivative)会在部分硬件上产生瑕疵,因此做了规避处理。
.color : Color
材质颜色。默认 (1,1,1) 即白色。它实际是内部 uniform diffuse 的访问入口:
get color() { return this.uniforms.diffuse.value; }
set color( value ) { this.uniforms.diffuse.value = value; }
.dashed : boolean
线条是虚线还是实线。默认 false。
- 默认:
false
它对应 defines.USE_DASH(见 LineMaterial.js)。置为 true 会触发材质重编译,片段着色器中按“累计线长 + dashOffset 对 (dashSize+gapSize) 取模”来决定片段是绘制还是 discard(丢弃),同时端帽会被丢弃。注意:使用虚线前必须对几何体调用 computeLineDistances(),否则缺少 instanceDistanceStart/End 属性,虚线将无法正确排布(见第五节)。
.dashSize : number
单个短划线的长度(尺寸)。默认 1。
.gapSize : number
短划线之间的空隙长度。默认 0(即实线),设置虚线时需要大于 0。
.dashScale : number
虚线与空隙的整体缩放系数。默认 1。它实际上用于在顶点着色器把累计距离放大/缩小,从而不改变几何体本身而整体缩放虚线疏密。
.dashOffset : number
虚线循环(dash cycle)从何处开始,即整条虚线的相位偏移。默认 0。改变它可以让虚线的“段落起点”沿线条前后移动(常用于模拟流动的动画线)。片段着色器中通过 mod( vLineDistance + dashOffset, dashSize + gapSize ) > dashSize 丢弃空隙片段。
.linewidth : number
控制线条粗细:
worldUnits = false(默认)时,单位为 CSS 像素(屏幕空间);worldUnits = true时,单位为 世界单位(随距离衰减)。
默认 1。覆盖 ShaderMaterial#linewidth。对应 uniform linewidth,源码在修改时会对 this.uniforms.linewidth 做存在性检查后写值(LineMaterial.js)。
.opacity : number
整体透明度。默认 1。覆盖 ShaderMaterial#opacity。实际映射 uniform opacity,并作为最终 alpha 输出。若想半透明线条获得正确混合,请结合父类 Material 的 transparent 属性使用(LineMaterial 未自动开启透明)。
.resolution : Vector2
视口尺寸(屏幕像素)。必须保持为最新,屏幕空间渲染才能精确。LineSegments2.onBeforeRender 回调会对可见对象自动执行该更新:
// examples/jsm/lines/LineSegments2.js
onBeforeRender( renderer ) {
const uniforms = this.material.uniforms;
if ( uniforms && uniforms.resolution ) {
renderer.getViewport( _viewport );
this.material.uniforms.resolution.value.set( _viewport.z, _viewport.w );
}
}
因此常规渲染场景下你通常无需手动维护;但如果线条在首次渲染之前就被用于射线拾取(Raycaster),或未走正常渲染流程,就需要自行设置,否则拾取会因 resolution 为 0 而提前返回(见 LineSegments2.js)。注意该分辨率取的是当前 viewport(非整张画布),示例中的画中画(inset viewport)渲染场景也能自动适配。
.worldUnits : boolean
材质尺寸(线宽、虚线的间距)是否使用世界单位。默认 false(屏幕空间 / 像素)。
- 默认:
false
它对应 defines.WORLD_UNITS(LineMaterial.js),切换会导致着色器重编译。两条渲染路径差异较大:
- 屏幕空间(默认):在 NDC 空间把线段法线方向按
linewidth / resolution.y偏移,得到恒定像素宽度的线条,其着色器在顶点处对穿过相机近平面的线段做“裁剪修剪”,再结合端帽展开形成四边形。 - 世界单位:在观察空间根据
linewidth与世界方向构建worldUp / worldFwd正交基并偏移顶点,得到真实世界粗细的线(存在近大远小的透视衰减)。同时片段着色器改用“视线射线与线段最近距离”判定覆盖率,几何开销更高但物理感更强。
.isLineMaterial : boolean(只读)
类型测试标志,默认 true。用于在运行时快速区分该材质是否为 LineMaterial(相当于 instanceof 的轻量替代),源码中在构造器里直接硬编码为 true。
继承自 ShaderMaterial 的可复用特性
因为继承自 ShaderMaterial,顶点色、雾、裁剪平面、对数深度缓冲等能力默认已被内置着色器支持:
- 顶点色:设
vertexColors: true即可按实例给线段两端分别着色,对应源码中的instanceColorStart / instanceColorEnd属性; - 裁剪平面:构造器中显式设置了
clipping: true; - 雾:uniform 合并了
UniformsLib.common、UniformsLib.fog与UniformsLib.line(见 LineMaterial.js),所以场景雾效对线条生效。
六、内置着色器与 uniforms
LineMaterial 的着色器通过注册进全局 ShaderLib['line'] 的 GLSL 字符串定义。uniform 默认值集中在 UniformsLib.line(LineMaterial.js):
UniformsLib.line = {
worldUnits: { value: 1 },
linewidth: { value: 1 },
resolution: { value: new Vector2() },
dashOffset: { value: 0 },
dashScale: { value: 1 },
dashSize: { value: 1 },
gapSize: { value: 1 } // todo FIX - maybe change to totalSize
};
顶点着色器大致流程:将实例起止点变换到相机空间 → 对透视投影下跨越近平面(z<0 一侧)的线段做裁剪(trimSegmentAlpha 计算插值系数,见 LineMaterial.js)→ 投影到 NDC → 屏幕空间路径按线段垂直方向构建像素偏移、世界单位路径按世界正交基构建偏移 → 输出 gl_Position 并追加雾与裁剪处理。片段着色器负责端帽圆形化(vUv 上的圆形 discard)、虚线取模 discard 以及 worldUnits 下的“点到线段距离”软裁剪。理解这些有助于排查两类典型问题:
- 相机穿入线段内部或线段横跨近裁剪面时出现贯穿闪烁——这是 NDC 空间三角形被近平面撕裂导致的,源码中专门做了线段裁剪与深度重叠规避(将
clip.z重设为原线段 NDC 深度以免两个端帽深度排序冲突); - 开启
alphaToCoverage后仍有锯齿——通常是因为渲染器未开启 MSAA(antialias需在创建 renderer 时指定)。
七、典型应用示例
官方示例 examples/webgl_lines_fat.html 是对 LineMaterial 能力的完整演示:它通过 GeometryUtils.hilbert3D 生成希尔伯特曲线采样点,再用 CatmullRomCurve3 插值得到光滑折线,配合逐点 HSL 渐变的顶点色构造 LineGeometry:
const geometry = new LineGeometry();
geometry.setPositions( positions );
geometry.setColors( colors );
matLine = new LineMaterial( {
color: 0xffffff,
linewidth: 5, // worldUnits=false 时即像素宽度
vertexColors: true,
dashed: false,
alphaToCoverage: true,
} );
line = new Line2( geometry, matLine );
line.computeLineDistances();
scene.add( line );
同时该示例还渲染了一条使用 LineBasicMaterial(gl.LINE_STRIP 固定管线)的普通细线作为对照:LineBasicMaterial 在多数平台上无法控制线宽、始终为 1px 且抗锯齿效果差,而 LineMaterial 支持像素级宽度与 MSAA 软边。其 GUI 面板演示了三个核心参数的运行时调节方式:
// 世界单位 / 像素单位切换:切换后需要重新编译(源码会自动置 needsUpdate)
matLine.worldUnits = val;
matLine.needsUpdate = true;
// 线宽调节(像素模式 1~10,世界模式 0.1~0.5)
matLine.linewidth = val;
// 虚线与整体疏密、占空比
matLine.dashed = true;
matLine.dashScale = val; // 疏密
matLine.dashSize = 2; // 例如 2:1
matLine.gapSize = 1;
使用虚线时的完整最小步骤
结合源码(LineSegments2.computeLineDistances 在 LineSegments2.js 中实现累计长度并写回 instanceDistanceStart/End)与示例,要点如下:
- 构建
LineGeometry/LineSegmentsGeometry并setPositions; - 创建
LineMaterial时设置dashed: true与dashSize/gapSize/dashScale/dashOffset; - 创建
Line2/LineSegments2后调用computeLineDistances(); - 放入场景正常渲染。
八、拾取(Raycaster)注意事项
LineSegments2.raycast(LineSegments2.js)对两种模式分别实现了精确的“射线—线段”求交:
- 会依据材质
resolution计算世界空间包围球/包围盒的扩展边距,以包含屏幕空间线宽,避免误剔除; - 屏幕空间模式下
Raycaster.camera必须被设置,否则控制台会打印错误;同时若材质尚未渲染(resolution 为 0)会直接返回,因此先渲染一帧或用屏幕空间模式拾取前务必保证 resolution 有效; - 世界单位模式不依赖 camera,拾取边距直接取
linewidth * 0.5。
若线条常与鼠标交互,建议直接观看官方示例 examples/webgl_lines_fat_raycasting.html(该页面同时演示了 LineSegments2 实例级拾取与 Line2 折线拾取)。
九、性能与使用建议小结
- LineMaterial 基于实例化绘制,每段线段为一个实例化四边形,绘制大量短线段时比逐段生成 Mesh 更省状态切换;但每条线段的片元都在做额外的圆形端帽/距离计算,超大线宽 + 超长线段会放大片元开销。
- 需要“恒定像素宽”的标注线、边界线用默认屏幕空间模式即可;需要随透视缩小(如模拟实体金属丝、头发丝)用
worldUnits: true。 - MSAA 场景开启
alphaToCoverage: true可显著柔化边缘;在关闭 MSAA 的后处理链(如 SSAO、SSR 之后)中线条抗锯齿需另寻方案。 - WebGPURenderer 用户应改用 Line2NodeMaterial(src/materials/nodes/Line2NodeMaterial.js)与 examples/jsm/lines/webgpu/ 下的对象,两者 API 大体对齐但着色实现基于节点系统,渲染后端为 WebGPU。
如需深入了解配套几何体与 WebGPU 替代品,可继续阅读仓库中的 docs/pages/Line2NodeMaterial.html.md、examples/jsm/lines/Line2.js、examples/jsm/lines/LineSegmentsGeometry.js 与源码 examples/jsm/lines/LineMaterial.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 StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00