Basics
Render Modes
Threlte offers three different render modes to optimize the performance and power usage of your Threlte app. Ideally, you only want to render the scene when it is necessary, such as when the camera moves or when objects are added or removed from the scene. The render mode determines how and when the scene is rendered.
In the default 'on-demand'
mode, Threlte is able to
determine when a re-render is necessary by observing components. When setting
the render mode to 'manual'
you must manually trigger a
re-render. You can tell Threlte to continuously render the scene in the
'always'
mode.
'on-demand'
Mode <script lang="ts">
import { Canvas } from '@threlte/core'
import RenderIndicator from './RenderIndicator.svelte'
import Scene from './Scene.svelte'
</script>
<div class="wrapper">
<Canvas>
<Scene />
<RenderIndicator />
</Canvas>
<div class="description">
<p>
<strong>Click and drag</strong> to rotate the camera.
</p>
<p>
<strong>Hover</strong> over the sphere to scale it up.
</p>
</div>
</div>
<style>
div.wrapper {
height: 100%;
}
div.description {
position: absolute;
bottom: 10px;
left: 10px;
z-index: 10;
color: #fe3d00;
}
</style>
<script lang="ts">
import { useStage, useTask, useThrelte } from '@threlte/core'
import { Pane, WaveformMonitor } from 'svelte-tweakpane-ui'
const { shouldRender, renderStage } = useThrelte()
const afterRenderStage = useStage('after-render', {
after: renderStage
})
let log = Array(100).fill(0)
useTask(
() => {
log = update(log)
},
{
autoInvalidate: false,
stage: afterRenderStage
}
)
function update(log: number[]) {
log.shift()
log.push(shouldRender() ? 1 : 0)
return log
}
</script>
<Pane
title="Rendering Activity"
position="fixed"
>
<WaveformMonitor
value={log}
min={-1}
max={2}
/>
</Pane>
<script lang="ts">
import { T } from '@threlte/core'
import { Grid, OrbitControls, interactivity } from '@threlte/extras'
import { spring } from 'svelte/motion'
interactivity()
const scale = spring(1)
</script>
<T.PerspectiveCamera
makeDefault
position={[10, 10, 10]}
on:create={({ ref }) => {
ref.lookAt(0, 0, 0)
}}
>
<OrbitControls />
</T.PerspectiveCamera>
<T.DirectionalLight
position={[3, 10, 7]}
intensity={Math.PI}
/>
<T.AmbientLight intensity={0.3} />
<T.Group
scale={$scale}
on:pointerenter={() => scale.set(1.5)}
on:pointerleave={() => scale.set(1)}
>
<T.Mesh position.y={1}>
<T.SphereGeometry args={[1]} />
<T.MeshStandardMaterial
color="#FE3D00"
toneMapped={false}
/>
</T.Mesh>
</T.Group>
<Grid
cellColor="#FE3D00"
sectionColor="#FE3D00"
/>
In the mode 'on-demand'
, Threlte renders the scene only when the current frame
is invalidated. This may happen automatically when changes are
detected or the frame is manually
invalidated. This is the default mode and the recommended
way of working with Threlte.
Automatic Invalidation
Threlte is able to automatically invalidate the current frame by observing
component props and the mounting and unmounting of components. This means that
when you e.g. change the position of a <T.Mesh>
via component props, Threlte
will automatically invalidate the current frame and request a new frame.
<script>
import { T } from '@threlte/core'
let x = 0
const move = () => {
x += 1
}
</script>
<T.Mesh position.x={x} />
Manual Invalidation
In some cases, you may want to manually invalidate the current frame because
Threlte is not able to detect changes. To do this, you can use the invalidate
function from the useThrelte
hook.
<script>
import { T, useThrelte } from '@threlte/core'
import { Mesh } from 'three'
const { invalidate } = useThrelte()
const mesh = new Mesh()
export const moveMesh = () => {
// moving the mesh manually
mesh.position.x = 1
// invalidate the current frame
invalidate()
}
</script>
<T is={mesh} />
useTask
The useTask
hook is by default configured to
automatically invalidate the current frame on every frame. This means that you
can use it to animate your scene without having to manually invalidate the
current frame.
import { useTask } from '@threlte/core'
useTask(() => {
// useTask will automatically invalidate the current
// frame, so you don't have to do it manually.
})
Sometimes you may want to manually invalidate the current frame from within a
task. To do this, you can use the invalidate
function from the
useThrelte
hook and set the autoInvalidate
option to false
:
import { useTask, useThrelte } from '@threlte/core'
const { invalidate } = useThrelte()
useTask(
() => {
// Because `autoInvalidate` is set to `false`, the current
// frame will not be invalidated automatically and you can
// conditionally invalidate the current frame.
invalidate()
},
{ autoInvalidate: false }
)
'manual'
Mode In the manual mode, you must manually trigger a re-render:
const { advance } = useThrelte()
advance()
This mode is useful when you want to have full control over when the scene is rendered. For example, you may want to render the scene only when the user interacts with the scene.
'always'
Mode In the 'always'
mode, Threlte continuously renders the scene. This mode is the
easiest to use, but it is also the most resource intensive and should only be
used when necessary.
Setting the Render Mode
<Canvas>
Prop
You can set the render mode by setting the property renderMode
on the
<Canvas>
component:
<Canvas renderMode="on-demand" />
useThrelte
Hook
You can also set the render mode from anywhere within your Threlte app using the
useThrelte
hook:
const { renderMode } = useThrelte()
renderMode.set('on-demand')
The renderMode property can be changed at any time, but it will only take effect on the next frame.
Render Modes and Custom Rendering
By default, Threlte will automatically render the scene for you. In some cases, you may want to render the scene yourself, for example when using post processing.
- Set
autoRender
tofalse
on the<Canvas>
component. This will prevent Threlte from automatically rendering the scene and you can render the scene yourself.
<Canvas autoRender={false} />
- Set up a task that renders the scene. There are two ways to do this:
- Add a task to Threlte’s default
renderStage
. Tasks in that stage will be executed after tasks in Threlte’smainStage
and only when a re-render is necessary based on the current render mode. This is the recommended approach.
import { useTask, useThrelte } from '@threlte/core'
const { renderStage } = useThrelte()
useTask(
() => {
// render here
},
{ stage: renderStage, autoInvalidate: false }
)
- Use
shouldRender
from the hookuseThrelte
. This function will evaluate totrue
based on the current render mode. This allows for more fine-grained control over when to render and is useful when you want to render in a task that is not in Threlte’s defaultrenderStage
.
import { useThrelte, useTask } from '@threlte/core'
const { shouldRender } = useThrelte()
useTask(
() => {
if (shouldRender()) {
// render here
}
},
{ autoInvalidate: false }
)