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>