Anna Ehrnsperger
Design Code

Embed 3D Objects with three.js and React

12 Oct 20

react-three-fiber is an easy way to use three.js with React. It allows us to display 3D models, animations and shapes in the browser. We can use reusable components, so the codebase is even more structured and clearer than with three.js.

Check out the demo!

Big thanks to pppanik for providing the awesome 3D model. 🌹

Setup

npx create-react-app my-app
cd my-app
npm install three react-three-fiber
npm start

react-three-fiber

Everything regarding 3D will live within the <Canvas />.

import React from 'react';
import { Canvas } from 'react-three-fiber';

const App = () => (
  <main>
    <Canvas>
    </Canvas>
  </main>
);

export default App;

Let's import the 3D model. The glTF format is perfectly suited for that. We will also import an additional package for react-three-fiber. drei has many useful helpers such as useGLTF.

First, we need a <mesh /> component, which allows us to display the object itself. Inside of that, we can specify a material for the object. More about materials.

Make sure your glTF file lives inside the public folder. Then import it via useGLTF. If you log out the constant gltf, it will help you to find the naming of geometry. Since the model is asynchronous, we need to use React Suspense. As a fallback, we can set a loading state.

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

const Model = () => {
  const gltf = useGLTF('/model.gltf');

  return (
    <mesh geometry={gltf.nodes.g_Group16190_Group161901.geometry}>
      <meshStandardMaterial />
    </mesh>
  );
};

const App = () => (
  <main>
    <Canvas style={{ position: 'absolute' }}>
      <OrbitControls />
      <Suspense fallback={<Html center><p>LOADING 3D</p></Html>}>
        <Model />
      </Suspense>
    </Canvas>
  </main>
);

export default App;

Because we also use OrbitControls, we can already interact with our model. But the model is pitch black! We have to add light.

I use a point light, whose position takes three parameters [x, y, z]. Also, I increased the intensity and changed the color to a blueish tone. More about light.

I also added a texture. Just import the image and use another helper form drei useTexture to apply it. Roughness and metalness give the object a shiny appearance.

import React, { Suspense } from 'react';
import { Canvas } from 'react-three-fiber';
import { OrbitControls, Html, useGLTF, useTexture } from '@react-three/drei';
import Texture from './texture.png';

const Model = () => {
  const { nodes } = useGLTF('/model.gltf');
  const texture = useTexture(Texture);

  return (
    <mesh geometry={nodes.g_Group16190_Group161901.geometry}>
      <meshStandardMaterial roughness={0.5} metalness={1} map={texture} />
    </mesh>
  );
};

const App = () => (
  <main>
    <Canvas style={{ position: 'absolute' }}>
      <OrbitControls />
      <pointLight position={[0, 200, 200]} intensity={2} color='#97ffff' />
      <Suspense fallback={<Html center><p>LOADING 3D</p></Html>}>
        <Model />
      </Suspense>
    </Canvas>
  </main>
);

export default App;

Finally we let it rotate! With the hook useFrame the animation runs 60 times per second, so we increase the rotation with every frame.

import React, { Suspense, useRef } from 'react';
import { Canvas, useFrame } from 'react-three-fiber';
import { OrbitControls, Html, useGLTF, useTexture } from '@react-three/drei';
import Texture from './texture.png';

const Model = () => {
  const { nodes } = useGLTF('/model.gltf');
  const texture = useTexture(Texture);
  
  const ref = useRef();

  useFrame(() => {
    ref.current.rotation.y += 0.02;
  });

  return (
    <mesh ref={ref} geometry={nodes.g_Group16190_Group161901.geometry}>
      <meshStandardMaterial roughness={0.5} metalness={1} map={texture} />
    </mesh>
  );
};

const App = () => (
  <main>
    <Canvas style={{ position: 'absolute' }}>
      <OrbitControls />
      <pointLight position={[0, 200, 200]} intensity={2} color='#97ffff' />
      <Suspense fallback={<Html center><p>LOADING 3D</p></Html>}>
        <Model />
      </Suspense>
    </Canvas>
  </main>
);

export default App;

Insights

You should make sure that both the glTF file and the textures are not too big, otherwise the website will load forever. There is a nice tool for glTF compression, that I've used. glTF Pipeline.

gltf-pipeline -i model.gltf -o modelDraco.gltf -d