threlte logo
@threlte/extras

<MeshLineMaterial>

webgl <MeshLineMaterial> works with WebGL and is available from the @threlte/extras export. webgpu <MeshLineMaterial> works with WebGPU and is available from the @threlte/extras/webgpu export.

Used in combination with <MeshLineGeometry> to create a line formed of a strip of billboarded triangles, based on THREE.MeshLine.

<script lang="ts">
  import { Canvas } from '@threlte/core'
  import Scene from './Scene.svelte'
  import { Checkbox, Folder, Pane, Slider } from 'svelte-tweakpane-ui'

  let opacity = $state(1)
  let dashArray = $state(0.5)
  let dashRatio = $state(0.5)
  let attenuate = $state(true)
  let scaleDown = $state(0)
</script>

<Pane
  title=""
  position="fixed"
>
  <Slider
    bind:value={opacity}
    label="opacity"
    min={0}
    max={1}
    step={0.01}
  />
  <Checkbox
    bind:value={attenuate}
    label="attenuate"
  />
  <Slider
    bind:value={scaleDown}
    label="scaleDown"
    min={0}
    max={10}
    step={0.1}
  />
  <Folder title="Dash">
    <Slider
      bind:value={dashArray}
      label="dashArray"
      min={0}
      max={1}
      step={0.01}
    />
    <Slider
      bind:value={dashRatio}
      label="dashRatio"
      min={0}
      max={1}
      step={0.01}
    />
  </Folder>
</Pane>

<div>
  <Canvas>
    <Scene
      {opacity}
      {dashArray}
      {dashRatio}
      {attenuate}
      {scaleDown}
    />
  </Canvas>
</div>

<style>
  div {
    height: 100%;
  }
</style>
<script>
  import { T, useTask } from '@threlte/core'
  import {
    MeshLineMaterial,
    MeshLineGeometry,
    Grid,
    OrbitControls,
    useTexture
  } from '@threlte/extras'
  import { Vector3, CatmullRomCurve3, Color } from 'three'

  let {
    width = 0.5,
    opacity = 1,
    dashArray = 0.5,
    dashRatio = 0.5,
    attenuate = true,
    scaleDown = 0
  } = $props()

  // create a smooth curve from 4 points
  const curve = new CatmullRomCurve3([
    new Vector3(-3, 0, 0),
    new Vector3(-1, 1, -1),
    new Vector3(1, -1, 1),
    new Vector3(3, 0, 0)
  ])

  // convert curve to an array of 100 points
  const points = curve.getPoints(100)

  let dashOffset = $state(0)
  let color = $state('#fe3d00')
  const orange = new Color('#fe3d00')
  const purple = new Color('#9800fe')
  const c = new Color()
  c.lerpColors(orange, purple, 0.5)

  useTask((delta) => {
    // every frame we:
    // increase the dash offset
    dashOffset += delta / 2
    // transition between two colors
    color = `#${c.lerpColors(orange, purple, Math.sin(dashOffset * 2) / 2 + 0.5).getHexString()}`
    // shrink and grow the line width
    width = Math.sin(dashOffset * 2) / 5 + 0.3
  })
</script>

<T.Mesh
  position.y={3}
  scale={2}
>
  <MeshLineGeometry {points} />

  <MeshLineMaterial
    {width}
    {color}
    {opacity}
    {dashArray}
    {dashRatio}
    {dashOffset}
    {attenuate}
    {scaleDown}
    transparent
    depthTest={false}
  />
</T.Mesh>

<T.PerspectiveCamera
  makeDefault
  oncreate={(ref) => {
    ref.position.set(10, 3, 10)
  }}
>
  <OrbitControls
    autoRotate={true}
    autoRotateSpeed={2}
    enableDamping
    enableZoom={false}
    target.y={2}
  />
</T.PerspectiveCamera>

<Grid
  gridSize={[10, 10]}
  cellColor={'#46536b'}
  sectionThickness={0}
/>
<script lang="ts">
  import { Canvas } from '@threlte/core/webgpu'
  import Scene from './Scene.svelte'
</script>

<div>
  <Canvas>
    <Scene />
  </Canvas>
</div>

<style>
  div {
    height: 100%;
  }
</style>
<script lang="ts">
  import { T, useTask } from '@threlte/core/webgpu'
  import { Grid, MeshLineGeometry, MeshLineMaterial, OrbitControls } from '@threlte/extras/webgpu'
  import { CatmullRomCurve3, Color, Vector3 } from 'three/webgpu'

  let {
    width = 0.5,
    opacity = 1,
    dashArray = 0.5,
    dashRatio = 0.5,
    attenuate = true,
    scaleDown = 0
  } = $props()

  const curve = new CatmullRomCurve3([
    new Vector3(-3, 0, 0),
    new Vector3(-1, 1, -1),
    new Vector3(1, -1, 1),
    new Vector3(3, 0, 0)
  ])

  const points = curve.getPoints(100)

  let dashOffset = $state(0)
  let color = $state('#fe3d00')
  const orange = new Color('#fe3d00')
  const purple = new Color('#9800fe')
  const lerped = new Color()

  useTask((delta) => {
    dashOffset += delta / 2
    color = `#${lerped.lerpColors(orange, purple, Math.sin(dashOffset * 2) / 2 + 0.5).getHexString()}`
    width = Math.sin(dashOffset * 2) / 5 + 0.3
  })
</script>

<T.Mesh
  position.y={3}
  scale={2}
>
  <MeshLineGeometry {points} />

  <MeshLineMaterial
    {width}
    {color}
    {opacity}
    {dashArray}
    {dashRatio}
    {dashOffset}
    {attenuate}
    {scaleDown}
    transparent
    depthTest={false}
  />
</T.Mesh>

<T.PerspectiveCamera
  makeDefault
  oncreate={(ref) => {
    ref.position.set(10, 3, 10)
  }}
>
  <OrbitControls
    autoRotate
    autoRotateSpeed={2}
    enableDamping
    enableZoom={false}
    target.y={2}
  />
</T.PerspectiveCamera>

<Grid
  gridSize={[10, 10]}
  cellColor="#46536b"
  sectionThickness={0}
/>

Usage

This component works by taking a line geometry from <MeshLineGeometry> and projecting and expanding the vertices in screen space. Both <MeshLineMaterial> and <MeshLineGeometry> need belong to the same parent mesh.

Example

<script>
  const points = [new Vector3(-5, 1, 0), new Vector3(0, 1, 0), new Vector3(5, 1, 0)]
</script>

<T.Mesh>
  <MeshLineGeometry {points} />
  <MeshLineMaterial
    width={0.5}
    color="#fe3d00"
  />
</T.Mesh>

Width and color

By default the line will be white and have a width of 1. The width property will use world units and scale correctly with other objects in your scene. If you would like the line to be a fixed size regardless of distance from the camera you can set the attenuate property to false.

Opacity and dashes

Just like other materials in Three.js you need to set transparent to true for opacity to have any effect. You must also set transparent to true for if you are using dashed lines.

You can use a combination of dashArray, dashRatio and dashOffset to create dashed lines.

If you’re rendering transparent lines, dashed lines or lines with an alpha map you can avoid issues where the line overlaps itself by setting depthTest to false.

Alpha map

You can pass a texture to the alphaMap property to use as an alpha mask along the length of the line, where black is invisible and white is visible. In the example below we load a paint brush texture with the useTexture hook.

<script lang="ts">
  import { Canvas } from '@threlte/core'
  import Scene from './Scene.svelte'
</script>

<div>
  <Canvas>
    <Scene />
  </Canvas>
</div>

<style>
  div {
    height: 100%;
  }
</style>
<script>
  import { T } from '@threlte/core'
  import {
    Grid,
    MeshLineGeometry,
    MeshLineMaterial,
    OrbitControls,
    useTexture
  } from '@threlte/extras'
  import { CubicBezierCurve3, DoubleSide, Vector3 } from 'three'

  const texture = useTexture('/brush-texture.png')

  // create a smooth bezier curve
  const curve = new CubicBezierCurve3(
    new Vector3(-5, 0, 0),
    new Vector3(-5, 7, 0),
    new Vector3(5, 7, 0),
    new Vector3(5, 0, 0)
  )

  // convert curve to an array of 100 points
  const points = curve.getPoints(100)
</script>

<T.Mesh rotation.z={-0.1}>
  <MeshLineGeometry {points} />
  {#await texture then alphaMap}
    <MeshLineMaterial
      width={1}
      color={'#fe3d00'}
      transparent
      depthTest={false}
      {alphaMap}
    />
  {/await}
</T.Mesh>

{#await texture then map}
  <T.Mesh
    position.y={2}
    scale={2}
  >
    <T.PlaneGeometry />
    <T.MeshBasicMaterial
      {map}
      side={DoubleSide}
    />
  </T.Mesh>
{/await}

<T.PerspectiveCamera
  makeDefault
  position={[0, 3, 10]}
>
  <OrbitControls
    autoRotateSpeed={2}
    enableDamping
    target.y={2}
  />
</T.PerspectiveCamera>

<Grid
  gridSize={[10, 10]}
  cellColor={'#46536b'}
  sectionThickness={0}
/>
<script lang="ts">
  import { Canvas } from '@threlte/core/webgpu'
  import Scene from './Scene.svelte'
</script>

<div>
  <Canvas>
    <Scene />
  </Canvas>
</div>

<style>
  div {
    height: 100%;
  }
</style>
<script lang="ts">
  import { T } from '@threlte/core/webgpu'
  import {
    Grid,
    MeshLineGeometry,
    MeshLineMaterial,
    OrbitControls,
    useTexture
  } from '@threlte/extras/webgpu'
  import { CubicBezierCurve3, DoubleSide, Vector3 } from 'three/webgpu'

  const texture = useTexture('/brush-texture.png')

  const curve = new CubicBezierCurve3(
    new Vector3(-5, 0, 0),
    new Vector3(-5, 7, 0),
    new Vector3(5, 7, 0),
    new Vector3(5, 0, 0)
  )

  const points = curve.getPoints(100)
</script>

<T.Mesh rotation.z={-0.1}>
  <MeshLineGeometry {points} />
  {#await texture then alphaMap}
    <MeshLineMaterial
      width={1}
      color="#fe3d00"
      transparent
      depthTest={false}
      {alphaMap}
    />
  {/await}
</T.Mesh>

{#await texture then map}
  <T.Mesh
    position.y={2}
    scale={2}
  >
    <T.PlaneGeometry />
    <T.MeshBasicMaterial
      {map}
      side={DoubleSide}
    />
  </T.Mesh>
{/await}

<T.PerspectiveCamera
  makeDefault
  position={[10, 0, 0]}
>
  <OrbitControls
    autoRotateSpeed={2}
    enableDamping
    target.y={2}
  />
</T.PerspectiveCamera>

<Grid
  gridSize={[10, 10]}
  cellColor="#46536b"
  sectionThickness={0}
/>

Component Signature

<MeshLineMaterial> extends < T . ShaderMaterial > and supports all its props, snippets, bindings and events.

Props

name
type
required
default
description

alphaMap
Texture
no
A texture whose red channel sets the line's alpha along its length.

attenuate
boolean
no
true
Makes the line thinner with distance from the camera. When false, width is a constant size on screen.

color
ColorRepresentation
no
'#ffffff'
The color of the line.

dashArray
number
no
0
The length of one dash and its gap, as a fraction of the line's length. 0 draws a solid line.

dashOffset
number
no
0
Shifts the dash pattern along the line. Animate it to move the dashes.

dashRatio
number
no
0
The share of each dash cycle that is a gap, from 0 (no gap) to 1 (no dash).

opacity
number
no
1
The opacity of the line. Values below 1 need transparent set.

scaleDown
number
no
0
Thins the line where consecutive points are close together on screen, which hides artifacts at sharp turns. 0 disables it.

width
number
no
1
The width of the line. In world units when attenuate is true, otherwise in screen pixels.