How to use a glb model multiple times when using GLTFLoader?

I am trying to understand how I can reuse models in ThreeJS / React Three Fiber

So let’s say I have a component Dude, where I load a model like so

import model from "../../assets/dude.glb";

and use it like so

const Dude = () => {
    const dudeRef  = useRef();
    const groupRef = useRef();
    const gltf = useLoader(GLTFLoader, model);

    return (
        <group ref={groupRef} position={[0, 6, 0]}>
            <primitive
                ref={dudeRef}
                object={gltf.scene}
                scale={[1, 1, 1]}
            />
        </group>
    );

If I load that model in other components, it will use the model that is already loaded the first time

I can make duplicate files and that works, so I know the problem isn’t the model itself

I tried cloning the scene and using clone method from SkeletonUtils, but it did not work

Does anyone know what might my problem be? Ty :heart:

NVM I was using clone method from SkeletonUtils incorrectly :skull:

This is what worked for

import { clone } from 'three/examples/jsm/utils/SkeletonUtils';

const Dude = () => {
    const dudeRef  = useRef();
    const groupRef = useRef();
    const gltf = useLoader(GLTFLoader, model);

    return (
        <group ref={groupRef} position={[0, 6, 0]}>
            <primitive
                ref={dudeRef}
                object={clone(gltf.scene)}
                scale={[1, 1, 1]}
            />
        </group>
    );

I was previously doing

gltf.scene.clone()

Which did not work for me

there is a misconception, both would be anti patterns.

your first example uses primitive, read this React Three Fiber Documentation

Scene objects can only ever be added once in Threejs. If you attempt to add one and the same object in two places Threejs will remove the first instance automatically. This will also happen with primitive! If you want to re-use an existing object, you must clone it first.

your second example does clone, but without usememo, so the whole model will be cloned when the component re-renders. btw skelleton clone is usually for skinned meshes.

function Dude(props) {
  const { scene } = useGltf(model)
  const clone = useMemo(() => scene.clone(), [scene])
  return <primitive object={clone} {...props} />
}

<Dude position={[0, 6, 0]} scale={1} />
<Dude position={[6, 0, 0]} scale={2} />

there’s two better ways,

1. using drei/Gltf

import { Gltf } from '@react-three/drei'

<Gltf src={model} position={[0, 6, 0]} scale={1} />
<Gltf src={model} position={[6, 0, 0]} scale={2} />

2. using gltfjsx

1 Like

Thank you for the hint :heart:

Is there a big enough advantage to using gltfjsx as compated to Gltf from drei? Reading the description page the big advantage from it seems to be it’s ability to compress models?

I tried using gltfjsx but the Model component it makes is not reusable for me for whatever reason

This is the command I used

npx gltfjsx@6.2.18 src/assets/dude.glb --name DudeModel --transform --simplify 

And it made me a Model component (I thought the named would be DudeModel) as well as a dude-transformed.glb

I moved that new glb file into src/assets/ and update path in Model


import React, { useRef } from 'react'
import { useGLTF, useAnimations } from '@react-three/drei'
import model from "../../assets/dude-transformed.glb";

export function Model(props) {
  const group = useRef()
  const { nodes, materials, animations } = useGLTF(model)

And I tried using it just like it was suggested in the docs

<Model key="model1" position={[10, 10, -60]}/>
<Model key="model2" position={[10, 10, -70]}/>

And it did not work, only let’s me place the 1 copy

Was there something I overlooked?

the purpose of gltfjsx is declarative re-usability

example 1 Re-using GLTFs - CodeSandbox
example 2 (skinned meshes cannot be re-used in threejs without skelletontools) https://codesandbox.io/p/sandbox/gltf-animations-re-used-k8phr?

ps, i might have to make the skeletontools thing automatic, it’s too complex and hidden. i think the only place where it comes up is here three.js docs

I see so even after using gltfjsx you have to adjust the Model component to make use of skeletontools, guess I’ll stick with using Gltf from drei per your first suggestion ty ty :heart:

i’ll push a change in a few minutes, it’s already working locally. it will take care of it. <Gltf uses skeletontools also but you’d use declarativity.

Is skinned instancing an option here? Not r3f based but the source looks straight forward enough to port a workable component from…

@iseafish69 it’s in

npx gltfjsx stacy.glb --transform
/*
Auto-generated by: https://github.com/pmndrs/gltfjsx
Command: npx gltfjsx@6.4.0 stacy.glb --transform --console 
Files: stacy.glb [1.87MB] > /Users/ph/Downloads/651190fd-3842-46db-b089-e27b20818530/public/stacy-transformed.glb [822.97KB] (56%)
*/

import React from 'react'
import { useGraph } from '@react-three/fiber'
import { useGLTF, useAnimations } from '@react-three/drei'
import { SkeletonUtils } from 'three-stdlib'

export function Model(props) {
  const group = React.useRef()
  const { scene } = useGLTF('/stacy-transformed.glb')
  const clone = React.useMemo(() => SkeletonUtils.clone(scene), [scene])
  const { nodes, materials, animations } = useGraph(clone)
  const { actions } = useAnimations(animations, group)
  return (
    <group ref={group} {...props} dispose={null}>
      <group name="Scene">
        <group name="Stacy" rotation={[Math.PI / 2, 0, 0]} scale={0.01}>
          <primitive object={nodes.mixamorigHips} />
        </group>
        <skinnedMesh name="stacy" geometry={nodes.stacy.geometry} material={nodes.stacy.material} skeleton={nodes.stacy.skeleton} />
      </group>
    </group>
  )
}

useGLTF.preload('/stacy-transformed.glb')
1 Like