threlte logo
@threlte/extras

<Backdrop>

A mesh of a curved plane designed to look like a studio backdrop. Its intent is to break up light and shadows in more interesting ways.

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

  let floor = $state(0.5)
  let receiveShadow = $state(true)
  let segments = $state(20)

  let materialColor = $state('#ffffff')
  let materialWireframe = $state(false)

  let lightHelperVisible = $state(true)
</script>

<Pane
  position="fixed"
  title="backdrop"
>
  <Folder title="component props">
    <Slider
      bind:value={floor}
      min={0}
      max={1}
      step={0.25}
      label="floor"
    />
    <Checkbox
      bind:value={receiveShadow}
      label="receive shadow"
    />
    <Slider
      min={10}
      max={50}
      step={10}
      bind:value={segments}
      label="segments"
    />
  </Folder>
  <Folder title="material props">
    <Color
      bind:value={materialColor}
      label="color"
    />
    <Checkbox
      label="wireframe"
      bind:value={materialWireframe}
    />
  </Folder>
  <Folder title="light helper">
    <Checkbox
      label="visible"
      bind:value={lightHelperVisible}
    />
  </Folder>
</Pane>

<div>
  <Canvas>
    <Scene
      {floor}
      {materialColor}
      {materialWireframe}
      {receiveShadow}
      {segments}
      {lightHelperVisible}
    />
  </Canvas>
</div>

<style>
  div {
    height: 100%;
  }
</style>
<script lang="ts">
  import type { BackdropProps } from '@threlte/extras'
  import type { TransformControls as ThreeTransformControls } from 'three/examples/jsm/Addons.js'
  import type {
    ColorRepresentation,
    DirectionalLight,
    DirectionalLightHelper,
    Mesh,
    MeshStandardMaterial
  } from 'three'
  import { Backdrop, OrbitControls } from '@threlte/extras'
  import { Resize, TransformControls, useGltf } from '@threlte/extras'
  import { T, useThrelte } from '@threlte/core'

  let {
    lightHelperVisible = true,
    materialColor = 'white',
    materialWireframe = false,
    ...backdropProps
  }: BackdropProps & {
    lightHelperVisible: boolean
    materialColor: ColorRepresentation
    materialWireframe: boolean
  } = $props()

  const gltf = useGltf<{
    nodes: { LOD3spShape: Mesh }
    materials: { 'blinn3-fx': MeshStandardMaterial }
  }>('/models/Duck.glb').then((gltf) => {
    gltf.nodes.LOD3spShape.castShadow = true
    return gltf
  })

  const { scene } = useThrelte()

  let helper = $state.raw<DirectionalLightHelper>()
  let controls = $state.raw<ThreeTransformControls>()
  let light = $state.raw<DirectionalLight>()

  $effect(() => {
    if (lightHelperVisible && light !== undefined) {
      controls?.attach(light)
    }
    return () => {
      controls?.detach()
    }
  })
</script>

<T.PerspectiveCamera
  makeDefault
  position.x={10}
  position.y={5}
  position.z={10}
>
  <OrbitControls
    enableDamping
    maxPolarAngle={0.5 * Math.PI}
    minAzimuthAngle={-1 * 0.25 * Math.PI}
    maxAzimuthAngle={0.25 * Math.PI}
    maxDistance={20}
  />
</T.PerspectiveCamera>

<T.DirectionalLight
  position.x={-1 * 3}
  position.y={2}
  position.z={4}
  castShadow
  bind:ref={light}
>
  {#snippet children({ ref })}
    <TransformControls
      bind:controls
      onobjectChange={() => {
        helper?.update()
      }}
    />
    <T.DirectionalLightHelper
      bind:ref={helper}
      args={[ref]}
      attach={scene}
      visible={lightHelperVisible}
    />
  {/snippet}
</T.DirectionalLight>

<Backdrop
  {...backdropProps}
  scale={[50, 10, 10]}
>
  <T.MeshStandardMaterial
    color={materialColor}
    wireframe={materialWireframe}
  />
</Backdrop>

{#await gltf then { scene }}
  <Resize
    scale={4}
    position.z={1}
    rotation.y={Math.PI}
  >
    <T is={scene} />
  </Resize>
{/await}

Children are “slotted” into the underlying mesh so you can set the material of the backdrop like so:

<Backdrop>
  <T.MeshStandardMaterial color="#ffffdd" />
</Backdrop>

Props

floor controls the length of the “floor” segment of the geometry. The higher the value, the longer the segment.

receiveShadow controls whether the mesh receives shadows or not.

segments controls the resolution of the mesh.

Be aware that updating the segments prop causes the geometry to be rebuilt. It’s advised not to update segments in hot-code paths such as useTask().

Component Signature

<Backdrop> extends <T . Group> and supports all its props, slot props, bindings and events.

Props

name
type
required
default
description

floor
number
no
0.25
length of the floor segment

receiveShadow
boolean
no
false
whether the backdrop is to receive shadows

segments
number
no
20
resolution of the backdrop's geometry