threlte logo
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.

App.svelte
<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.

Scene.svelte
<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.

Scene.svelte
<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.

Scene.svelte
<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.

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')

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 Threlte’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. In our example, this equated to mesh.position.y = 1.

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.

Step 5. Keep the prop type constant for the lifetime of the component - If the prop value changes, Threlte will try to set the property again. If the type of the prop value changes, Threlte won’t be able to reliably do that. For instance the type of the value of a variable that is used as a prop should not change from a single number to an array of numbers.

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.

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.

Constant prop types

The type of an inferred prop (or “auto prop”) must be constant. This means that the type of a prop must not change for the lifetime of the component. For instance you can’t use a variable as a prop that is an array of numbers and then later on change the value of that variable to a single number. This is considered a type change and therefore not allowed.

Pointing the Camera

We’re still staring at the side of a cube, let’s add a camera and offset it from the center:

Scene.svelte
<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>.

Scene.svelte
<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 useTask 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.

Scene.svelte
<script>
  import { T, useTask } from '@threlte/core'
  import { interactivity } from '@threlte/extras'
  import { spring } from 'svelte/motion'

  interactivity()
  const scale = spring(1)

  let rotation = 0
  useTask((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>

useTask registers a callback that will be invoked on every frame. The callback receives the time delta since the last frame as an argument. 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.

Scene.svelte
<script>
  import { T, useTask } from '@threlte/core'
  import { interactivity } from '@threlte/extras'
  import { spring } from 'svelte/motion'

  interactivity()
  const scale = spring(1)
  let rotation = 0
  useTask((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:

Scene.svelte
<script>
  import { T, useTask } from '@threlte/core'
  import { interactivity } from '@threlte/extras'
  import { spring } from 'svelte/motion'

  interactivity()
  const scale = spring(1)
  let rotation = 0
  useTask((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.