Add an Image as Texture to a 3D Object in a React App
Tags: react, 3d, frontend, threejs
Published: 2023-08-17 09:31:57

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>
  );
};
```

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>
  );
};
```

Now use this in `ShapeCanvas`:

```
const ShapeCanvas = () => {
  return (
    <Canvas frameloop="demand">
      <Shape />
    </Canvas>
  );
};
```
Right now, we can see a dark shape in our browser:

![3d model without light](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bupdn4kttd9lou3z3frt.png)

**_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](https://docs.pmnd.rs/react-three-fiber/getting-started/your-first-scene#adding-lights).

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]} />
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e85n5ezkiiwxw3me0l59.png)

### 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>
  );
};
```

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>
  );
};

```
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.