Loading Models
Cientos gives you three ways to get a .glb/.gltf model on screen. They are not alternatives to
each other so much as three points on the same line: the further down this page you go, the more
control you get over the model's tree.
| Approach | Control | Typed | Best for |
|---|---|---|---|
<GLTFModel /> | Whole scene, as-is | No | Dropping a model in as-is |
useGLTF | Pick nodes and materials by hand | Optional | Reusing parts of a model |
tres gltf codegen | Every node is an element | Yes | Production scenes, interaction, per-node overrides |
Drop the model in
The fastest path. GLTFModel loads the file and renders its scene graph untouched:
<script setup lang="ts">
import { GLTFModel } from '@tresjs/cientos'
</script>
<template>
<GLTFModel path="/models/mug.glb" />
</template>
You get the whole model or nothing. There is no way to swap one mesh's material, attach a click handler to a door, or hide a node, because none of them exist as elements in your template.
Pick the parts you need
useGLTF hands you the parsed nodes and materials, so you compose the scene yourself:
<script setup lang="ts">
import { useGLTF } from '@tresjs/cientos'
const { nodes, materials, isLoading } = useGLTF('/models/mug.glb')
</script>
<template>
<TresMesh
v-if="!isLoading"
:geometry="nodes.Mesh.geometry"
:material="materials.coat"
@click="onClick"
/>
</template>
This is the right tool when you only want a couple of nodes out of a bigger file. For a whole model it stops scaling: you are hand-writing an element per node, re-reading the tree in Blender to find the names, and re-writing all of it when the artist re-exports.
nodes and materials are keyed by the names in the file, so their shape is only known at
runtime. Pass the shape as generics to get them typed, see Typed nodes and materials below.Generate a component with the CLI
@tresjs/cli writes that element-per-node component for you, typed, from the model itself:
npx @tresjs/cli gltf public/models/mug.glb
# ✔ src/models/Mug.gen.vue
# 1 slot: Mug
<script setup lang="ts">
/*
Auto-generated by @tresjs/cli. Do not edit.
Command: tres gltf public/models/mug.glb
Override the named slots from the parent instead; regenerating keeps your overrides.
*/
import type { Group, Mesh, MeshStandardMaterial } from 'three'
import { useGLTF } from '@tresjs/cientos'
interface ModelNodes {
Scene: Group
Mug: Group
Mesh: Mesh
Mesh_1: Mesh
}
interface ModelMaterials {
'4k-Metal-Worn': MeshStandardMaterial
'coat': MeshStandardMaterial
}
defineSlots<{
Mug?: (props: { node: Group }) => any
}>()
const { nodes, materials, isLoading } = useGLTF<ModelNodes, ModelMaterials>('/models/mug.glb', { draco: true })
defineExpose({ nodes, materials })
</script>
<template>
<TresGroup :dispose="null">
<template v-if="!isLoading">
<slot name="Mug" :node="nodes.Mug">
<TresGroup :position="[0, 1, 0]">
<TresMesh :geometry="nodes.Mesh.geometry" :material="materials['4k-Metal-Worn']" />
<TresMesh :geometry="nodes.Mesh_1.geometry" :material="materials.coat" />
</TresGroup>
</slot>
</template>
</TresGroup>
</template>
Import it like any other component:
<script setup lang="ts">
import { TresCanvas } from '@tresjs/core'
import Mug from '@/models/Mug.gen.vue'
</script>
<template>
<TresCanvas>
<TresPerspectiveCamera :position="[3, 2, 5]" />
<Mug />
<TresAmbientLight :intensity="1" />
</TresCanvas>
</template>
Overriding a node
Every named node is a <slot> whose fallback is the generated markup, so you change one mesh
from the parent without touching the generated file:
<template>
<Mug>
<template #Mug="{ node }">
<TresMesh :geometry="node.geometry" :material="hologram" @click="onClick" />
</template>
</Mug>
</template>
Regenerate after the artist re-exports and the override survives, because it never lived in the generated file. If the node is renamed, the override becomes a type error instead of quietly doing nothing at runtime.
*.gen.vue files as build output: edit the parent, not the generated file. The CLI refuses
to overwrite a file it did not generate, so a hand-edited one needs --force to regenerate.Typed nodes and materials
useGLTF takes two generics that type nodes and materials. The CLI writes them for you, but
you can also declare them by hand:
import type { Mesh, MeshStandardMaterial } from 'three'
import { useGLTF } from '@tresjs/cientos'
interface ModelNodes { Body: Mesh }
interface ModelMaterials { Skin: MeshStandardMaterial }
const { nodes, materials } = useGLTF<ModelNodes, ModelMaterials>('/models/robot.glb')
nodes.value.Body.geometry // Mesh, not any
Animated models
Clips live on the loaded state, and useAnimations turns
them into actions:
<script setup lang="ts">
import { useAnimations, useGLTF } from '@tresjs/cientos'
import { computed, ref, watch } from 'vue'
const { state, nodes, isLoading } = useGLTF('/models/knight.glb')
const modelRef = ref()
const animations = computed(() => state.value?.animations ?? [])
const { actions } = useAnimations(animations, modelRef)
watch(actions, current => current.Idle?.play())
</script>
<template>
<TresGroup ref="modelRef">
<primitive v-if="!isLoading" :object="nodes.Rig" />
</TresGroup>
</template>
A generated component wires all of that up and exposes actions keyed by a union of the model's
clip names, so actions.Idle type-checks and actions.Idl does not:
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import Knight from '@/models/Knight.gen.vue'
const knight = ref<InstanceType<typeof Knight>>()
onMounted(() => knight.value?.actions.Idle?.play())
</script>
<template>
<Knight ref="knight" />
</template>
ref mounted and gate its children on loading. A
ref on a v-ifed element is still undefined one flush after the clips land, and the mixer
would then be built with no root to bind against.Draco-compressed models
Pass draco: true and the loader pulls the decoder from Google's CDN, or point decoderPath at
a local copy:
const { state } = useGLTF('/models/mug.glb', { draco: true })
A Draco model renders nothing without it. The CLI detects compression while parsing and writes
{ draco: true } into the generated component for you.
Where to find models
- poly.pizza — free 3D models
- Pmndrs Market — free assets, curated
- KayKit — animated CC0 character packs