Getting Started
Your First Scene
You should be versed in both Svelte and Three.js before rushing into Threlte. If you are unsure about Svelte, consult its Tutorial for a quick introduction. As for Threejs, make sure you at least glance over its official documentation.
Structuring Your App
As a first step we’re creating a new Svelte file called App.svelte
where we are importing the <Canvas>
component.
<script>
import { Canvas } from '@threlte/core'
import Scene from './Scene.svelte'
</script>
<Canvas>
<Scene />
</Canvas>
The <Canvas>
component is the root component of your Threlte application. It creates a
renderer and sets up some sensible defaults for you like antialiasing and color management.
It also creates a default camera and provides the context in which your Threlte application
will run. For improving access to this runtime context, it’s best practice
to create a seperate component called Scene.svelte
and including it in our App.svelte
file.
Creating Objects
At this point we’re looking at a blank screen. Let’s add a simple cube to it.
In Scene.svelte
, we’re importing the <T>
component which is
the main building block of your Threlte application. It’s a generic
component that we use to render any Three.js object. In this case we’re creating a
THREE.Mesh
which is made up from
a THREE.BoxGeometry
and
a THREE.MeshBasicMaterial
.
We should now be looking at a white cube on a transparent background.
<script>
import { T } from '@threlte/core'
</script>
<T.Mesh>
<T.BoxGeometry />
<T.MeshBasicMaterial />
</T.Mesh>
attach
Behind the scenes we’re using the property attach
available on <T>
to attach an object to a property of
its parent. Binding geometries to the property geometry
and materials to the property material
is a common
pattern so Threlte takes care of it for you.
Learn more
We’re using the property attach
available on <T>
to
attach an object to a property of its parent. In our case we’re attaching the underlying Three.js
object of <T.BoxGeometry>
to the property geometry
of the <T.Mesh>
component. We’re also attaching
the underlying Three.js object of <T.MeshBasicMaterial>
to the property material
of the <T.Mesh>
component.
<script>
import { T } from '@threlte/core'
</script>
<T.Mesh>
<T.BoxGeometry attach="geometry" />
<T.MeshBasicMaterial attach="material" />
</T.Mesh>
Binding geometries to the property geometry
and materials to the property material
is a common
pattern so Threlte will take care of it. It checks for the properties isMaterial
and isGeometry
on
the underlying Three.js object and attaches it to the correct property.
Three.js equivalent
// creating the objects
const geometry = new THREE.BoxGeometry()
const material = new THREE.MeshBasicMaterial()
const mesh = new THREE.Mesh()
// "attaching" the objects
mesh.geometry = geometry
mesh.material = material
Modifying Objects
That cube is still a bit boring. Let’s add some color to it, and make it a bit bigger! We also want to move it up a little to highlight it. We can do this by passing props to the
<T>
component.
<script>
import { T } from '@threlte/core'
</script>
<T.Mesh position.y={1}>
<T.BoxGeometry args={[1, 2, 1]} />
<T.MeshBasicMaterial color="hotpink" />
</T.Mesh>
Threlte automatically generates props for <T>
based on the underlying Three.js object. This means you can easily guess most <T>
props
based on the Three.js docs for the class you are using.
Props
The special args prop we use in <T.BoxGeometry>
corresponds to the object’s constructor arguments. Props interpreted from the underlying Three.js object are
called auto props, like color
in our <T.MeshBasicMaterial>
. Leveraging Svelte’s Pierced Props you can directly assigned to attributes of props like
position.y
in our <T.Mesh>
.
Learn more
args
In Three.js objects are classes that are instantiated. These classes can receive one-time constructor
arguments (new THREE.SphereGeometry(1, 32)
). In Threlte, constructor arguments are always passed as an
array via the prop args
. If args
change later on, the object must naturally get reconstructed from scratch!
Auto Props
For all other props, Threlte tries to automatically interpret props passed to <T>
component.
Step 1. Find Properties - First, Threlte will try to find the property on the underlying Three.js object based on the
name of the prop. In our example, color
is a property of THREE.MeshBasicMaterial
.
Step 2. Try set
Methods - Next, Threlte will look for a set
method on that property and use it to set the new value. In our example
it will call material.color.set('hotpink')
to set the color of our material.
Step 3. Try setting the property directly - If there’s no set
method, it will try to set the property directly.
Step 4. Check for array values - When setting a property that accepts more than one value (such as a THREE.Vector3
: vec3.set(1, 2, 3)
),
we can pass an array as a prop.
Pierced Props
Because the property position
of our THREE.Mesh
is a THREE.Vector3
, it also has
x
, y
and z
properties which we can set directly via dot-notation, we call this Pierced Props.
Three.js equivalent
const mesh = new THREE.Mesh()
const geometry = new THREE.BoxGeometry(1, 2, 1)
const material = new THREE.MeshBasicMaterial()
mesh.position.y = 1;
material.color.set('hotpink')
Primitive Values
From a performance perspective, it’s often better to use pierced props because primitive prop values can safely be compared for equality. This means that if the value of a prop doesn’t change, Threlte will skip any updates to the underlying Three.js object.
Pointing the Camera
We’re still staring at the side of a cube, let’s add a camera and offset it from the center:
<script>
import { T } from '@threlte/core'
</script>
<T.PerspectiveCamera
makeDefault
position={[10, 10, 10]}
on:create={({ ref }) => {
ref.lookAt(0, 1, 0)
}}
/>
<T.Mesh position.y={1}>
<T.BoxGeometry args={[1, 2, 1]} />
<T.MeshBasicMaterial color="hotpink" />
</T.Mesh>
We’re again using the <T>
component to create a THREE.PerspectiveCamera
.
We’re also passing a makeDefault
prop which will make this camera the default camera of our application.
The renderer now uses this camera to render our scene.
Events
Threlte supports listening to certain events on <T>
components. Here, we use the create
event to get a reference to the underlying Three.js object as soon as it’s created and use the method lookAt
to look at the cube.
Enabling Interactivity
Let’s say we want to scale our cube as soon as we hover over it. We first have to import the
plugin interactivity
from
@threlte/extras
and invoke it in our Scene.svelte file;
We can now add interaction event listeners to our <T>
components. We will add pointerenter
and
pointerleave
event listeners to our cube. In the event handlers we’ll update the value of a Svelte spring store
and apply the stores value to the property scale
of the component <T.Mesh>
.
<script>
import { T } from '@threlte/core'
import { 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, 1, 0)
}}
/>
<T.Mesh
position.y={1}
scale={$scale}
on:pointerenter={() => scale.set(1.5)}
on:pointerleave={() => scale.set(1)}
>
<T.BoxGeometry args={[1, 2, 1]} />
<T.MeshBasicMaterial color="hotpink" />
</T.Mesh>
Automatic Vector & Scalar Detection
You might have noticed that we’re only passing a single number to the prop scale
on <T.Mesh>
. Threlte automatically
figures out whether you are passing an array or a number and uses the appropriate underlying Three.js method.
Learn more
The component <T>
will first look for a property setScalar
on the underlying Three.js object and use that method if
only a single number is passed. This is equivalent to calling scale.setScalar($scale)
.
Realtime Variables
When working with realtime apps where variables e.g. position and rotation change constantly, an easy way observe the values is with live expressions.
Adding Animation
Let’s add some motion to our cube. We will use Threlte’s useFrame
hook to tap
into Threlte’s unified frame loop and run a function on every frame. We again use a Pierced Prop to let the
cube rotate around its y-axis.
<script>
import { T, useFrame } from '@threlte/core'
import { interactivity } from '@threlte/extras'
import { spring } from 'svelte/motion'
interactivity()
const scale = spring(1)
let rotation = 0
useFrame((state, delta) => {
rotation += delta
})
</script>
<T.PerspectiveCamera
makeDefault
position={[10, 10, 10]}
on:create={({ ref }) => {
ref.lookAt(0, 1, 0)
}}
/>
<T.Mesh
rotation.y={rotation}
position.y={1}
scale={$scale}
on:pointerenter={() => scale.set(1.5)}
on:pointerleave={() => scale.set(1)}
>
<T.BoxGeometry args={[1, 2, 1]} />
<T.MeshBasicMaterial color="hotpink" />
</T.Mesh>
useFrame
registers a callback that will be invoked on every frame. The function receives two arguments: the
current state of the Threlte application (the same context available via the
useThrelte
) and the time delta since the last frame. We use
the delta to update the rotation
independent of the frame rate
– the cube will rotate at the same speed regardless of the frame rate.
Adjusting the Lighting
We’re almost done. Let’s add some shading to our cube and a light source. We’ll use a
THREE.MeshStandardMaterial
on our cube and a THREE.DirectionalLight
to illuminate our scene.
<script>
import { T, useFrame } from '@threlte/core'
import { interactivity } from '@threlte/extras'
import { spring } from 'svelte/motion'
interactivity()
const scale = spring(1)
let rotation = 0
useFrame((state, delta) => {
rotation += delta
})
</script>
<T.PerspectiveCamera
makeDefault
position={[10, 10, 10]}
on:create={({ ref }) => {
ref.lookAt(0, 1, 0)
}}
/>
<T.DirectionalLight position={[0, 10, 10]} />
<T.Mesh
rotation.y={rotation}
position.y={1}
scale={$scale}
on:pointerenter={() => scale.set(1.5)}
on:pointerleave={() => scale.set(1)}
>
<T.BoxGeometry args={[1, 2, 1]} />
<T.MeshStandardMaterial color="hotpink" />
</T.Mesh>
Casting Shadows
We would like our cube to cast a shadow. To do so, we need a floor for it to cast a shadow on,
so we add a new <T.Mesh>
but this time with <T.CircleGeometry>
. To enable shadows, we need to
set castShadow
on both the light and our cube, and set receiveShadow
on our new floor:
<script>
import { T, useFrame } from '@threlte/core'
import { interactivity } from '@threlte/extras'
import { spring } from 'svelte/motion'
interactivity()
const scale = spring(1)
let rotation = 0
useFrame((state, delta) => {
rotation += delta
})
</script>
<T.PerspectiveCamera
makeDefault
position={[10, 10, 10]}
on:create={({ ref }) => {
ref.lookAt(0, 1, 0)
}}
/>
<T.DirectionalLight position={[0, 10, 10]} castShadow />
<T.Mesh
rotation.y={rotation}
position.y={1}
scale={$scale}
on:pointerenter={() => scale.set(1.5)}
on:pointerleave={() => scale.set(1)}
castShadow
>
<T.BoxGeometry args={[1, 2, 1]} />
<T.MeshStandardMaterial color="hotpink" />
</T.Mesh>
<T.Mesh rotation.x={-Math.PI/2} receiveShadow>
<T.CircleGeometry args={[4, 40]}/>
<T.MeshStandardMaterial color="white" />
</T.Mesh>
Conclusion
Congratulations, you’ve just created your first Three.js scene with Threlte! It includes important Three.js and Threlte concepts and should give you a good starting point for your first Threlte project.