You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Three.js r91:如何使用新linewidth属性加宽线条?

嘿,我太懂你这个烦恼了——Three.js里LineBasicMaterial的linewidth简直是个摆设,想搞出那种漫画风的粗线框轮廓,还得内部透明,确实卡壳。我给你分享几个亲测有效的解决方案,按需选就行:

方法1:使用three-meshline库(最推荐)

这个库就是专门解决Three.js线条宽度限制的,支持任意粗细的线条,还能玩各种自定义效果,上手也简单。

  • 先安装依赖:
    npm install three-meshline

  • 代码示例:

import * as THREE from 'three';
import { MeshLine, MeshLineMaterial } from 'three-meshline';

// 自定义你的轮廓顶点,这里用一个正方形做例子
const points = [
  new THREE.Vector3(-1, 1, 0),
  new THREE.Vector3(1, 1, 0),
  new THREE.Vector3(1, -1, 0),
  new THREE.Vector3(-1, -1, 0),
  new THREE.Vector3(-1, 1, 0) // 闭合轮廓
];

// 创建MeshLine对象,传入顶点数据
const line = new MeshLine();
line.setPoints(points);

// 配置材质:设置粗度、颜色,开启透明
const material = new MeshLineMaterial({
  color: 0x000000, // 轮廓颜色
  lineWidth: 10, // 这里的宽度完全生效!想设多粗都行
  transparent: true,
  opacity: 1,
  depthWrite: false, // 避免和其他物体的遮挡问题
  side: THREE.DoubleSide // 确保线条两面都能渲染
});

// 把线条网格加到场景里
const mesh = new THREE.Mesh(line, material);
scene.add(mesh);

优点:线条粗细完全可控,支持曲线、动画,边缘平滑;缺点:需要引入一个轻量第三方库,不过完全不麻烦。

方法2:自定义ShaderMaterial(灵活无依赖)

如果不想加第三方库,自己写个简单的着色器就能搞定核心问题——绕开WebGL对linewidth的限制,在屏幕空间计算线条宽度。

  • 代码示例:
import * as THREE from 'three';

// 顶点着色器:处理线条偏移实现粗度
const vertexShader = `
  attribute vec3 position;
  attribute vec3 previousPosition;
  attribute vec3 nextPosition;
  attribute float side;

  uniform float lineWidth;
  uniform mat4 projectionMatrix;
  uniform mat4 modelViewMatrix;

  vec2 getScreenSpace(vec3 p) {
    vec4 clipPos = projectionMatrix * modelViewMatrix * vec4(p, 1.0);
    return clipPos.xy / clipPos.w * 0.5 + 0.5;
  }

  void main() {
    vec2 screenPos = getScreenSpace(position);
    vec2 prevScreenPos = getScreenSpace(previousPosition);
    vec2 nextScreenPos = getScreenSpace(nextPosition);

    float aspect = projectionMatrix[0][0] / projectionMatrix[1][1];

    vec2 dir;
    if (prevScreenPos == screenPos) {
      dir = normalize(nextScreenPos - screenPos);
    } else if (nextScreenPos == screenPos) {
      dir = normalize(screenPos - prevScreenPos);
    } else {
      vec2 prevDir = normalize(screenPos - prevScreenPos);
      vec2 nextDir = normalize(nextScreenPos - screenPos);
      dir = normalize(prevDir + nextDir);
    }

    vec2 normal = vec2(-dir.y, dir.x) * aspect;
    normal = normalize(normal);

    vec3 offset = vec3(normal * lineWidth * side * 0.001, 0.0);
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position + offset, 1.0);
  }
`;

// 片元着色器:设置线条颜色和透明度
const fragmentShader = `
  uniform vec3 color;
  uniform float opacity;

  void main() {
    gl_FragColor = vec4(color, opacity);
  }
`;

// 工具函数:处理顶点数据,添加前后顶点和侧边属性
function createThickLineGeometry(points) {
  const geometry = new THREE.BufferGeometry();
  const positions = [];
  const previousPositions = [];
  const nextPositions = [];
  const sides = [];

  for (let i = 0; i < points.length; i++) {
    const prev = points[i === 0 ? points.length - 1 : i - 1];
    const curr = points[i];
    const next = points[i === points.length - 1 ? 0 : i + 1];

    // 给每个顶点生成左右两个偏移点
    for (let side of [-1, 1]) {
      positions.push(curr.x, curr.y, curr.z);
      previousPositions.push(prev.x, prev.y, prev.z);
      nextPositions.push(next.x, next.y, next.z);
      sides.push(side);
    }
  }

  geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
  geometry.setAttribute('previousPosition', new THREE.Float32BufferAttribute(previousPositions, 3));
  geometry.setAttribute('nextPosition', new THREE.Float32BufferAttribute(nextPositions, 3));
  geometry.setAttribute('side', new THREE.Float32BufferAttribute(sides, 1));

  return geometry;
}

// 使用示例
const points = [
  new THREE.Vector3(-1, 1, 0),
  new THREE.Vector3(1, 1, 0),
  new THREE.Vector3(1, -1, 0),
  new THREE.Vector3(-1, -1, 0),
  new THREE.Vector3(-1, 1, 0)
];

const geometry = createThickLineGeometry(points);
const material = new THREE.ShaderMaterial({
  vertexShader,
  fragmentShader,
  uniforms: {
    lineWidth: { value: 20 }, // 调整这个值控制线条粗细
    color: { value: new THREE.Color(0x000000) },
    opacity: { value: 1 }
  },
  transparent: true,
  side: THREE.DoubleSide
});

const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

优点:完全自定义,不需要任何第三方依赖;缺点:需要懂一点GLSL基础,处理顶点数据的步骤稍微繁琐。

方法3:多线条叠加(快速临时方案)

如果你的场景很简单,不想折腾库或者着色器,这个“笨办法”也能应急——创建多条稍微偏移的线条,叠加在一起看起来就变粗了。

  • 代码示例:
import * as THREE from 'three';

function createThickLine(points, targetWidth, color) {
  const lineCount = Math.floor(targetWidth / 2); // 要叠加的线条数量
  const baseGeometry = new THREE.BufferGeometry().setFromPoints(points);
  
  // 先加一条基础线条
  const baseMaterial = new THREE.LineBasicMaterial({ color, transparent: true, opacity: 1 });
  const baseLine = new THREE.LineLoop(baseGeometry, baseMaterial);
  scene.add(baseLine);

  // 叠加偏移的线条,这里简单做斜向偏移,复杂形状需要计算法线偏移
  for (let i = 1; i <= lineCount; i++) {
    const offset = i * 0.01; // 每次偏移的距离,根据你的场景缩放调整
    // 右上偏移
    const offsetPoints1 = points.map(p => new THREE.Vector3(p.x + offset, p.y + offset, p.z));
    const line1 = new THREE.LineLoop(new THREE.BufferGeometry().setFromPoints(offsetPoints1), baseMaterial);
    scene.add(line1);
    // 左下偏移
    const offsetPoints2 = points.map(p => new THREE.Vector3(p.x - offset, p.y - offset, p.z));
    const line2 = new THREE.LineLoop(new THREE.BufferGeometry().setFromPoints(offsetPoints2), baseMaterial);
    scene.add(line2);
  }
}

// 使用示例
const points = [
  new THREE.Vector3(-1, 1, 0),
  new THREE.Vector3(1, 1, 0),
  new THREE.Vector3(1, -1, 0),
  new THREE.Vector3(-1, -1, 0),
  new THREE.Vector3(-1, 1, 0)
];

createThickLine(points, 10, 0x000000);

优点:实现超简单,不用额外依赖;缺点:线条边缘不够平滑,复杂形状偏移会出现错位,线条多了会影响性能。

方法4:EdgesGeometry + Mesh(网格专属轮廓)

如果你是给3D网格加线框轮廓,这个方法最省心——用EdgesGeometry提取网格的边,然后用Mesh而不是Line,通过缩放Mesh来实现粗线条。

  • 代码示例:
import * as THREE from 'three';

// 创建一个立方体,内部设为透明
const cubeGeometry = new THREE.BoxGeometry(2, 2, 2);
const cubeMaterial = new THREE.MeshBasicMaterial({ 
  color: 0xffffff, 
  transparent: true, 
  opacity: 0 // 内部完全透明,只留轮廓
});
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
scene.add(cube);

// 提取立方体的边,生成边几何体
const edgesGeometry = new THREE.EdgesGeometry(cubeGeometry);
// 用Mesh渲染边,这样可以缩放来加粗
const edgeMaterial = new THREE.MeshBasicMaterial({ color: 0x000000, side: THREE.DoubleSide });
const edgeMesh = new THREE.Mesh(edgesGeometry, edgeMaterial);
// 稍微放大边网格,实现粗线效果
edgeMesh.scale.set(1.02, 1.02, 1.02);
cube.add(edgeMesh);

优点:针对网格轮廓的场景特别好用,实现简单;缺点:只适用于网格对象,自由绘制的线条没法用。

这些方法里,我最推荐用three-meshline,省心效果还好。如果不想加库,自定义着色器是最灵活的选择,根据你的具体场景挑就行~

内容的提问来源于stack exchange,提问作者JJ Gerrish

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:31:40