Building Your First Browser Game with Three.js and React: Part 2 - Implementing 3D Models
Tags: webdev, beginners, threejs, react
Published: 2024-05-10 10:49:09

## Introduction

Welcome back to our series on building a 3D basketball game in the browser!
In Part 1, we set up our project with Vite and created a basic 3D scene with a cube.
Today, we're adding key elements to our game: the basketball and the table (or hoop stand).
By the end of this tutorial, you'll see these core game components come to life in our scene.

## Step 1: Preparing Your 3D Models

Before we start coding, ensure you have the 3D models for the basketball and the table.
We're using models prepared for Three.js (just created in Blender and exported in the right format), which are available for download below
(If these links don't lead directly to downloads, simply copy the entire page and paste the content into the files mentioned later.)

- [Download the basketball model](https://raw.githubusercontent.com/Romaixn/mini-basket/main/public/models/basketball.glb) (`basketball.glb`)
- [Download the table model](https://raw.githubusercontent.com/Romaixn/mini-basket/main/public/models/table.gltf) (`table.gltf`)

Create a new folder named `models/` within the `public/` directory and place the two models in it.
Now, the project contains these files: `public/models/basketball.glb` and `public/models/table.gltf`.

## Step 2: Creating the Ball Component

We will create two components using the [GLTFJSX tool](https://gltf.pmnd.rs/).
This tool transforms 3D models (in gltf format or others) into ready-to-use components for your **React Three Fiber** project. Try it yourself with our two models!

Let's start by adding the basketball to our scene. We'll use the `useGLTF` hook from `@react-three/drei` to load our model.
You can find everything drei has to offer on the [github of the project](https://github.com/pmndrs/drei), don't hesitate to go on the documentation if you want more explanation about what we use!

First, create a new `Components` folder within the `src/` directory to house the Ball and Table components and create a new `Ball.jsx` file inside:

```jsx
// src/Components/Ball.jsx

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

const Ball = ({ position }) => {
    const { nodes, materials } = useGLTF("/models/basketball.glb");

    return (
        <mesh
            position={position}
            geometry={nodes.Sphere.geometry}
            material={materials["Material.001"]}
            castShadow
            receiveShadow
        />
    );
};

useGLTF.preload("/models/basketball.glb");

export default Ball;
```

In this component, we're loading the basketball model and allow us to placing it at a specified position in the scene with the `position` prop.
The preload function ensures the model is lazy loaded before it's needed, improving the performance.

This component is straightforward, containing just a single **mesh**—a 3D object made up of vertices and polygons—equipped with specific **geometry** and **material**. These parameters can be found by logging `nodes` and `materials`, the two parameters that the function `useGLTF` give us. Additionally, we've included parameters such as `castShadow` and `receiveShadow` to control how the mesh casts and receives shadows from other objects.

## Step 3: Creating the Table Component

Next, we'll add the table. This is a significant component as it's more complex than the ball.

Create a new component named `Table.jsx`:

```jsx
// src/Components/Table.jsx

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

const Table = (props) => {
  const { nodes, materials } = useGLTF('/models/table.gltf');

  return (
    <group {...props} dispose={null}>
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.Table.geometry}
        material={materials.Wood}
        position={[0, 0.068, 0]}
      />
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.Controls.geometry}
        material={materials.Wood}
        position={[4.135, 0.092, -0.003]}
      />
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.Control_A.geometry}
        material={materials.Red}
        position={[4.184, 0.129, 0.744]}>
        <mesh
          castShadow
          receiveShadow
          geometry={nodes.Control_A_Text.geometry}
          material={materials.White}
          position={[0.237, 0.046, 0.21]}
          rotation={[Math.PI / 2, 1.179, -Math.PI / 2]}
        />
      </mesh>
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.Control_B.geometry}
        material={materials.Green}
        position={[4.183, 0.128, -0.754]}>
        <mesh
          castShadow
          receiveShadow
          geometry={nodes.Control_B_Text.geometry}
          material={materials.White}
          position={[0.25, 0.043, 0.207]}
          rotation={[Math.PI / 2, 1.184, -Math.PI / 2]}
        />
      </mesh>
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.Thruster_B.geometry}
        material={materials.Black}
        position={[2.259, -0.189, -0.764]}
      />
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.Thruster_A.geometry}
        material={materials.Black}
        position={[2.259, -0.189, 0.765]}
      />
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.Hide_Thruster.geometry}
        material={materials.Black}
        position={[2.257, -0.047, 0]}
      />
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.Base.geometry}
        material={materials.Wood}
        position={[-2.235, 0.565, 0]}
      />
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.Cylinder.geometry}
        material={materials.Red}
        position={[-2.235, 1.177, 0]}
      />
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.Panel.geometry}
        material={materials.Wood}
        position={[-2.234, 1.814, 0]}
      />
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.Ring.geometry}
        material={materials.Red}
        position={[-1.686, 1.46, 0]}
      />
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.Glass.geometry}
        position={[0.497, 1.54, 0.005]}
      >
        <MeshTransmissionMaterial anisotropy={0.1} chromaticAberration={0.04} distortionScale={0} temporalDistortion={0} />
      </mesh>
    </group>
  )
}

useGLTF.preload('/models/table.gltf');

export default Table;
```

This component loads the table model and all of its parts, such as the basket hop ring, controls, thrusters, and so on. You can identify each component by examining the **geometry** prop of each mesh. For instance, `nodes.Ring.geometry` corresponds to the ring of the basketball hoop!

We simply modify the glass part of the model (The mesh with the `nodes.Glass.geometry` geometry) by adding a custom material: the `MeshTransmissionMaterial` to achieve appealing glass aesthetics! To achieve this, we simply add a child to the **mesh** that contains the **Glass** geometry and remove the existing **material** from this mesh. This way, the mesh will use the material specified in its child.
We give it some default parameter for a good looking glass.

## Step 4: Integrating the Components into the Scene

Now, let's add both the Ball and Table components into our existing scene.

Update your `Experience.jsx` file to incorporate these new components and implement a few minor changes.

```jsx
import React from 'react';
import { Box, Center, Environment, OrbitControls } from '@react-three/drei';
import Table from './Components/Table';
import Ball from './Components/Ball';

const Experience = () => {
  return (
    <>
      <color attach="background" args={["#ddc28d"]} />

      <ambientLight />
      <directionalLight position={[0, 1, 2]} intensity={1.5} />
      <Environment preset="city" />

      <OrbitControls makeDefault />

      <Center>
        <Table position={[0, 0, 0]} />
        <Ball position={[0.25, 1.5, 0]} />
      </Center>
    </>
  );
}

export default Experience;
```

Now we have added these two new components inside another component from drei called `Center`.
As the name suggests, this component is very useful for centering 3D objects on the screen.

We also include a `<color>` component to set the scene's background color.
The transmission of our table's glass uses this to determine its background color.

We make slight adjustments to the lighting by replacing the point light with a directional light,
and we remove the parameter of the ambient light, opting to use the default one for a better view of our scene.
Lastly, we add an `Environment` component from drei to enhance the reflection on our table model.

## Step 5: Testing Your Scene

Launch your development server (if it's not already running) and navigate to your app.
You should see the basketball and table rendered in the scene.

## Conclusion

Great job! You've successfully incorporated complex 3D models into your basketball game.
This progress brings us one step closer to finalizing our interactive game.

In the next part of our series, we'll introduce interactivity and physics.

Stay tuned, and happy coding! 🏀