@threlte/extras
<BackdropGeometry>
A mesh of a curved plane designed to look like a studio backdrop. Its intent is to break up light and shadows in more interesting ways.
<script lang="ts">
import Scene from './Scene.svelte'
import { Canvas } from '@threlte/core'
import { Checkbox, Color, Folder, Pane, Wheel } from 'svelte-tweakpane-ui'
let length = $state(0.25)
let segments = $state(20)
let materialColor = $state('#ffffff')
let materialWireframe = $state(false)
</script>
<Pane
position="fixed"
title=""
>
<Wheel
bind:value={length}
step={0.1}
label="length"
/>
<Wheel
step={1}
bind:value={segments}
label="segments"
/>
<Folder title="material props">
<Color
bind:value={materialColor}
label="color"
/>
<Checkbox
label="wireframe"
bind:value={materialWireframe}
/>
</Folder>
</Pane>
<div>
<Canvas>
<Scene
{length}
{materialColor}
{materialWireframe}
{segments}
/>
</Canvas>
</div>
<style>
div {
height: 100%;
}
</style>
<script lang="ts">
import {
MathUtils,
type ColorRepresentation,
type DirectionalLight,
type DirectionalLightHelper,
type Mesh,
type MeshStandardMaterial
} from 'three'
import {
BackdropGeometry,
Bounds,
Environment,
Gizmo,
OrbitControls,
TransformControls
} from '@threlte/extras'
import { useGltf } from '@threlte/extras'
import { isInstanceOf, T, useThrelte } from '@threlte/core'
interface Props {
length: number
segments: number
materialColor: ColorRepresentation
materialWireframe: boolean
}
let { materialColor = 'white', materialWireframe = false, length, segments }: Props = $props()
const gltf = useGltf<{
nodes: { LOD3spShape: Mesh }
materials: { 'blinn3-fx': MeshStandardMaterial }
}>('/models/Duck.glb').then((gltf) => {
gltf.nodes.LOD3spShape.castShadow = true
return gltf
})
const { scene } = useThrelte()
let helper = $state.raw<DirectionalLightHelper>()
let light = $state.raw<DirectionalLight>()
let debug = false
</script>
<Environment url="/textures/equirectangular/hdr/blouberg_sunrise_2_1k.hdr" />
<T.DirectionalLight
position.x={2}
position.y={0.5}
position.z={10}
intensity={2}
castShadow
shadow.camera.left={-10}
shadow.camera.right={10}
shadow.camera.top={-10}
shadow.camera.bottom={10}
shadow.bias={-0.001}
bind:ref={light}
>
{#snippet children({ ref })}
{#if debug}
<T.DirectionalLightHelper
bind:ref={helper}
args={[ref]}
attach={scene}
/>
<T.CameraHelper args={[ref.shadow.camera]} />
{/if}
{/snippet}
</T.DirectionalLight>
<TransformControls mode="scale">
<T.Mesh
receiveShadow
scale={20}
position.z={-5}
>
<BackdropGeometry
{length}
{segments}
/>
<T.MeshStandardMaterial
color={materialColor}
wireframe={materialWireframe}
receiveShadow
roughness={0.4}
metalness={0.1}
/>
</T.Mesh>
</TransformControls>
{#await gltf then { scene }}
<Bounds margin={0.5}>
{#each { length: 3 }, index}
<T.Group
scale={2}
position.z={Math.cos(index * MathUtils.degToRad(120)) * 4}
position.x={Math.sin(index * MathUtils.degToRad(120)) * 4}
position.y={-0}
rotation.y={Math.PI}
oncreate={(ref) => {
ref.lookAt(0, -0.2, 0)
}}
>
<T
is={scene.clone()}
rotation.y={-Math.PI / 2}
oncreate={(ref) => {
ref.traverse((child) => {
child.castShadow = true
child.receiveShadow = true
console.log(child)
if (isInstanceOf(child, 'Mesh')) {
const material = child.material
if (isInstanceOf(material, 'MeshStandardMaterial')) {
material.roughness = 0.1
}
}
})
}}
/>
</T.Group>
{/each}
</Bounds>
{/await}
<T.PerspectiveCamera
makeDefault
position.x={-30}
position.y={5}
position.z={10}
>
<OrbitControls
enableDamping
maxPolarAngle={0.5 * Math.PI}
minAzimuthAngle={-1 * 0.25 * Math.PI}
maxAzimuthAngle={0.25 * Math.PI}
>
<Gizmo />
</OrbitControls>
</T.PerspectiveCamera>
Props
floor controls the length of the “floor” segment of the geometry. The higher the value, the longer the segment.
receiveShadow controls whether the mesh receives shadows or not.
segments controls the resolution of the mesh.
Be aware that updating the segments prop causes the geometry to be rebuilt. It’s advised not to
update segments in hot-code paths such as useTask().
Component Signature
<BackdropGeometry> extends
<
T
.
PlaneGeometry
>
and supports all its props, snippets, bindings and events.