@threlte/extras
<SoftShadows>
<SoftShadows> replaces Three.js’s default hard shadows with Percentage-Closer Soft Shadows (PCSS): shadows that are sharp where they touch their caster and soften with distance. Mount it alongside the rest of your scene — adding or removing it after the first render recompiles every shadow-receiving material.
<script lang="ts">
import { Canvas } from '@threlte/core'
import { Checkbox, Pane, Slider } from 'svelte-tweakpane-ui'
import Scene from './Scene.svelte'
let enabled = $state(true)
let size = $state(60)
let focus = $state(0)
let samples = $state(16)
</script>
<Pane
title="SoftShadows"
position="fixed"
>
<Checkbox
bind:value={enabled}
label="enabled"
/>
<Slider
bind:value={size}
label="size"
min={1}
max={100}
step={1}
/>
<Slider
bind:value={focus}
label="focus"
min={0}
max={2}
step={0.01}
/>
<Slider
bind:value={samples}
label="samples"
min={1}
max={32}
step={1}
/>
</Pane>
<div>
<Canvas>
<Scene
{enabled}
{size}
{focus}
{samples}
/>
</Canvas>
</div>
<style>
div {
height: 100%;
}
</style>
<script lang="ts">
import { T } from '@threlte/core'
import { Environment, OrbitControls, SoftShadows } from '@threlte/extras'
import Suzanne from './Suzanne.svelte'
interface Props {
enabled: boolean
size: number
focus: number
samples: number
}
let { enabled, size, focus, samples }: Props = $props()
</script>
<T.PerspectiveCamera
makeDefault
position={[0, 8, 15]}
fov={36}
>
<OrbitControls
enableZoom={false}
enableDamping
/>
</T.PerspectiveCamera>
<Suzanne />
<T.DirectionalLight
position={[5, 8, 4]}
castShadow
shadow.mapSize.width={1024}
shadow.mapSize.height={1024}
shadow.bias={0.0001}
/>
{#if enabled}
<SoftShadows
{size}
{focus}
{samples}
/>
{/if}
<Environment url="/textures/equirectangular/hdr/mpumalanga_veld_puresky_1k.hdr" />
<!--
Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf
Command: npx @threlte/gltf@1.0.1 ./Suzanne.glb -t -s
-->
<script lang="ts">
import { T, useTask } from '@threlte/core'
import { Float, useGltf } from '@threlte/extras'
import type * as THREE from 'three'
type GLTFResult = {
nodes: {
Suzanne: THREE.Mesh
Icosphere: THREE.Mesh
Cylinder: THREE.Mesh
Floor: THREE.Mesh
}
materials: {
Mat: THREE.MeshStandardMaterial
}
}
let rotation = $state(0)
useTask((dt) => {
rotation += dt
})
const gltf = useGltf<GLTFResult>('/models/Suzanne.glb')
</script>
<T.Group dispose={false}>
{#await gltf then gltf}
<Float
floatIntensity={10}
speed={2}
floatingRange={[0.15, 0.4]}
>
<T.Mesh
castShadow
receiveShadow
geometry={gltf.nodes.Suzanne.geometry}
material={gltf.materials.Mat}
rotation.x={-0.62}
rotation.y={rotation}
/>
</Float>
<Float
floatIntensity={8}
seed={1}
speed={3}
floatingRange={[0.2, 0.6]}
position={[2.2, 0, -0.5]}
>
<T.Mesh
castShadow
receiveShadow
geometry={gltf.nodes.Icosphere.geometry}
material={gltf.materials.Mat}
rotation.x={-0.62}
rotation.y={0.09 + rotation}
rotation.z={1.4 + rotation / 2}
/>
</Float>
<Float
floatIntensity={6}
seed={2}
speed={4}
floatingRange={[0.2, 0.5]}
position={[-2.4, 0, 0.2]}
>
<T.Mesh
castShadow
receiveShadow
geometry={gltf.nodes.Cylinder.geometry}
material={gltf.materials.Mat}
/>
</Float>
<T.Mesh
receiveShadow
geometry={gltf.nodes.Floor.geometry}
material={gltf.materials.Mat}
position={[0, -0.1, 0]}
/>
{/await}
</T.Group>