@threlte/gltf
Getting Started
A small command-line tool that turns GLTF assets into declarative and re-usable Threlte components.
The GLTF workflow on the web is not ideal.
- GLTF is thrown wholesale into the scene which prevents re-use, in threejs objects can only be mounted once
- Contents can only be found by traversal which is cumbersome and slow
- Changes to queried nodes are made by mutation, which alters the source data and prevents re-use
- Re-structuring content, making nodes conditional or adding/removing is cumbersome
- Model compression is complex and not easily achieved
- Models often have unnecessary nodes that cause extra work and matrix updates
@threlte/gltf
fixes that.
- It creates a virtual graph of all objects and materials. Now you can easily alter contents and re-use.
- The graph gets pruned (empty groups, unnecessary transforms, …) for better performance.
- It will optionally compress your model with up to 70%-90% size reduction.
Usage
npx @threlte/gltf@latest /path/to/Model.glb [options]
npx
at this moment. Options
Option | Description |
---|---|
--output, -o | Output file name/path |
--types, -t | Add Typescript definitions |
--keepnames, -k | Keep original names |
--meta, -m | Include metadata (as userData ) |
--shadows, -s | Let meshes cast and receive shadows |
--printwidth, -w | Prettier printWidth (default: 120) |
--precision, -p | Number of fractional digits (default: 2) |
--draco, -d | Draco binary path |
--preload -P | Add preload method to module script |
--suspense -u | Make the component suspense-ready |
--isolated, -i | Output as isolated module (No $$restProps usage) |
--root, -r | Sets directory from which .gltf file is served |
--transform, -T | Transform the asset for the web (draco, prune, resize) |
--resolution, -R | Transform resolution for texture resizing (default: 1024) |
--simplify, -S | Transform simplification (default: false, experimental) |
--weld | Weld tolerance (default: 0.0001) |
--ratio | Simplifier ratio (default: 0.75) |
--error | Simplifier error threshold (default: 0.001) |
--debug, -D | Debug output |
Example
This example assumes you have your model set up and exported from an application like Blender as a GLTF file.
First you run your model through @threlte/gltf
. npx
allows you to use npm packages without installing them.
npx @threlte/gltf@latest model.gltf --transform
This will create a Model.svelte
file that plots out all of the assets contents.
<!--
Auto-generated by: https://github.com/pmndrs/gltfjsx
Command: npx gltfjsx@0.0.1 ./stacy.glb
-->
<script>
import { Group } from 'three'
import { T } from '@threlte/core'
import { useGltf, useGltfAnimations } from '@threlte/extras'
let {
ref = $bindable(),
actions = $bindable(),
mixer = $bindable(),
children,
...props
} = $props()
const gltf = useGltf('/stacy.glb')
const animations = useGltfAnimations(gltf, ref)
actions = animations.actions
mixer = animations.mixer
</script>
{#if $gltf}
<T
is={ref}
{...props}
>
<T.Group name="Scene">
<T.Group
name="Stacy"
rotation={[Math.PI / 2, 0, 0]}
scale={0.01}
>
<T is={$gltf.nodes.mixamorigHips} />
<T.SkinnedMesh
name="stacy"
geometry={$gltf.nodes.stacy.geometry}
material={$gltf.nodes.stacy.material}
skeleton={$gltf.nodes.stacy.skeleton}
rotation={[-Math.PI / 2, 0, 0]}
scale={100}
/>
</T.Group>
</T.Group>
{@render children?.({ ref })}
</T>
{/if}
Add your model to your /static
folder as you would normally do. With the --transform
flag it has created a compressed copy of it (in the above case model-transformed.glb
). Without the flag just copy the original model.
static/
model-transformed.glb
The component can now be dropped into your scene.
<script>
import { Canvas } from '@threlte/core'
import Model from './Model.svelte'
</script>
<Canvas>
<Model />
</Canvas>
You can re-use it, it will re-use geometries and materials out of the box:
<Model position={[0, 0, 0]} />
<Model position={[10, 0, -10]} />
Or make the model dynamic. Change its colors for example:
<T.Mesh
geometry={$gltf.nodes.robot.geometry}
material={$gltf.materials.metal}
material.color="green"
/>
Or exchange materials:
<T.Mesh geometry={$gltf.nodes.robot.geometry}>
<T.MeshPhysicalMaterial color="hotpink" />
</T.Mesh>
Make contents conditional:
{#if condition}
<T.Mesh
geometry={$gltf.nodes.robot.geometry}
material={$gltf.materials.metal}
/>
{/if}
DRACO Compression
You don’t need to do anything if your models are draco compressed, since useGltf
defaults to a draco CDN. By adding the --draco
flag you can refer to local binaries which must reside in your /public folder.
Auto-Transform
With the --transform
flag it creates a binary-packed, draco-compressed, texture-resized (1024x1024), webp compressed, deduped, instanced and pruned *.glb ready to be consumed on a web site. It uses glTF-Transform. This can reduce the size of an asset by 70%-90%.
It will not alter the original but create a copy and append [modelname]-transformed.glb
.
Type-Safety
Add the --types
flag and your component will be typesafe.
<!--
Auto-generated by: https://github.com/pmndrs/gltfjsx
Command: npx gltfjsx@0.0.1 ./stacy.glb -t
-->
<script lang="ts">
import type * as THREE from 'three'
import { Group } from 'three'
import { T, type Props } from '@threlte/core'
import { useGltf, useGltfAnimations } from '@threlte/extras'
import type { Snippet } from 'svelte'
type Props = {
ref?: Group
actions?: ReturnType<typeof useGltfAnimations>['actions']
mixer?: ReturnType<typeof useGltfAnimations>['mixer']
children?: Snippet<[{ ref: Group }]>
}
let {
ref = $bindable()
actions = $bindable(),
mixer = $bindable(),
children,
...props
}: Props = $props()
const group = new Group()
type ActionName =
| 'pockets'
| 'rope'
| 'swingdance'
| 'jump'
| 'react'
| 'shrug'
| 'wave'
| 'golf'
| 'idle'
type GLTFResult = {
nodes: {
stacy: THREE.SkinnedMesh
mixamorigHips: THREE.Bone
}
materials: {}
}
const { actions, mixer } = useGltfAnimations<ActionName>(gltf, ref)
const gltf = useGltf<GLTFResult>('/stacy.glb')
actions = animations.actions
mixer = animations.mixer
</script>
{#if $gltf}
<T
bind:ref
is={group}
{...props}
>
<T.Group name="Scene">
<T.Group
name="Stacy"
rotation={[Math.PI / 2, 0, 0]}
scale={0.01}
>
<T is={$gltf.nodes.mixamorigHips} />
<T.SkinnedMesh
name="stacy"
geometry={$gltf.nodes.stacy.geometry}
material={$gltf.nodes.stacy.material}
skeleton={$gltf.nodes.stacy.skeleton}
rotation={[-Math.PI / 2, 0, 0]}
scale={100}
/>
</T.Group>
</T.Group>
{@render children?.({ ref })}
</T>
{/if}
Animations
If your GLTF contains animations it will add @threlte/extras’s useGltfAnimations
hook, which extracts all clips and prepares them as actions:
const gltf = useGltf('/stacy.glb') export const {(actions, mixer)} = useGltfAnimations(gltf, ref)
If you want to play an animation you can do so at any time:
const onEvent = () => {
$actions.jump.play()
}
Suspense
If you want to use the component <Suspense>
to suspend the rendering of loading components (and therefore models) and optionally show a fallback in a parent component component, you can do so by passing the flag --suspense
to make the generated Threlte component suspense-ready:
<script>
import Model from './Model.svelte'
import Fallback from './Fallback.svelte'
import { Suspense } from '@threlte/extras'
</script>
<Suspense>
{#snippet fallback()}
<Fallback />
{/snippet}
<Model />
</Suspense>
Asset Pipeline
In larger projects with a lot of models and assets, it’s recommended to set up an asset pipeline with tools like npm-watch
and Node.js scripts to automatically transform models to Threlte components and copy them to the right place as this makes iterating over models and assets much faster.