<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>Controlling Updates
Section titled “Controlling Updates”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.
Manual Updates
Section titled “Manual Updates”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.
As a Component Export
Section titled “As a Component Export”<script> let cubeCameraComponent = $state()
$effect(() => { // … cubeCameraComponent?.update() })</script>
<CubeCamera frames={0} bind:this={cubeCameraComponent}> <!-- … --></CubeCamera>Through Children Snippet
Section titled “Through Children Snippet”<CubeCamera frames={0}> {#snippet children({ update })} <T.Mesh oncreate{update}> <T.BoxGeometry /> </T.Mesh> {/snippet}</CubeCamera>Restarting the Task
Section titled “Restarting the Task”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>Scene Props
Section titled “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.
Callback Props
Section titled “Callback Props”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.