DEV Community

Cover image for Add an Image as Texture to a 3D Object in a React App
Abdullah Nadeem
Abdullah Nadeem

Posted on

Add an Image as Texture to a 3D Object in a React App

In this article, we will learn how to add a picture as a texture to a 3D object in a React application. We will use @react-three/fiber to accomplish this feat.
We will be assuming you already have a basic understanding of React and a new React application set up.

Step 1: Install Required Packages

Let's install the dependencies first. Run the following command in your terminal (make sure you are in the root directory of your project):

npm install three @react-three/fiber @react-three/drei

Here, three is the base library that react-three-fiber uses to create and render the 3D scene. @react-three/drei is a helper library for react-three-fiber that provides a set of reusable components and functions.

Step 2: Setup the Canvas

Now, import the Canvas from @react-three/fiber in your React component.

import { Canvas } from '@react-three/fiber';

Use the Canvas inside return like so:

const ShapeCanvas = () => {
  return (
    <Canvas frameloop="demand">
    </Canvas>
  );
};
Enter fullscreen mode Exit fullscreen mode

The frameloop prop set to demand is telling react-three-fiber to only re-render when state changes occur.

Step 3: Render a 3D Shape

Let's make another component and name it Shape. We will use this to render a 3D model and use it inside our Canvas.

We first use the Float component from @react-three/drei to give our model a floating effect. Import Float like so:

import { Float } from '@react-three/drei';

Inside the return statement, write:

<Float speed={1.75} rotationIntensity={1} floatIntensity={2}></Float>

  • speed refers to the animation speed.

  • rotationIntensity is the XYZ rotation intensity.

  • floatIntensity is the Up/Down float intensity.

We'll now use a mesh component which is a low-level react-three-fiber component used to create and manipulate 3D objects. Inside Float, write:

<mesh castShadow receiveShadow scale={1.75}></mesh>

We have allowed the mesh to cast and receive shadows and set its scale according to our use case.

A mesh is usually composed of a geometry and a material. So let's add these inside our mesh.

We will use an icosahedron geometry as our shape to render. Inside mesh, write:

<icosahedronGeometry args={[1, 1]} />

The icosahedron is a polyhedron with 20 identical equilateral triangular faces. The args={[1, 1]} prop specifies the radius and detail of the icosahedron.

Now, add a material inside mesh like so:

<meshStandardMaterial color="#fff8eb" polygonOffset polygonOffsetFactor={-5} flatShading />

  • color is used to specify a color for the material that will be used on our geometry.

  • polygonOffset and polygonOffsetFactor properties are used to avoid an artifact known as z-fighting, where two faces occupy the same space and create a flickering effect.

  • flatShading set to true means that the material will have a sharp and flat look instead of the default smooth one.

The Shape component now looks like this:

const Shape = () => {
  return (
    <Float speed={1.75} rotationIntensity={1} floatIntensity={2}>
      <mesh castShadow receiveShadow scale={1.75}>
        <icosahedronGeometry args={[1, 1]} />
        <meshStandardMaterial color="#fff8eb" polygonOffset polygonOffsetFactor={-5} flatShading /> 
      </mesh>
    </Float>
  );
};
Enter fullscreen mode Exit fullscreen mode

Now use this in ShapeCanvas:

const ShapeCanvas = () => {
  return (
    <Canvas frameloop="demand">
      <Shape />
    </Canvas>
  );
};
Enter fullscreen mode Exit fullscreen mode

Right now, we can see a dark shape in our browser:

3d model without light

Does this mean we did something wrong? Not really! We just need to add some lights to the scene to see our 3D shape clearly. So let's do that.

There are multiple options for us to add lights in a scene which you can explore here.

For now, we will use ambientLight and directionalLight. Write the following lines above the mesh.

<ambientLight intensity={0.25} />
<directionalLight position={[0, 0, 0.05]} />
Enter fullscreen mode Exit fullscreen mode

We have adjusted the intensity and position props for the lights according to our need. Our 3D shape now looks like this:

Illuminated 3D shape

Step 4: Add an Image as Texture to the Shape

To use an image as texture on our shape, we will use the useTexture hook from @react-three/drei. This hook is used to load one or several textures asynchronously. A texture, in this context, is essentially an image that gets mapped onto the surface of a 3D model. We use the hook as follows:

const [texture] = useTexture([threejs]);

threejs is just an svg that can be imported in our component.

We now use Decal from @react-three/drei to map the texture onto our model.

<Decal position={[0, 0, 1]} rotation={[2 * Math.PI, 0, 6.25]} scale={1} map={texture} flatShading />

  • position determines where the decal is located relative to the origin of the object it's applied to.

  • rotation determines the rotation of the decal around the x, y, and z axes in radians.

  • scale determines the size of the decal.

  • map specifies the texture/image to be used as the decal.

  • flatShading does the same thing it does to our material as explained before.

Our Shape component now looks like this:

import { Decal, Float, useTexture } from '@react-three/drei';
import { threejs } from '../../assets';

const Shape = () => {
  const [texture] = useTexture([threejs]);

  return (
    <Float speed={1.75} rotationIntensity={1} floatIntensity={2}>
      <ambientLight intensity={0.25} />
      <directionalLight position={[0, 0, 0.05]} />
      <mesh castShadow receiveShadow scale={1.75}>
        <icosahedronGeometry args={[1, 1]} />
        <meshStandardMaterial color="#fff8eb" polygonOffset polygonOffsetFactor={-5} flatShading />
        <Decal position={[0, 0, 1]} rotation={[2 * Math.PI, 0, 6.25]} scale={1} map={texture} flatShading />
      </mesh>
    </Float>
  );
};
Enter fullscreen mode Exit fullscreen mode

And that's all we need to add an image as a texture to our 3D object.

Step 5: Adding Interactivity

We can finally add some interactivity to make it more fun by using OrbitControls from @react-three/drei. In the ShapeCanvas component, import OrbitControls from @react-three/drei and use it like this:

const ShapeCanvas = () => {
  return (
    <Canvas frameloop="demand">
      <OrbitControls enableZoom={false} enablePan={false} />
      <Shape />
    </Canvas>
  );
};

Enter fullscreen mode Exit fullscreen mode

This will disable the zooming and panning of our 3D shape but will allow us to rotate it using our cursor.

Conclusion

In this article, we dove deep into integrating 3D objects within a React application using @react-three/fiber. We started from setting up a basic 3D canvas, progressing to rendering a 3D shape, and finally applying a textured image onto it. By adding the OrbitControls, we also introduced a layer of interactivity, letting users rotate the object. The combination of React with 3D holds vast potential, setting the stage for richer web interactions and experiences and @react-three/fiber is just the right place to start for you.

Top comments (0)