Skip to main content

πŸ—“οΈ 01062024 1921
πŸ“Ž #threejs #wip

load_models

Loading​

  • hamburger-draco.glb DRACO compressed
  • hamburger.glb non-DRACO compressed
  • R3F provides a hook named useLoader that abstract loading.
  • Need use a <primitive> to display models
    • Just some holder
import { useLoader } from "@react-three/fiber";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";

const model = useLoader(GLTFLoader, "./hamburger.glb");

export default function Experience() {
return (
<>
<Perf position="top-left" />

<OrbitControls makeDefault />

<directionalLight castShadow position={[1, 2, 3]} intensity={4.5} />
<ambientLight intensity={1.5} />

<mesh
receiveShadow
position-y={-1}
rotation-x={-Math.PI * 0.5}
scale={10}
>
<planeGeometry />
<meshStandardMaterial color="greenyellow" />
</mesh>
<primitive object={model.scene} scale={0.35} />
</>
);
}

DRACO​

NOTE

Need to instantiate a DRACOLoader class and add it to the GLTFLoader instance with setDRACOLoader()

import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";

const dracoModel = useLoader(GLTFLoader, "./hamburger-draco.glb", (loader) => {
console.log(loader);
});

Lazy loading​

Network throttling​

  • Activate network throttling to simulate IRL exp from dev tools (100Mbit/s in download works well in our case)

Suspense​

  • Showing a placeholder whilst loading the model
import { useLoader } from "@react-three/fiber";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";

export default function Model() {
const model = useLoader(
GLTFLoader,
"./FlightHelmet/glTF/FlightHelmet.gltf",
(loader) => {
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("./draco/");
loader.setDRACOLoader(dracoLoader);
}
);

return <primitive object={model.scene} scale={5} position-y={-1} />;
}
<Suspense fallback={<Placeholder />} />
<Model />
</Suspense>

Parent container

GLTF loading with drei​

TLDR

Drei implements multiple loader helpers like useGLTF and useFBX.

  • No need to specify decoder / handle DRACO compressed version, drei's helpers handles
const model = useGLTF("./hamburger-draco.glb");

Preloading​

  • Normally, the model would be loaded when the component is instantiated

  • For starting to load the instance

export default function Hamburger({ ...props }) {
// ...
}

useGLTF.preload("./hamburger-draco.glb");

Multiple instances​

  • Use drei's clone for multiple instances of the same object
import { Clone, useGLTF } from "@react-three/drei";

<Clone object={model.scene} scale={0.35} />;

export default function Model() {
// ...

return (
<>
<Clone object={model.scene} scale={0.35} position-x={-4} />
<Clone object={model.scene} scale={0.35} position-x={0} />
<Clone object={model.scene} scale={0.35} position-x={4} />
</>
);
}

If you check the performance monitoring, you’ll see that the amount of geometries and shaders stays the same Clone creates multiple meshes, it is still based on the same geometries and materials.

GLTF to component​

  • Converting a model to a jsx component for ease of update
  • Alternatives:
    • traverse the loaded model, search for the right child, save it in some ways and apply whatever we need to it
    • open it in a 3D software, change it and export it again
  • Command-line tool: https://github.com/pmndrs/gltfjsx
  • Online version: https://gltf.pmnd.rs/
// Auto-generated by: https://github.com/pmndrs/gltfjsx

import React, { useRef } from "react";
import { useGLTF } from "@react-three/drei";

export function Model(props) {
const { nodes, materials } = useGLTF("/hamburger.glb");
return (
<group {...props} dispose={null}>
<mesh
castShadow
receiveShadow
geometry={nodes.bottomBun.geometry}
material={materials.BunMaterial}
/>
<mesh
castShadow
receiveShadow
geometry={nodes.meat.geometry}
material={materials.SteakMaterial}
position={[0, 2.82, 0]}
/>
<mesh
castShadow
receiveShadow
geometry={nodes.cheese.geometry}
material={materials.CheeseMaterial}
position={[0, 3.04, 0]}
/>
<mesh
castShadow
receiveShadow
geometry={nodes.topBun.geometry}
material={materials.BunMaterial}
position={[0, 1.77, 0]}
/>
</group>
);
}

useGLTF.preload("/hamburger.glb");
NOTE

Fixing the shadow

  • The shadows look a bit weird with stripes crossing the surface of the hamburger
  • this is called shadow acne and it’s due to the model casting shadows on itself
  • We can fix this issue by tweaking the bias or shadowBias on the directional light shadow in Experience.jsx
<directionalLight
castShadow
position={[1, 2, 3]}
intensity={4.5}
shadow-normalBias={0.04}
/>

Animation​

  • Kronos Group in the glTF-Sample-Models GitHub repository.

Animation control and cleanup phase​

WARNING

For some reasons, going back to an animation that faded out won’t work and we need to reset it.

import { useAnimations, useGLTF } from "@react-three/drei";
import { useControls } from "leva";

export default function Fox() {
const fox = useGLTF("./Fox/glTF/Fox.gltf");
const animations = useAnimations(fox.animations, fox.scene);

const { animationName } = useControls({
animationName: { options: animations.names },
});

// useEffect(() => {
// const action = animations.actions.Run;
// action.play(); // React Three Fiber and the useAnimations helper will take care of updating the animation on each frame
// window.setTimeout(() => {
// animations.actions.Walk.play();
// // fox to start walking after a few seconds
// // fadeOut the Run and fadeIn the Walk:
// animations.actions.Walk.crossFadeFrom(animations.actions.Run, 1);
// }, 2000);
// }, []);

useEffect(() => {
const action = animations.actions[animationName];
action.play();
// Allows the animation to transition smoothly
action.fadeIn(0.5).play();
return () => {
action.fadeOut(0.5);
};
}, [animationName]);

return (
<primitive
object={fox.scene}
scale={0.02}
position={[-2.5, 0, 2.5]}
rotation-y={0.3}
/>
);
}