@threlte/extras
<InstancedMesh>
The component <InstancedMesh>
is wrapping the Three.js object InstancedMesh and provides instanced rendering support. Use <InstancedMesh>
if you have to render a large number of objects with the same geometry and material but with different world transformations and colors. The usage of <InstancedMesh>
will help you to reduce the number of draw calls and thus improve the overall rendering performance in your application.
<script lang="ts">
import { Canvas } from '@threlte/core'
import Scene from './Scene.svelte'
</script>
<div>
<Canvas>
<Scene />
</Canvas>
</div>
<style>
div {
height: 100%;
}
</style>
<script lang="ts">
import { T, useTask } from '@threlte/core'
import { Instance, InstancedMesh } from '@threlte/extras'
let dn = Date.now()
useTask(() => (dn = Date.now()))
</script>
<InstancedMesh>
<T.SphereGeometry args={[0.5]} />
<T.MeshStandardMaterial color="white" />
<Instance
position.x={-2}
position.y={Math.sin(dn / 1000 + 40)}
/>
<Instance
position.x={-1}
position.y={Math.sin(dn / 1000 + 10)}
/>
<Instance
position.x={0}
position.y={Math.sin(dn / 1000 + 5)}
/>
<Instance
position.x={1}
position.y={Math.sin(dn / 1000 + 200)}
/>
<Instance
position.x={2}
position.y={Math.sin(dn / 1000 + 550)}
/>
</InstancedMesh>
<T.DirectionalLight
position.y={10}
position.z={5}
/>
<T.AmbientLight intensity={0.1} />
Usage
An <InstancedMesh>
is used in conjunction with the <Instance>
component:
<InstancedMesh>
<T.BoxGeometry />
<T.MeshStandardMaterial />
<Instance />
<Instance />
</InstancedMesh>
It’s also possible to nest other objects in an <InstancedMesh>
component:
<InstancedMesh>
<T.BoxGeometry />
<T.MeshStandardMaterial />
<Instance />
<Instance />
<GLTF />
</InstancedMesh>
Provide an id
to use multiple <InstancedMesh>
components:
<InstancedMesh id="tree">
<T is={treeGeometry} />
<T.MeshStandardMaterial map={treeTexture} />
<InstancedMesh id="leaf">
<T is={leafGeometry} />
<T.MeshStandardMaterial map={leafTexture} />
<T.Group position.x={1}>
<Instance id="tree" /> // Instance of InstancedMesh with id="tree"
<Instance id="leaf" /> // Instance of InstancedMesh with id="leaf"
</T.Group>
<T.Group position.x={-2}>
<Instance id="tree" />
<Instance id="leaf" />
</T.Group>
</InstancedMesh>
</InstancedMesh>
Instance count
Use the property limit
to set the maximum amount of <Instance>
components (defaults to 1000). The property limit
will be used to initialize the internally used Float32Array. Use the property range
to optionally limit the amount of drawn instances.
<InstancedMesh
limit={10000}
range={100}
>
<T.BoxGeometry />
<T.MeshStandardMaterial />
<Instance />
<Instance />
</InstancedMesh>
Events
Mouse around in the example below.
<script lang="ts">
import Scene from './Scene.svelte'
import { Canvas } from '@threlte/core'
import { Checkbox, Pane } from 'svelte-tweakpane-ui'
let paused = $state(false)
</script>
<Pane
position="fixed"
title="Instanced Colors"
>
<Checkbox
bind:value={paused}
label="paused"
/>
</Pane>
<Canvas>
<Scene {paused} />
</Canvas>
import { Color } from 'three'
import { Tween } from 'svelte/motion'
import { cubicOut } from 'svelte/easing'
export default class {
y = new Tween(0, { easing: cubicOut, duration: 250 })
scale = $derived(this.y.current + 1)
startColor = new Color()
endColor = new Color()
color = $derived(this.startColor.clone().lerpHSL(this.endColor, this.y.current))
constructor(
startColor: Color,
endColor: Color,
public x: number,
public z: number
) {
this.startColor.set(startColor)
this.endColor.set(endColor)
}
}
<script lang="ts">
import BallInstance from './BallInstance.svelte'
import { Color } from 'three'
import { DirectionalLight } from 'three'
import { Instance, InstancedMesh, interactivity } from '@threlte/extras'
import { T, useTask, useThrelte } from '@threlte/core'
let { paused = false }: { paused?: boolean } = $props()
const width = 10
const limit = width * width
const gap = 2.5
const offset = (width * gap) / 2
const startColor = new Color('blue')
const endColor = new Color('yellow')
const instances: BallInstance[] = []
for (let i = 0; i < limit; i += 1) {
const x = (i % width) * gap - offset
const z = Math.floor(i / width) * gap - offset
instances.push(new BallInstance(startColor, endColor, x, z))
}
const { size } = useThrelte()
const zoom = $derived($size.width / (1.5 * gap * width))
interactivity({
filter(items) {
// only report the first intersection
return items.slice(0, 1)
}
})
const light = new DirectionalLight()
const lightRadius = 10
const lightHeight = 5
let time = 0
const { start, stop } = useTask(
(delta) => {
time += delta
const x = lightRadius * Math.cos(time)
const z = lightRadius * Math.sin(time)
light.position.set(x, lightHeight, z)
light.lookAt(0, 0, 0)
},
{ autoStart: false }
)
$effect(() => {
if (!paused) {
start()
}
return () => {
stop()
}
})
</script>
<T.OrthographicCamera
position={[width, width, width]}
{zoom}
makeDefault
oncreate={(ref) => {
ref.lookAt(0, 0, 0)
}}
/>
<InstancedMesh
{limit}
range={limit}
>
<T.SphereGeometry />
<T.MeshToonMaterial />
{#each instances as instance}
<Instance
rotation.x={0.5 * Math.PI}
position.x={instance.x}
position.y={instance.y.current}
scale={instance.scale}
position.z={instance.z}
color={instance.color}
onpointerenter={() => {
instance.y.set(1)
}}
onpointerleave={() => {
instance.y.set(0)
}}
/>
{/each}
</InstancedMesh>
<T is={light} />
Instances also support interactivity events.
<InstancedMesh>
<T.BoxGeometry />
<T.MeshStandardMaterial />
<Instance onclick={onClick} />
</InstancedMesh>
Nesting
Instances can be nested in other objects and all parent transformations apply as usual:
<InstancedMesh>
<T.BoxGeometry />
<T.MeshStandardMaterial />
<T.Group rotation.z={DEG2RAD * 180}>
<Instance />
<T.Group position.y={2}>
<Instance />
</T.Group>
</T.Group>
</InstancedMesh>
Component Signature
<InstancedMesh>
extends
<T
.
InstancedMesh>
and supports all its props, slot props, bindings and events.