threlte logo
@threlte/extras

<Wireframe>

<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="Wireframe"
  position="fixed"
>
  <Slider
    label="thickness"
    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, type QuaternionTuple, type Vector3Tuple } from 'three'

  let { wireframeProps } = $props()

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

  let poses: {
    position: Vector3Tuple
    quaternion: QuaternionTuple
  }[] = []

  const numCubes = 70
  for (let i = 0; i < numCubes; i += 1) {
    const position = new Vector3().randomDirection().multiplyScalar(1).toArray()
    position[1] += 1.2
    poses.push({
      position,
      quaternion: new Quaternion().random().toArray()
    })
  }

  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 poses as { position, quaternion }, index}
    <Float seed={Math.random() * index}>
      <Instance
        {position}
        {quaternion}
      />
    </Float>
  {/each}
</InstancedMesh>

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

This means that any geometry with a <Wireframe> that has indices will be converted to a non-indexed version.

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

backfaceStroke
THREE.ColorRepresentation
no
"#0000ff"

colorBackfaces
boolean
no
false

dash
boolean
no
false

dashInvert
boolean
no
true

dashLength
number
no
0.5

dashRepeats
number
no
4

fill
THREE.ColorRepresentation
no
"#00ff00"

fillMix
number
no
0

fillOpacity
number
no
0

fillOpacity
number
no
false

simplify
boolean
no
false

squeeze
boolean
no
false

squeezeMax
number
no
1

squeezeMin
number
no
0.2

stroke
THREE.ColorRepresentation
no
"#ff0000"

strokeOpacity
number
no
1

thickness
number
no
0.05