threlte logo
@threlte/extras

<BakeShadows>

<BakeShadows> instantly freezes all shadow maps upon mounting and unfreezes them upon unmounting. This improves performance by making all shadows static. Use <BakeShadows if you have a complex but static scene as the shadows will only be calculated once.

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

  let bake = $state(false)
</script>

<Pane
  title="BakeShadows"
  position="fixed"
>
  <Checkbox
    bind:value={bake}
    label="bake shadows"
  />
</Pane>

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

<style>
  div {
    height: 100%;
  }
</style>
<script lang="ts">
  import { BakeShadows } from '@threlte/extras'
  import { T, useTask } from '@threlte/core'

  type Props = {
    bake: boolean
  }

  let { bake }: Props = $props()

  let rotation = $state(0)
  useTask((delta) => {
    rotation += delta
  })
</script>

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

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

<T.Mesh
  castShadow
  position.y={1}
  rotation.y={rotation}
>
  <T.BoxGeometry args={[1, 2, 1]} />
  <T.MeshStandardMaterial color="hotpink" />
</T.Mesh>

<T.Mesh
  receiveShadow
  rotation.x={-1 * 0.5 * Math.PI}
>
  <T.CircleGeometry args={[4, 40]} />
  <T.MeshStandardMaterial color="white" />
</T.Mesh>

{#if bake}
  <BakeShadows />
{/if}

When <BakeShadows> is used within a <Suspense>, shadows will be frozen after the boundary is no longer in a suspended state. It will unfreeze if the boundary is re-suspended.

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

  let bake = $state(false)
</script>

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

<style>
  div {
    height: 100%;
  }
</style>
<script lang="ts">
  import Spaceship from './Spaceship.svelte'
  import type { SpaceshipProps } from './Spaceship.svelte'
  import { BakeShadows, OrbitControls, Portal, Suspense } from '@threlte/extras'
  import { Color } from 'three'
  import { T, useThrelte } from '@threlte/core'

  const { size, scene } = useThrelte()
  scene.background = new Color('black')

  let zoom = $derived($size.width / 50)

  type Ship = Required<Pick<SpaceshipProps, 'name' | 'position'>>

  const ships: Ship[] = [
    { name: 'Bob', position: [-12, 0, 3] },
    { name: 'Challenger', position: [10, 5, 6] },
    { name: 'Dispatcher', position: [8, 3, -23] },
    { name: 'Executioner', position: [12, -4, 6] },
    { name: 'Imperial', position: [-1, 0, -21] },
    { name: 'Insurgent', position: [-13, 1, -21] },
    { name: 'Omen', position: [-9, -5, 13] },
    { name: 'Pancake', position: [-9, -3, -9] },
    { name: 'Spitfire', position: [1, 0, 1] },
    { name: 'Striker', position: [8, -1, -10] },
    { name: 'Zenith', position: [-1, 0, 13] }
  ]
</script>

<T.OrthographicCamera
  position={[-40, 25, 40]}
  makeDefault
  {zoom}
  oncreate={(ref) => {
    ref.lookAt(0, 0, -8)
  }}
>
  <OrbitControls />
</T.OrthographicCamera>

<T.SpotLight
  position={[0, 25, 0]}
  castShadow
  intensity={1000}
  angle={Math.PI / 3}
/>

<Suspense final>
  {#each ships as { name, position }}
    <Spaceship
      {name}
      {position}
    />
  {/each}
  <BakeShadows />
</Suspense>

<T.Mesh
  receiveShadow
  position.y={-10}
  rotation.x={-1 * 0.5 * Math.PI}
>
  <T.CircleGeometry args={[100]} />
  <T.MeshStandardMaterial color="white" />
</T.Mesh>
<script lang="ts">
  import type { SpaceshipProps } from './Spaceship.svelte'
  import { T } from '@threlte/core'
  import { useGltf, useSuspense } from '@threlte/extras'

  let { name, ...props }: SpaceshipProps = $props()

  const suspend = useSuspense()

  let gltf = $derived(suspend(useGltf(`/models/spaceships/${name}.gltf`)))
</script>

{#await gltf then { scene }}
  <T.Group {...props}>
    <T
      is={scene}
      oncreate={(ref) => {
        for (const child of ref.children) {
          child.castShadow = true
          child.receiveShadow = true
        }
      }}
    />
  </T.Group>
{/await}
import type { Props } from '@threlte/core'
import type { SvelteComponent } from 'svelte'
import type { Group } from 'three'

export type SpaceshipProps = Props<Group> & {
  name:
    | 'Bob'
    | 'Challenger'
    | 'Dispatcher'
    | 'Executioner'
    | 'Imperial'
    | 'Insurgent'
    | 'Omen'
    | 'Pancake'
    | 'Spitfire'
    | 'Striker'
    | 'Zenith'
}

export default class Spaceship extends SvelteComponent<SpaceshipProps> {}

The example above is an ideal scene for <BakeShadows> because all objects are stationary and the shadows will never change.