Outlines
Implements the Outline postprocessing pass. Vanilla threejs example here
An outlined cube loops through a maze, with a different outline color when the object is hidden.
<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 { useTask, useThrelte } from '@threlte/core'
import {
BlendFunction,
EffectComposer,
EffectPass,
OutlineEffect,
RenderPass
} from 'postprocessing'
import { onMount } from 'svelte'
export let selectedMesh: THREE.Mesh
const { scene, renderer, camera, size, autoRender, renderStage } = useThrelte()
const composer = new EffectComposer(renderer)
const setupEffectComposer = (camera: THREE.Camera, selectedMesh: THREE.Mesh) => {
composer.removeAllPasses()
composer.addPass(new RenderPass(scene, camera))
const outlineEffect = new OutlineEffect(scene, camera, {
blendFunction: BlendFunction.ALPHA,
edgeStrength: 100,
pulseSpeed: 0.0,
visibleEdgeColor: 0xffffff,
hiddenEdgeColor: 0x9900ff,
xRay: true,
blur: true
})
if (selectedMesh !== undefined) {
outlineEffect.selection.add(selectedMesh)
}
composer.addPass(new EffectPass(camera, outlineEffect))
}
$: setupEffectComposer($camera, selectedMesh)
$: composer.setSize($size.width, $size.height)
onMount(() => {
let before = autoRender.current
autoRender.set(false)
return () => {
autoRender.set(before)
}
})
useTask(
(delta) => {
composer.render(delta)
},
{ stage: renderStage, autoInvalidate: false }
)
</script>
<script>
import { T } from '@threlte/core'
</script>
<T.Mesh
position={[6, 2, 4]}
rotation.y={Math.PI / 2}
>
<T.MeshStandardMaterial color="silver" />
<T.BoxGeometry args={[7, 4, 1]} />
</T.Mesh>
<T.Mesh
position={[-6, 2, 4]}
rotation.y={Math.PI / 2}
>
<T.MeshStandardMaterial color="silver" />
<T.BoxGeometry args={[7, 4, 1]} />
</T.Mesh>
<T.Mesh position={[-4, 2, 0]}>
<T.MeshStandardMaterial color="silver" />
<T.BoxGeometry args={[5, 4, 1]} />
</T.Mesh>
<T.Mesh position={[4, 2, 0]}>
<T.MeshStandardMaterial color="silver" />
<T.BoxGeometry args={[5, 4, 1]} />
</T.Mesh>
<T.Mesh position={[-3, 2, 7]}>
<T.MeshStandardMaterial color="silver" />
<T.BoxGeometry args={[7, 4, 1]} />
</T.Mesh>
<T.Mesh position={[5, 2, 7]}>
<T.MeshStandardMaterial color="silver" />
<T.BoxGeometry args={[3, 4, 1]} />
</T.Mesh>
<T.Mesh position={[-1, 2, 3.5]}>
<T.MeshStandardMaterial color="silver" />
<T.BoxGeometry args={[10, 4, 1]} />
</T.Mesh>
<script lang="ts">
import { onMount } from 'svelte'
import { quadInOut } from 'svelte/easing'
import { tweened } from 'svelte/motion'
import { T } from '@threlte/core'
import { OrbitControls, Grid } from '@threlte/extras'
import Maze from './Maze.svelte'
import CustomRenderer from './CustomRenderer.svelte'
const route = [
[0, 1, -3],
[0, 1, 1.5],
[4.7, 1, 1.5],
[4.7, 1, 5],
[2, 1, 5],
[2, 1, 9],
[8, 1, 9],
[8, 1, -3]
]
let routeIndex = 0
let cubePosition = tweened(route[routeIndex], {
duration: 400,
easing: quadInOut
})
let outlinedCube: THREE.Mesh
onMount(() => {
const interval = setInterval(nextCubePosition, 500)
return () => {
clearInterval(interval)
}
})
const nextCubePosition = () => {
if (routeIndex < route.length - 1) {
routeIndex++
} else {
routeIndex = 0
}
cubePosition.set(route[routeIndex])
}
</script>
<Maze />
<T.Mesh
position={$cubePosition}
bind:ref={outlinedCube}
>
<T.MeshToonMaterial color="gold" />
<T.BoxGeometry />
</T.Mesh>
<CustomRenderer selectedMesh={outlinedCube} />
<T.PerspectiveCamera
makeDefault
position={[0, 6, -10]}
fov={15}
zoom={0.2}
>
<OrbitControls
enableZoom={true}
enableDamping
target={[0, 0, 5]}
/>
</T.PerspectiveCamera>
<T.DirectionalLight
intensity={0.8}
position.x={5}
position.y={10}
/>
<T.AmbientLight intensity={0.2} />
<Grid
gridSize={18}
position={[0, -0.001, 5]}
cellColor="#ffffff"
sectionColor="#ffffff"
sectionThickness={0}
fadeDistance={25}
/>
How it works
- In
Scene.svelte
- Bind the mesh we want to outline, and pass it as prop
selectedMesh
toCustomRenderer
component
- Bind the mesh we want to outline, and pass it as prop
- Postprocessing is performed within
CustomRenderer
component- We use the ‘postprocessing’ library
- Create a new
EffectComposer
with Threlte’srenderer
- Then run our own render loop with this new render function, using
useTask
from threlte, make sure to setautoRender
tofalse
- Our function
setupEffectComposer
adds the requiredRenderPass
, andOutlinePass
to theEffectComposer
, specifically to ourMesh
object - This function will re-run if
selectedMesh
changes
- Animation of the cube is done with
svelte/motion
inScene.svelte