threlte logo
@threlte/extras

<Wireframe>

webgl <Wireframe> works with WebGL only.

<Wireframe> patches the material of its parent to render an antialiased, shader based wireframe on a geometry.

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

  let wireframeProps = $state({
    thickness: 0.7,

    squeeze: true,
    squeezeMin: 0.2,
    squeezeMax: -0.13,

    dash: false,
    dashInvert: true,
    dashRepeats: 4,
    dashLength: 0.1,

    fill: new Color('lightgreen'),
    fillOpacity: 1,
    fillMix: 0,

    stroke: new Color('red'),
    strokeOpacity: 1,
    colorBackfaces: false,
    backfaceStroke: new Color('lightred')
  })
</script>

<Pane
  title=""
  position="fixed"
>
  <Slider
    label="thickness"
    min={0}
    max={20}
    step={0.1}
    bind:value={wireframeProps.thickness}
  />
  <Separator />

  <Checkbox
    label="squeeze"
    bind:value={wireframeProps.squeeze}
  />
  <Slider
    label="squeezeMin"
    bind:value={wireframeProps.squeezeMin}
  />
  <Slider
    label="squeezeMax"
    bind:value={wireframeProps.squeezeMax}
  />
  <Separator />

  <Checkbox
    label="dash"
    bind:value={wireframeProps.dash}
  />
  <Checkbox
    label="dashInvert"
    bind:value={wireframeProps.dashInvert}
  />
  <Slider
    label="dashLength"
    bind:value={wireframeProps.dashLength}
  />
  <Slider
    label="dashRepeats"
    step={1}
    bind:value={wireframeProps.dashRepeats}
  />
  <Separator />

  <ColorInput
    label="fill"
    type="float"
    bind:value={wireframeProps.fill}
  />
  <Slider
    label="fillOpacity"
    bind:value={wireframeProps.fillOpacity}
  />
  <Slider
    label="fillMix"
    step={0.01}
    bind:value={wireframeProps.fillMix}
  />
  <Separator />

  <ColorInput
    label="stroke"
    type="float"
    bind:value={wireframeProps.stroke}
  />
  <Slider
    label="fillOpacity"
    bind:value={wireframeProps.strokeOpacity}
  />
  <ColorInput
    label="backfaceStroke"
    type="float"
    bind:value={wireframeProps.backfaceStroke}
  />
</Pane>

<div>
  <Canvas>
    <Scene {wireframeProps} />
  </Canvas>
</div>

<style>
  div {
    position: relative;
    height: 100%;
    width: 100%;
  }
</style>
<script lang="ts">
  import { T } from '@threlte/core'
  import { useGltf, useGltfAnimations, Wireframe } from '@threlte/extras'

  let { wireframeProps } = $props()

  const gltf = useGltf('https://threejs.org/examples/models/gltf/Xbot.glb')
  let { actions } = useGltfAnimations(() => $gltf)

  $effect(() => {
    // This effect acts like an init default pose
    $actions?.idle?.play()
  })
</script>

<T.Group dispose={false}>
  {#await gltf then { nodes, materials }}
    <T is={nodes.Scene}>
      <T
        is={nodes.Armature}
        name="Armature"
        scale={0.01}
      >
        <T is={nodes.mixamorigHips} />
        <T.SkinnedMesh
          name="Beta_Joints"
          geometry={nodes.Beta_Joints.geometry}
          material={materials.Beta_Joints_MAT}
          skeleton={nodes.Beta_Joints.skeleton}
          castShadow
        />
        <T.SkinnedMesh
          name="Beta_Surface"
          geometry={nodes.Beta_Surface.geometry}
          material={materials['asdf1:Beta_HighLimbsGeoSG2']}
          skeleton={nodes.Beta_Surface.skeleton}
          castShadow
        >
          <Wireframe {...wireframeProps} />
        </T.SkinnedMesh>
      </T>
    </T>
  {/await}
</T.Group>
<script lang="ts">
  import type { InstancedMesh as ThreeInstancedMesh } from 'three'
  import { T, useTask } from '@threlte/core'
  import Character from './Character.svelte'
  import { InstancedMesh, Instance, Wireframe, Outlines, Float } from '@threlte/extras'
  import { Vector3, Quaternion } from 'three'

  let { wireframeProps } = $props()

  let boxes = $state.raw<ThreeInstancedMesh>()

  const numCubes = 70

  useTask((delta) => {
    if (boxes) boxes.rotation.y += delta / 60
  })
</script>

<T.PerspectiveCamera
  makeDefault
  position={[-0.8, 1.2, 1.7]}
  oncreate={(ref) => {
    ref.lookAt(0, 1, 0)
  }}
/>

<T.AmbientLight />
<T.DirectionalLight
  position={[10, 5, 5]}
  castShadow
/>

<Character {wireframeProps} />

<T.Mesh
  rotation.x={-90 * (Math.PI / 180)}
  receiveShadow
>
  <T.CircleGeometry args={[3, 72]} />
  <T.MeshStandardMaterial color={'white'} />

  <Outlines
    color="red"
    thickness={10}
  />
</T.Mesh>

<InstancedMesh
  bind:ref={boxes}
  castShadow
>
  <T.BoxGeometry args={[0.07, 0.07, 0.07]} />
  <T.MeshStandardMaterial />

  <Wireframe {...wireframeProps} />
  {#each { length: numCubes }, index}
    {@const height = new Vector3(0, 1.2, 0)}
    {@const position = new Vector3().randomDirection().add(height).toArray()}
    {@const quaternion = new Quaternion().random().toArray()}
    {@const scale = new Vector3().randomDirection().multiplyScalar(2).toArray()}

    <Float seed={Math.random() * index}>
      <Instance
        {position}
        {quaternion}
        {scale}
      />
    </Float>
  {/each}
</InstancedMesh>

<Wireframe> uses barycentric coordinates to generate the wireframe, which requires a non-indexed geometry.

This means that any geometry with a <Wireframe> that has indices will use a cloned, non-indexed version while <Wireframe> is mounted.

Basic Example

Wireframe.svelte
<script>
  import { T } from '@threlte/core'
  import { Wireframe } from '@threlte/extras'
</script>

<T.Mesh>
  <T.BoxGeometry />
  <T.MeshStandardMaterial />
  <Wireframe />
</T.Mesh>

Component Signature

Props

name
type
required
default
description

backfaceStroke
ColorRepresentation
no
'#0000ff'
The color of the lines on back faces, when colorBackfaces is enabled.

colorBackfaces
boolean
no
false
Draws the lines of back faces in backfaceStroke.

dash
boolean
no
false
Draws the lines as dashes.

dashInvert
boolean
no
true
Centers the dashes on the corners of each triangle instead of on the middle of each edge.

dashLength
number
no
0.5
The length of each dash, as a fraction of the space it repeats over, from 0 to 1.

dashRepeats
number
no
4
The number of dashes along each edge.

fill
ColorRepresentation
no
'#00ff00'
The color of the faces between the lines, mixed in by fillMix.

fillMix
number
no
0
How much the faces take on fill and fillOpacity instead of the material's own color and opacity, from 0 to 1.

fillOpacity
number
no
0
The opacity of the faces between the lines, mixed in by fillMix.

simplify
boolean
no
false
Hides the diagonal edge shared by each pair of triangles, so quads are drawn as quads.

squeeze
boolean
no
false
Thins each line toward the middle of its edge.

squeezeMax
number
no
1
The thickness multiplier at the ends of each edge when squeeze is enabled.

squeezeMin
number
no
0.2
The thickness multiplier at the middle of each edge when squeeze is enabled.

stroke
ColorRepresentation
no
'#ff0000'
The color of the lines.

strokeOpacity
number
no
1
The opacity of the lines.

thickness
number
no
1
The thickness of the lines, in approximate screen pixels.