DEV Community

Cover image for How to Use Three.js And React to Render a 3D Model of Your Self
Nourdine Labib
Nourdine Labib

Posted on • Updated on • Originally published at blog.nourdinedev.com

How to Use Three.js And React to Render a 3D Model of Your Self

In this article, we’ll cover how to render and configure 3D assets created in a 3D software program like Blender or Maya in a React project using react-three-fiber. By the end of this article, you’ll be able to render 3D models (gltf / glb) on your website.

Get a 3D model of yourself

To get a customized 3D model, We well use Ready Player Me, a free-to-use 3D avatar creator from Wolf3D that allows anyone to create their own digital representation in a matter of minutes, no 3D modeling experience required. All you need to do is take a quick selfie and wait as the program automatically generates a custom 3D avatar based on your likeness.

You’re then free to make your own adjustments to the character using an okay range of hairstyles, skin tones, facial features, clothing options, and other customizable attributes.

After signing in to Ready Player Me, You need to follow the steps below and you good to go.

Choose a body type

Choose body type

Upload a photo of yourself

Upload a photo of yourself

Customize your look

Customize your look

Download your model

Download your model

Render the model in React

To render the model in our React app, We will use react-three-fiber a React renderer for Threejs.

Setting up the project

First, let’s create a new React project with Create React App:

npx create-react-app my-3d-model
#or
yarn create react-app my-3d-model
Enter fullscreen mode Exit fullscreen mode

Afterwards, install @react-three/fiber and @react-three/drei with the command below:

npm install three @react-three/fiber @react-three/drei
#or
yarn add three @react-three/fiber @react-three/drei
Enter fullscreen mode Exit fullscreen mode

Converting the model into a reusable React component

Once you’re done, go ahead and run the command below to create a javascript file using gltfjsx that plots out all of the assets contents in the format of a React functional component.

npx gltfjsx model.glb
Enter fullscreen mode Exit fullscreen mode

The file’s content will look similar to the following code:

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

export default function Model({ ...props }) {
   const group = useRef();
   const { nodes, materials } = useGLTF('/model.glb');
   return (
      <group ref={group} {...props} dispose={null}>
         <primitive object={nodes.Hips} />
         <skinnedMesh
            geometry={nodes.Wolf3D_Body.geometry}
            material={materials.Wolf3D_Body}
            skeleton={nodes.Wolf3D_Body.skeleton}
         />
         <skinnedMesh
            geometry={nodes.Wolf3D_Glasses.geometry}
            material={materials.Wolf3D_Glasses}
            skeleton={nodes.Wolf3D_Glasses.skeleton}
         />
         <skinnedMesh
            geometry={nodes.Wolf3D_Hair.geometry}
            material={materials.Wolf3D_Hair}
            skeleton={nodes.Wolf3D_Hair.skeleton}
         />
         <skinnedMesh
            geometry={nodes.Wolf3D_Outfit_Bottom.geometry}
            material={materials.Wolf3D_Outfit_Bottom}
            skeleton={nodes.Wolf3D_Outfit_Bottom.skeleton}
         />
         <skinnedMesh
            geometry={nodes.Wolf3D_Outfit_Footwear.geometry}
            material={materials.Wolf3D_Outfit_Footwear}
            skeleton={nodes.Wolf3D_Outfit_Footwear.skeleton}
         />
         <skinnedMesh
            geometry={nodes.Wolf3D_Outfit_Top.geometry}
            material={materials.Wolf3D_Outfit_Top}
            skeleton={nodes.Wolf3D_Outfit_Top.skeleton}
         />
         <skinnedMesh
            name="EyeLeft"
            geometry={nodes.EyeLeft.geometry}
            material={nodes.EyeLeft.material}
            skeleton={nodes.EyeLeft.skeleton}
            morphTargetDictionary={nodes.EyeLeft.morphTargetDictionary}
            morphTargetInfluences={nodes.EyeLeft.morphTargetInfluences}
         />
         <skinnedMesh
            name="EyeRight"
            geometry={nodes.EyeRight.geometry}
            material={nodes.EyeRight.material}
            skeleton={nodes.EyeRight.skeleton}
            morphTargetDictionary={nodes.EyeRight.morphTargetDictionary}
            morphTargetInfluences={nodes.EyeRight.morphTargetInfluences}
         />
         <skinnedMesh
            name="Wolf3D_Head"
            geometry={nodes.Wolf3D_Head.geometry}
            material={materials.Wolf3D_Skin}
            skeleton={nodes.Wolf3D_Head.skeleton}
            morphTargetDictionary={nodes.Wolf3D_Head.morphTargetDictionary}
            morphTargetInfluences={nodes.Wolf3D_Head.morphTargetInfluences}
         />
         <skinnedMesh
            name="Wolf3D_Teeth"
            geometry={nodes.Wolf3D_Teeth.geometry}
            material={materials.Wolf3D_Teeth}
            skeleton={nodes.Wolf3D_Teeth.skeleton}
            morphTargetDictionary={nodes.Wolf3D_Teeth.morphTargetDictionary}
            morphTargetInfluences={nodes.Wolf3D_Teeth.morphTargetInfluences}
         />
      </group>
   );
}

useGLTF.preload('/model.glb');
Enter fullscreen mode Exit fullscreen mode

creating the scene

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

export default function App() {
   return (
      <Canvas
         camera={{ position: [2, 0, 12.25], fov: 15 }}
         style={{
            backgroundColor: '#111a21',
            width: '100vw',
            height: '100vh',
         }}
      >
         <ambientLight intensity={1.25} />
         <ambientLight intensity={0.1} />
         <directionalLight intensity={0.4} />
         <Suspense fallback={null}>
            // your model here
         </Suspense>
         <OrbitControls />
      </Canvas>
   );
}
Enter fullscreen mode Exit fullscreen mode

Adding the model to the scene

First add the model (glb file) to the public folder, For the generated javascript file by gltfjsx you can add it either to the src folder or to the components folder.

import React, { Suspense } from 'react';
import { Canvas } from '@react-three/fiber';
import { OrbitControls } from '@react-three/drei';
import Model from './Model'; /* highlight-line */

export default function App() {
   return (
      <Canvas
         camera={{ position: [2, 0, 12.25], fov: 15 }}
         style={{
            backgroundColor: '#111a21',
            width: '100vw',
            height: '100vh',
         }}
      >
         <ambientLight intensity={1.25} />
         <ambientLight intensity={0.1} />
         <directionalLight intensity={0.4} />
         <Suspense fallback={null}>
            <Model position={[0.025, -0.9, 0]} /> /* highlight-line */
         </Suspense>
         <OrbitControls />
      </Canvas>
   );
}
Enter fullscreen mode Exit fullscreen mode
body {
   margin: 0;
   display: flex;
   align-items: center;
   justify-content: center;
   height: 100vh;
}
Enter fullscreen mode Exit fullscreen mode

result:

https://codesandbox.io/s/bold-wing-9w9n3i?file=/src/Model.js

Add animations to the model

To be able to add animations to your 3D model, You need to have blender installed in your machine.

Import the model to blender

Blender is the free and open source 3D creation suite. It supports the entirety of the 3D pipeline modeling, rigging, animation, simulation, rendering, compositing and motion tracking, even video editing and game creation.
learn more

Create a new blender project

new blender project

Clear the scene from all the objects

blank blender project

Import the glb file to blender

blender import glb file

blender import glb file

Select your model and click Import glTF 2.0

blender import glb file

Convert the model to fbx format

Before adding any animations to our model we need first to convert it into a FBX format.

Select the model

To select your 3D model in blender you only need to click on the letter a or you can use the mouse to do so.

blender model selection

Export the model as FBX

blender model export

blender model export config

Make sure to set Path Mode to Copy, and check the Embed textures option.

Adding animations with mixamo

Mixamo is a free online service for automatically rigging and animating 3-D characters. It was developed by Mixamo Incorporated, which was purchased by Adobe in 2015. Mixamo allows users to upload FBX, OBJ, or Zip files, and then the website attempts to automatically rig the character in under two minutes. The rigging process works best with humanoid characters.

Upload the model to mixamo

mixamo model upload

mixamo model upload

Select an animation and download the animated model

mixamo model animations

mixamo model download

Convert the animated model back to glb format

To use the model in our React app we need to change it back to glb format.

Import the animated model to blender

Import the animated model to blender

Export the animated model as glb

Export the animated model as glb

Rendering the animated model in React

In the public folder replace the model.glb file with the animated model, and add the changes below to src/Model.js file.

import React, { useRef, useEffect } from 'react'; /* highlight-line */
import { useGLTF, useAnimations } from '@react-three/drei'; /* highlight-line */

export default function Model({ ...props }) {
   const group = useRef();
   const { nodes, materials, animations } = useGLTF('/model.glb');

   const { actions } = useAnimations(animations, group); /* highlight-line */

   // 'Armature|mixamo.com|Layer0' is the name of the animation we need to run.
   // console.log(actions);

   useEffect(() => {/* highlight-line */
      actions['Armature|mixamo.com|Layer0'].play(); /* highlight-line */
   }); /* highlight-line */

   return (
      <group ref={group} {...props} dispose={null}>
         <primitive object={nodes.Hips} />
         <skinnedMesh
            geometry={nodes.Wolf3D_Body.geometry}
            material={materials.Wolf3D_Body}
            skeleton={nodes.Wolf3D_Body.skeleton}
         />
         <skinnedMesh
            geometry={nodes.Wolf3D_Glasses.geometry}
            material={materials.Wolf3D_Glasses}
            skeleton={nodes.Wolf3D_Glasses.skeleton}
         />
         <skinnedMesh
            geometry={nodes.Wolf3D_Hair.geometry}
            material={materials.Wolf3D_Hair}
            skeleton={nodes.Wolf3D_Hair.skeleton}
         />
         <skinnedMesh
            geometry={nodes.Wolf3D_Outfit_Bottom.geometry}
            material={materials.Wolf3D_Outfit_Bottom}
            skeleton={nodes.Wolf3D_Outfit_Bottom.skeleton}
         />
         <skinnedMesh
            geometry={nodes.Wolf3D_Outfit_Footwear.geometry}
            material={materials.Wolf3D_Outfit_Footwear}
            skeleton={nodes.Wolf3D_Outfit_Footwear.skeleton}
         />
         <skinnedMesh
            geometry={nodes.Wolf3D_Outfit_Top.geometry}
            material={materials.Wolf3D_Outfit_Top}
            skeleton={nodes.Wolf3D_Outfit_Top.skeleton}
         />
         <skinnedMesh
            name="EyeLeft"
            geometry={nodes.EyeLeft.geometry}
            material={nodes.EyeLeft.material}
            skeleton={nodes.EyeLeft.skeleton}
            morphTargetDictionary={nodes.EyeLeft.morphTargetDictionary}
            morphTargetInfluences={nodes.EyeLeft.morphTargetInfluences}
         />
         <skinnedMesh
            name="EyeRight"
            geometry={nodes.EyeRight.geometry}
            material={nodes.EyeRight.material}
            skeleton={nodes.EyeRight.skeleton}
            morphTargetDictionary={nodes.EyeRight.morphTargetDictionary}
            morphTargetInfluences={nodes.EyeRight.morphTargetInfluences}
         />
         <skinnedMesh
            name="Wolf3D_Head"
            geometry={nodes.Wolf3D_Head.geometry}
            material={materials.Wolf3D_Skin}
            skeleton={nodes.Wolf3D_Head.skeleton}
            morphTargetDictionary={nodes.Wolf3D_Head.morphTargetDictionary}
            morphTargetInfluences={nodes.Wolf3D_Head.morphTargetInfluences}
         />
         <skinnedMesh
            name="Wolf3D_Teeth"
            geometry={nodes.Wolf3D_Teeth.geometry}
            material={materials.Wolf3D_Teeth}
            skeleton={nodes.Wolf3D_Teeth.skeleton}
            morphTargetDictionary={nodes.Wolf3D_Teeth.morphTargetDictionary}
            morphTargetInfluences={nodes.Wolf3D_Teeth.morphTargetInfluences}
         />
      </group>
   );
}

useGLTF.preload('/model.glb');
Enter fullscreen mode Exit fullscreen mode

result:

https://codesandbox.io/s/3d-model-animation-d41e9u?file=/src/Model.js:271-281


Also read:
React Best Practices - Ways To Write Better Code in 2022
10 JavaScript One-Liners - Ways To Boost Your Productivity


My blog
My website
Find me on Upwork
Find me on twitter
Find me on linkedin
Find me on github

Discussion (33)

Collapse
martinlundqvist profile image
MartinLundqvist • • Edited on

Nice one! Only issue I have is that I lose the textures when exporting the fbx animation to glb from Blender in the final step.

  • I can see the textures in Mixamo and in Blender, so they are "there" so to speak.
  • Also, if I just use the ReadyPlayerMe model without adding any animations to it, then the textures also show.

Any advice?

Collapse
nourdinedev profile image
Nourdine Labib Author •

When you export the model as glb make sure you use this configurations.


I hope this fixes the problem.

Collapse
martinlundqvist profile image
MartinLundqvist •

Now only one problem remains.. the "position" property of the Model component doesn't seem to affect the position once the model is animating. Whenever I reload the app, the position will go back to the bottom of the model being in the center of the canvas.

Thread Thread
nourdinedev profile image
Nourdine Labib Author •

I'm sorry I didn't understand what problem you are having with the Model position, If you still didn't figure it out, provide me with some more feedback, and I will try my best to help.

Thread Thread
martinlundqvist profile image
MartinLundqvist • • Edited on

The "Model position={[0.2, -0.9, 0]}" positions the model as it should, when the model is not animating. However, as soon as I replace with the animated model, the position is somehow overridden and becomes [0,0,0]. I can see this behavior when I console.log(group.current.position).

Thread Thread
martinlundqvist profile image
MartinLundqvist •

Problem solved. By wrapping the group in a new parent group, and setting the position on the parent - I could solve the problem. The animation clearly reset the position of its group.

Thread Thread
nourdinedev profile image
Nourdine Labib Author •

I never had a problem like that before, glad you solved it.

Collapse
martinlundqvist profile image
MartinLundqvist •

Indeed it did. The only config that differed was "Selected Objects". When I checked this option, it started working.

Thanks!

Collapse
jhonrios85 profile image
jhonrios85 • • Edited on

I had the same problem, when I exported from Blender to FBX I lost the textures, what I did is:

  • Export the to FBX using blender
  • Add the animation in Mixamo and export the "Animated Version FBX" (Doesn't matter that the FBX have not textures, this is only to generate the animation)
  • Import the "Animated Version FBX" and the original GLB (with textures) to Blender
  • And follow these instructions to copy the animation from "Animated Version FBX" to "Original GLB with textures" gachokistudios.com/how-to-copy-ani...
  • Once you copy the animation, you can delete the "Animated Version FBX" from blender and export the original one (animated) to GLB and continue with the tutorial to include it to the project
Collapse
leminhung profile image
Lê Minh Hưng • • Edited on

i just follow your steps. When i compile "yarn start" then i receive this animation (character has no color). I don't know why. Can you please help fix this!

Collapse
nourdinedev profile image
Nourdine Labib Author • • Edited on

can you provide me with a link of a public Github repo that have your Code, and I will see if I can help.

Collapse
leminhung profile image
Lê Minh Hưng • • Edited on

Repo: github.com/leminhung/my-3d-model
This is my public github repo, Can you please clone repo and run this app to see the problems with color of animations.
Thank you so much!

Thread Thread
nourdinedev profile image
Nourdine Labib Author • • Edited on

Apparently when you added the animation to the model, the names of the materials changed, you need to generate a new Model.js file using gltfjsx.

npx gltfjsx model-with-animations.glb
Enter fullscreen mode Exit fullscreen mode

or you can use the code I did generate using gltfjsx.

import React, { useRef, useEffect } from 'react';
import { useGLTF, useAnimations } from '@react-three/drei';

export default function Model({ ...props }) {
   const group = useRef();
   const { nodes, materials, animations } = useGLTF(
      '/model-with-animations.glb'
   );
   const { actions } = useAnimations(animations, group);

   useEffect(() => {
      actions['Armature|mixamo.com|Layer0'].play();
   });
   return (
      <group ref={group} {...props} dispose={null}>
         <primitive object={nodes.Hips} />
         <skinnedMesh
            geometry={nodes.Wolf3D_Body.geometry}
            material={materials['Wolf3D_Body.001']}
            skeleton={nodes.Wolf3D_Body.skeleton}
         />
         <skinnedMesh
            geometry={nodes.Wolf3D_Hair.geometry}
            material={materials['Wolf3D_Hair.001']}
            skeleton={nodes.Wolf3D_Hair.skeleton}
         />
         <skinnedMesh
            geometry={nodes.Wolf3D_Outfit_Bottom.geometry}
            material={materials['Wolf3D_Outfit_Bottom.001']}
            skeleton={nodes.Wolf3D_Outfit_Bottom.skeleton}
         />
         <skinnedMesh
            geometry={nodes.Wolf3D_Outfit_Footwear.geometry}
            material={materials['Wolf3D_Outfit_Footwear.001']}
            skeleton={nodes.Wolf3D_Outfit_Footwear.skeleton}
         />
         <skinnedMesh
            geometry={nodes.Wolf3D_Outfit_Top.geometry}
            material={materials['Wolf3D_Outfit_Top.001']}
            skeleton={nodes.Wolf3D_Outfit_Top.skeleton}
         />
         <skinnedMesh
            name="EyeLeft"
            geometry={nodes.EyeLeft.geometry}
            material={nodes.EyeLeft.material}
            skeleton={nodes.EyeLeft.skeleton}
            morphTargetDictionary={nodes.EyeLeft.morphTargetDictionary}
            morphTargetInfluences={nodes.EyeLeft.morphTargetInfluences}
         />
         <skinnedMesh
            name="EyeRight"
            geometry={nodes.EyeRight.geometry}
            material={nodes.EyeRight.material}
            skeleton={nodes.EyeRight.skeleton}
            morphTargetDictionary={nodes.EyeRight.morphTargetDictionary}
            morphTargetInfluences={nodes.EyeRight.morphTargetInfluences}
         />
         <skinnedMesh
            name="Wolf3D_Head"
            geometry={nodes.Wolf3D_Head.geometry}
            material={materials['Wolf3D_Skin.001']}
            skeleton={nodes.Wolf3D_Head.skeleton}
            morphTargetDictionary={nodes.Wolf3D_Head.morphTargetDictionary}
            morphTargetInfluences={nodes.Wolf3D_Head.morphTargetInfluences}
         />
         <skinnedMesh
            name="Wolf3D_Teeth"
            geometry={nodes.Wolf3D_Teeth.geometry}
            material={materials['Wolf3D_Teeth.001']}
            skeleton={nodes.Wolf3D_Teeth.skeleton}
            morphTargetDictionary={nodes.Wolf3D_Teeth.morphTargetDictionary}
            morphTargetInfluences={nodes.Wolf3D_Teeth.morphTargetInfluences}
         />
      </group>
   );
}

useGLTF.preload('/model-with-animations.glb');
Enter fullscreen mode Exit fullscreen mode
Thread Thread
leminhung profile image
Lê Minh Hưng •

Ohh, problem was resolved. Thank you!

Collapse
nihaltuda profile image
nihal-tuda •

Uncaught Could not load ../public/breakDance.glb: Unexpected token < in JSON at position 0.
I'm getting this error and can't find a way to parse it as html. Need help!
I guess there is some issue with useGLTF function cuz when i inserted my avatar link it worked but it wasn't working when i used the path of .glb file in the useGLTF().

Collapse
nourdinedev profile image
Nourdine Labib Author •

can you upload it into a public git repo and give me the link so I can see what the problem is, or you can see my shared code on codesandbox at codesandbox.io/s/3d-model-animatio... and try to find if you missed any thing.

Collapse
maqi1520 profile image
Maik •

Thanks for sharing, May I translate this article into Chinese?

Collapse
nourdinedev profile image
Nourdine Labib Author •

Yeah no problem, you can translate the article.

Collapse
maqi1520 profile image
Maik •
Collapse
tythos profile image
Brian Kirkpatrick •

Cool stuff! I'll bet you could cut React out of this entirely and just use (say) ColladaLoader with THREE.js to port the same content.

Collapse
nourdinedev profile image
Nourdine Labib Author • • Edited on

Thanks!, I had used GLTFLoader outside of React but I never used the ColladaLoader, Over all if you have a collada model (.dae) you need to use the ColladaLoader, or if you have a model with other format you need to use a loader that supports the file type/format.

You are very much right, you can load any model with the power of Three.js only, thank you for pointing that out.

Collapse
jzombie profile image
jzombie •

This is radical! Thanks for sharing.

Collapse
nourdinedev profile image
Nourdine Labib Author •

Thank you, I appreciate that.

Collapse
robsonmuniz16 profile image
Robson Muniz •

Nice content, thanks for sharing it!

Collapse
luisnomad profile image
Luis Serrano 🇪🇺 •

Genius. Thank you for this! It's simply awesome.

Collapse
nourdinedev profile image
Nourdine Labib Author •

Thank you, I appreciate that.

Collapse
biomathcode profile image
Pratik sharma •

This is solid and really helpful. Thanks !!

Collapse
nourdinedev profile image
Nourdine Labib Author •

Thank you, glad this helped.

Collapse
xmannv profile image
Xman •

I could not import FBX to Mixamo with textures, it lost textures. I already followed by your note about export fbx. Don't know what's wrong

Can you check on repo: github.com/xmannv/avatar3d

Collapse
gullerg profile image
Gustav Grimberg •

I have the same issue...

Collapse
gullerg profile image
Gustav Grimberg •

I resolved this by downgrading Blender to 2.93

Collapse
gullerg profile image
Gustav Grimberg •

I lose the textures when I convert to fbx (the textures don't show in Mixamo...) - any idea why this happens?

Collapse
gullerg profile image
Gustav Grimberg •

Okay I resolved this by downgrading Blender to 2.93