threlte logo
@threlte/extras

<CubeCamera>

A wrapper around three’s CubeCamera that exposes a renderTarget prop. Before rendering to the render target, children are set to invisible to exclude them from the render.

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

  const resolutionOptions: ListOptions<number> = {
    128: 128,
    256: 256,
    512: 512
  } as const

  const environmentOptions: { [Key in keyof typeof hdrs]: Key } & { auto: 'auto' } = {
    auto: 'auto',
    industrial: 'industrial',
    puresky: 'puresky',
    workshop: 'workshop'
  } as const

  let hdr = $state('auto')
  let metalness = $state(1)
  let resolution = $state(256)
  let roughness = $state(0)

  let capFrames = $state(false)
  let frames = $derived(capFrames ? 3 : Infinity)
</script>

<Pane
  position="fixed"
  title="CubeCamera"
>
  <Folder title="render target">
    <List
      bind:value={resolution}
      label="resolution"
      options={resolutionOptions}
    />
    <List
      bind:value={hdr}
      label="environment"
      options={environmentOptions}
    />
    <Checkbox
      bind:value={capFrames}
      label="cap frames"
    />
  </Folder>
  <Folder title="texture">
    <Slider
      bind:value={metalness}
      max={1}
      min={0}
      step={0.1}
      label="metalness"
    />
    <Slider
      bind:value={roughness}
      max={1}
      min={0}
      step={0.1}
      label="roughness"
    />
  </Folder>
</Pane>

<div>
  <Canvas>
    <Scene
      {frames}
      {hdr}
      {metalness}
      {resolution}
      {roughness}
    />
  </Canvas>
</div>

<style>
  div {
    height: 100%;
  }
</style>
<script
  lang="ts"
  context="module"
>
  export const hdrs = {
    industrial: '/hdr/industrial_sunset_puresky_1k.hdr',
    workshop: '/hdr/aerodynamics_workshop_1k.hdr',
    puresky: '/hdr/mpumalanga_veld_puresky_1k.hdr'
  } as const

  const isHdrKey = (u: PropertyKey): u is keyof typeof hdrs => {
    return u in hdrs
  }
</script>

<script lang="ts">
  import type { Vector3Tuple } from 'three'
  import { CubeCamera, Environment, OrbitControls } from '@threlte/extras'
  import { EquirectangularReflectionMapping } from 'three'
  import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'
  import { T, useLoader, useTask } from '@threlte/core'

  type SceneProps = {
    frames?: number
    far?: number
    hdr?: 'auto' | keyof typeof hdrs
    metalness?: number
    near?: number
    resolution?: number
    roughness?: number
  }

  let {
    frames = Infinity,
    far = 1000,
    hdr = 'auto',
    metalness = 1,
    near = 0.1,
    resolution = 256,
    roughness = 0
  }: SceneProps = $props()

  const loader = useLoader(RGBELoader)
  const textures = loader.load(hdrs, {
    transform(texture) {
      texture.mapping = EquirectangularReflectionMapping
      return texture
    }
  })

  const colors = [0xff_00_ff, 0xff_ff_00, 0x00_ff_ff] as const

  const increment = (2 * Math.PI) / colors.length
  const radius = 3

  let time = $state(0)
  let y = $derived(2 * Math.sin(time))

  useTask((delta) => {
    time += delta
  })

  const cameraPosition: Vector3Tuple = [7, 7, 7]
</script>

<T.PerspectiveCamera
  makeDefault
  position={cameraPosition}
>
  <OrbitControls />
</T.PerspectiveCamera>

<T.AmbientLight />

{#each colors as color, i}
  {@const r = increment * i}
  <T.Mesh
    position.x={radius * Math.cos(r)}
    position.y={i}
    position.z={radius * Math.sin(r)}
  >
    <T.MeshStandardMaterial {color} />
    <T.BoxGeometry />
  </T.Mesh>
{/each}

<Environment
  path="/hdr/"
  files="shanghai_riverside_1k.hdr"
  isBackground
  format="hdr"
/>

{#await textures then textureRecord}
  {@const background = isHdrKey(hdr) ? textureRecord[hdr] : hdr}
  {#each Array(colors.length) as _, i}
    {@const r = Math.PI + increment * i}
    <CubeCamera
      {near}
      {far}
      {resolution}
      {background}
      {frames}
      position.y={y + i}
      position.x={radius * Math.cos(r)}
      position.z={radius * Math.sin(r)}
    >
      {#snippet children({ renderTarget })}
        <T.Mesh>
          <T.SphereGeometry />
          <T.MeshStandardMaterial
            {roughness}
            {metalness}
            envMap={renderTarget.texture}
          />
        </T.Mesh>
      {/snippet}
    </CubeCamera>
  {/each}
{/await}

Usage

The entire render target that is used by the underlying cube camera is available through the renderTarget snippet prop. Usually you’ll only want to use the renderTarget.texture.

<CubeCamera>
  {#snippet children({ renderTarget })}
    <T.Mesh>
      <T.SphereGeometry />
      <T.MeshStandardMaterial envMap={renderTarget.texture} />
    </T.Mesh>
  {/snippet}
</CubeCamera>

Other Snippet Props

The children snippet also has access to the cube camera and a restart function. Note that updates to the camera, render target or ref inside the snippet block will not be reflected on the render target unless frames is set to Infinity or you use the restart function. Calling restart will force the renderer to render to the render target.

{#snippet children({ camera, ref, renderTarget, restart })}
  <!-- may need to call `restart` here if updating any of the props above -->
{/snippet}

Controlling Render Count

By default, frames is set to Infinity which means the scene is rendered to the render target every frame. This is sometimes unnecessary especially if you have a static scene. To improve performance, you can use the frames prop to control how many times the scene should be rendered.

For moving objects, let frames default to Infinity. If you have a static scene, set frames equal to the number of <CubeCamera>s in the scene. This will allow each one to render and then be picked up in each other’s reflection.

Scene Props

<CubeCamera> accepts a background prop that can be used to set the background of the scene when rendering to the render target. By default the current scene.background is used. background can be any valid Scene.background.

<script>
  import { Color } from 'three'

  const background = new Color(0xff_00_ff)
</script>

<CubeCamera {background}>
  <!-- ... -->
</CubeCamera>

The fog prop is used the same way and accepts any valid scene.fog. By default scene.fog is used.

These “scene” props are only used when rendering the scene to the underlying render target. After rendering to the target, the original scene props are restored.

Callback Props

The onrenderstart callback prop is called anytime the underlying render task has been started. This means that it also fires anytime the renderer has been restarted manually through the restart component export.

<script>
  const onrenderstart = () => {
    console.log('render started')
  }

  let ref
  // `onrenderstart` will be fired if `ref.restart` is called
</script>

<CubeCamera
  bind:ref
  onrenderstart
>
  <!-- ... -->
</CubeCamera>

onrenderstart also fires if you use the restart children snippet prop

<CubeCamera onrenderstart>
  {#snippet children({ restart })}
    <T.Mesh oncreate={restart}>
      <!-- ... -->
    </T.Mesh>
  {/snippet}
</CubeCamera>

The onrenderstop callback fires anytime the render task stops running. It is called on restarts and when the internal counter goes over the frames limit. This means that if frames is set to Infinity, onrenderstop is never called.

Component Signature

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

Props

name
type
required
default
description

background
Three.Scene['background'] | 'auto'
no
'auto'
custom background to use when rendering to the render target

far
number
no
1000
passed along to the underlying cube camera

fog
Three.Scene['fog'] | 'auto'
no
'auto'
custom fog to use when rendering to the render target

frames
number
no
Infinity
controls how many frames the render task will run for

near
number
no
0.1
passed along to the underlying cube camera

onrenderstart
() => void
no
undefined
a callback prop that is called anytime the render task is started

onrenderstop
() => void
no
undefined
a callback prop that is called anytime the render task is stopped

resolution
number
no
256
size of the render target. more resolution means more detail

Exports

name
type

camera
THREE.CubeCamera

renderTarget
THREE.WebGLCubeRenderTarget

restart
() => void