Basics

Loading Assets

A typical Threlte application will make use of textures, models, and other assets. These assets are loaded using the useLoader hook. Assets loaded via useLoader are automatically cached and will not be loaded or parsed again if they are already in the cache.

This section assumes you placed your assets in your public folder or in a place in your application where you can import them easily.

Loading Models

Models of different 3D model extensions can be loaded with their respective loaders. For this guide, we’re going to use the GLTFLoader to load a .gltf model. In this section we’re also going to discuss a few things that are specific to loading and caching models.

We start off by importing the GLTFLoader and useLoader hook:

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { useLoader } from '@threlte/core'

Then we can use the useLoader hook to load our model:

const gltf = useLoader(GLTFLoader).load('/assets/model.gltf')

Again, the type of gltf is an AsyncWritable custom store. Its value will be undefined until the model has loaded. To use this model, we can use a conditional:

{#if $gltf}
  <T is={$gltf.scene} />
{/if}

Reusing Models

We included the <T> component here to add the model to our Threlte application but immediately two problems arise:

  • The contents of the model are opaque to us. At the code level we don’t know what the model contains and we can’t access it.
  • The model is cached. If we load the same model again, we won’t get a new model but the same model we already loaded and we can’t place it in our scene again at a different location.

To fix this, we’ll use Threlte’s CLI tool @threlte/gltf to generate a reusable Svelte component from our model.

Run npx @threlte/gltf@latest /path/to/model.gltf to generate a Svelte component from your model. This will generate a file called Model.svelte in the same directory as your model. This is how a generated component looks like:

<!--
Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf
Command: npx @threlte/gltf@0.0.5 Flower.glb
-->
<script>
  import { Group } from 'three'
  import { T } from '@threlte/core'
  import { useGltf } from '@threlte/extras'

  export const ref = new Group()

  const gltf = useGltf('/Flower.glb')
</script>

{#if $gltf}
  <T
    is={ref}
    {...$$restProps}
  >
    <T.Mesh
      geometry={$gltf.nodes.Blossom.geometry}
      material={$gltf.materials.Blossom}
      rotation={[Math.PI / 2, 0, 0]}
      scale={1.22}
    />
    <T.Mesh
      geometry={$gltf.nodes.Stem.geometry}
      material={$gltf.materials.Stem}
      rotation={[Math.PI / 2, 0, 0]}
      scale={1.22}
    />

    <slot {ref} />
  </T>
{/if}

The generated component will still use the cache and will only reinstaniate parts of the model that make it reusable. This practice reduces network requests, bandwidth, memory usage and improves the performance of your Threlte application.

You can then import this component and use it in your application:

<script>
  import Model from './Model.svelte'
</script>

<!-- Use props to transform your model -->
<Model
  position.x={2}
  scale={2}
/>

<!-- Reuse it across your application -->
<Model
  position.x={10}
  scale={1.5}
  rotation.y={Math.PI / 2}
/>

<!-- Nest other components in it -->
<Model
  position.x={10}
  scale={1.5}
  rotation.y={Math.PI / 2}
>
  <!-- <OtherModel /> -->
</Model>

Convenient: useGltf

@threlte/extras provides a handy hook for loading one-off .gltf models called useGltf:

<script>
  import { useGltf } from '@threlte/extras'
</script>

{#await useGltf('/assets/model.gltf') then gltf}
  <T is={gltf.scene} />
{/await}

Model Is Cached

Keep in mind that this hook caches the result and therefore is most suitable for loading models that are placed in the scene only once.

Loading Textures

To start off, we import the TextureLoader from three and the useLoader hook from @threlte/core:

import { TextureLoader } from 'three'
import { useLoader } from '@threlte/core'

Then we can use the useLoader hook to load our texture:

const texture = useLoader(TextureLoader).load('/assets/texture.png')

The type of texture is a custom store that Threlte provides called AsyncWritable. It’s very much like a regular Svelte store but with a few extra features. Its value will be undefined until the texture has loaded. Once the texture has loaded, the value will be the loaded texture. To use this texture on a material, we can use a conditional:

{#if $texture}
  <T.MeshStandardMaterial map={$texture} />
{/if}

Since the underlying promise properties then and catch are exposed on the store itself, we can also make use of that and await the texture to be loaded:

<!-- Here, "texture" is acting like a regular promise -->
{#await texture then value}
  <T.MeshStandardMaterial map={value} />
{/await}

Loading multiple textures

Some materials are composed of multiple textures for different material channels. useLoader provides a way to load multiple textures at once and spread the loaded textures on a material. Let’s say we want to load a texture for the map and normalMap channels:

const textures = useLoader(TextureLoader).load({
  map: '/assets/texture.png',
  normalMap: '/assets/normal.png'
})

We can then spread the textures on a material via Svelte’s spread syntax:

{#if $textures}
  <T.MeshStandardMaterial {...$textures} />
{/if}

Keep in mind that the promise only resolves and the store gets populated once all textures have loaded.

Applying different textures to different faces

To declaratively apply two different textures to two different faces of a BoxGeometry, set the attach prop to a function.

<T.Mesh>
  <T.BoxGeometry />
  <T.MeshStandardMaterial
    map={texture1}
    attach={(parent, self) => {
      if (Array.isArray(parent.material)) parent.material = [...parent.material, self]
      else parent.material = [self]
    }}
  />
  <T.MeshStandardMaterial
    map={texture2}
    attach={(parent, self) => {
      if (Array.isArray(parent.material)) parent.material = [...parent.material, self]
      else parent.material = [self]
    }}
  />
</T.Mesh>

Alternatively, mix declaritive and normal three.js code like so for the same result:

<script>
  // imports and other code
  const customMaterials = [
    new MeshStandardMaterial({ map: texture1 }),
    new MeshStandardMaterial({ map: texture2 })
  ]
  // ...
</script>

<T.Mesh>
  <T.BoxGeometry />
  <T
    is={customMaterials}
    attach="material"
  />
</T.Mesh>

Convenient: useTexture

@threlte/extras provides a handy hook for loading textures called useTexture:

<script>
  import { useTexture } from '@threlte/extras'
</script>

{#await useTexture('/assets/texture.png') then texture}
  <T.MeshStandardMaterial map={texture} />
{/await}

Context Awareness

The hooks useLoader, useTexture and useGltf are context aware. This means that they will automatically use the context of a parent <Canvas> component to make sure assets are only cached in the context of the current Threlte app. You can’t use them outside of a <Canvas> component, in a component that is not a child of a <Canvas> component, or outside of component initialization. If you know how to use Svelte’s onMount hook then you know how to use useLoader and useTexture.

To load assets outside of a components initialization you can separate instantiating the loader from loading the asset all while making use of asset caching:

<script>
  import { AudioLoader } from 'three'
  import { useLoader } from '@threlte/core'

  // Instantiate the loader at component initialization
  const loader = useLoader(AudioLoader)

  const onSomeEvent = async () => {
    // Load the asset when needed
    const audioBuffer = await loader.load('/assets/sound.mp3')
  }
</script>