Skip to content

<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> = {
32: 32,
64: 64,
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: keyof typeof environmentOptions = $state('auto')
let metalness = $state(1)
let resolution = $state(256)
let roughness = $state(0)
let capFrames = $state(false)
let frames = $derived(capFrames ? 3 : Infinity)
let near = $state(0.1)
</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="cube camera props">
<Slider
bind:value={near}
label="near"
max={15}
min={0.1}
/>
</Folder>
<Folder title="material props">
<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}
{near}
{resolution}
{roughness}
/>
</Canvas>
</div>
<style>
div {
height: 100%;
}
</style>
<script
lang="ts"
module
>
export const hdrs = {
industrial: 'industrial_sunset_puresky_1k.hdr',
workshop: 'aerodynamics_workshop_1k.hdr',
puresky: '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 { Group } from 'three'
import { CubeCamera, Environment, Grid, OrbitControls } from '@threlte/extras'
import { EquirectangularReflectionMapping } from 'three'
import { RGBELoader } from 'three/examples/jsm/Addons.js'
import { T, useLoader, useTask } from '@threlte/core'
type SceneProps = {
frames?: number
hdr?: 'auto' | keyof typeof hdrs
metalness?: number
near?: number
resolution?: number
roughness?: number
}
let {
frames = Infinity,
hdr = 'auto',
metalness = 1,
near = 0.1,
resolution = 256,
roughness = 0
}: SceneProps = $props()
const colors = ['#ff00ff', '#ffff00', '#00ffff'] as const
const increment = (2 * Math.PI) / colors.length
const radius = 3
let time = 0
const groups: Group[] = []
useTask((delta) => {
time += delta
let i = 0
for (const group of groups) {
group.position.setY(2 * Math.sin(time + i))
i += 1
}
})
const hdrPath = '/textures/equirectangular/hdr/'
const loader = useLoader(RGBELoader, {
extend(loader) {
loader.setPath(hdrPath)
}
})
const backgrounds = loader.load(hdrs, {
transform(texture) {
texture.mapping = EquirectangularReflectionMapping
return texture
}
})
</script>
<T.PerspectiveCamera
makeDefault
position={[10, 5, 10]}
fov={30}
>
<OrbitControls
enableDamping
enablePan={false}
enableZoom={false}
target.y={0.5}
autoRotate
autoRotateSpeed={0.1}
/>
</T.PerspectiveCamera>
<Environment url={`${hdrPath}shanghai_riverside_1k.hdr`} />
<Grid
position.y={-3}
sectionColor="#fff"
cellColor="#fff"
/>
{#await backgrounds then backgroundMap}
{@const background = isHdrKey(hdr) ? backgroundMap[hdr] : hdr}
{#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.SphereGeometry />
</T.Mesh>
{/each}
{#each Array(colors.length) as _, i}
{@const r = Math.PI + increment * i}
<T.Group
position.x={radius * Math.cos(r)}
position.z={radius * Math.sin(r)}
oncreate={(ref) => {
groups.push(ref)
}}
>
<CubeCamera
{background}
{frames}
{near}
{resolution}
>
{#snippet children({ renderTarget })}
<T.Mesh>
<T.SphereGeometry />
<T.MeshStandardMaterial
{roughness}
{metalness}
envMap={renderTarget.texture}
/>
</T.Mesh>
{/snippet}
</CubeCamera>
</T.Group>
{/each}
{/await}

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>

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.

If you want full control over updates, use the update function available as a component export and through the children snippet.

If you use the update function, be sure to set frames to 0 to prevent the internal update task from starting automatically.

Scene.svelte
<script>
let cubeCameraComponent = $state()
$effect(() => {
// …
cubeCameraComponent?.update()
})
</script>
<CubeCamera
frames={0}
bind:this={cubeCameraComponent}
>
<!-- … -->
</CubeCamera>
Scene.svelte
<CubeCamera frames={0}>
{#snippet children({ update })}
<T.Mesh oncreate{update}>
<T.BoxGeometry />
</T.Mesh>
{/snippet}
</CubeCamera>

If you need to restart the update task, you can do so through the restart component export

<script>
let cubeCameraComponent = $state()
$effect(() => {
// dependencies here
cubeCameraComponent?.restart()
})
</script>
<CubeCamera
frames={1}
bind:this={cubeCameraComponent}
>
<!-- ... -->
</CubeCamera>

restart is also available through the children snippet.

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

<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.

The onupdatestart callback prop is called anytime the underlying update task has been started.

<CubeCamera
onupdatestart={() => {
console.log('update started')
}}
>
<!-- ... -->
</CubeCamera>

The onupdatestop callback fires anytime the update task has stopped. It is called on restarts and when the internal counter goes over the frames limit. This means that if frames is set to Infinity, as it is by default, onupdatestop is never called.

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

Props

name
type
required
default
description

autoStart
boolean
no
true
whether to automatically start the render task.

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

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

onupdatestop
() => void
no
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
description

restart
() => void
restarts the internal update task

update
() => void
causes the cube camera to update which renders to the render target