Use the same GLTF model twice in react-three-fiber/drei/Three.js
Asked Answered
F

4

7

In this minimal react-three-fiber App I am trying to load and include the same GLTF model twice:

import React, { Suspense } from "react";
import { Canvas } from "@react-three/fiber";
import { useGLTF } from "@react-three/drei";

function MyContent() {
  const firstGltf = useGLTF("/eye/scene.gltf");
  const secondGltf = useGLTF("/eye/scene.gltf");

  return (
    <>
      <primitive object={firstGltf.scene} position={[-200, 0, -400]} />
      <primitive object={secondGltf.scene} position={[200, 0, -400]} />
    </>
  );
}

export default function App() {
  return (
    <Canvas>
      <ambientLight color="white" intensity={0.5} />

      <Suspense fallback={null}>
        <MyContent />
      </Suspense>
    </Canvas>
  );
}

See this codesandbox

However only the second <primitive> is visible. If i remove the second <primitive>, then the first one is visible. I'm struggling to understand why this happens and how to do it better.

(Is it because the second call to useGLTF remembers that "/eye/scene.gltf" has already been loaded and returns the same object? And is this somehow messing up the usage with <primitive>, maybe because materials/geometries haven't been re-created a second time and exist only once?)

In particular, this is what I want to achieve:

  • use a gltf model multiple times on my canvas
  • ideally, load the gltf only once

On top of that, maybe you can help me to clarify these questions as well so I get a better understanding what's actually going on here:

  • Since I only want a 3D model, is it the right approach to work with a gltf.scene object?
  • Is <primitive> actually the correct approach to show the 3D model? Or should I somehow extract the geometries/textures from the scene and render them?

Thank you!

Folse answered 17/8, 2021 at 8:3 Comment(0)
B
6

I am not an expert on three.js, just based on what I find and try to answer your questions.


1. Only one eye is shown even there is 2 primitives defined

If you import the same model by using useGLTF(), it will refer to the same object. Therefore, the 2 primitives are pointing to the same gltf and only last/one config is applied.

const firstGltf = useGLTF("/eye/scene.gltf");
const secondGltf = useGLTF("/eye/scene.gltf");
const glassesGltf = useGLTF("/glasses/scene.gltf");

// for demonstrating first eye is same as second eye
// Output: false, true
console.log(firstGltf === glassesGltf, firstGltf === secondGltf);

2. Is <primitive> actually the correct approach to show the 3D model?

Yes, it is. but if you want to display the same gltf to the screen more than once, you need to create meshes and apply the model's geometries and materials so you can have a new object.

function Model(props) {
  const { nodes, materials } = useGLTF("/eye/scene.gltf");
  return (
    <group
      {...props}
      dispose={null}
      rotation={[Math.PI, 0, -Math.PI / 2]}
      scale={[1, 1, 1]}
    >
      <mesh
        geometry={nodes.Sphere001_Eye_0.geometry}
        material={materials.material}
      />
    </group>
  );
}
...
<Model position={[-1, 0, 1]} />
<Model position={[1, 0, 1]} />

Here is the codesandbox for demo


FYR:

You can use this library https://github.com/pmndrs/gltfjsx to generate jsx from the model.

Benham answered 17/8, 2021 at 15:16 Comment(1)
Is this approach more performant than the deep clone approach described above?Diarmid
B
4

You can't reuse meshes or put the same object into the scene twice in webgl/threejs, it will just unmount and remount. You can either:

  1. share the geometry: see this example https://codesandbox.io/s/re-using-gltfs-dix1y?file=/src/Shoe.js:48-55
  2. or deep clone the base object

Here is how to deep clone the base object, using useMemo:

interface ObjectProps {
  url: string;
  position: [x: number, y: number, z: number];
}

const Object = ({ url, position, ...props }: ObjectProps) => {
  const { scene } = useLoader(GLTFLoader, url)
  const copiedScene = useMemo(() => scene.clone(), [scene])

  return (
    <group>
      <primitive object={copiedScene} position={position} />
    </group>
  );
};

Ref: https://github.com/pmndrs/react-three-fiber/issues/245#issuecomment-745552102

Bryantbryanty answered 1/3, 2022 at 22:19 Comment(1)
Works like a charm. I needed to be able to create multiple instances of the scene without knowing what the geometry could be.Giorgio
A
0

Maybe you can use Clone from '@react-three/drei'

<>
  <Clone object={firstGltf.scene} position={[-200, 0, -400]} />
  <Clone object={secondGltf.scene} position={[200, 0, -400]} />
</>
Anking answered 31/8, 2023 at 2:47 Comment(0)
K
-1

Using scene.clone() fixed it for me, e.g.:

const { scene } = useGLTF(file);
<primitive ref={ref} object={scene.clone()} position={position} scale={scale} />
Kelpie answered 16/5 at 23:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.