The start of my journey with 3D development

My first application with R3F/Three.js, starting with the basics.

Kien Dang
Kien DangJanuary 2, 2025

Initializing the project

I started off with creating a first scene following the docs of react-three/fiber. Here I encountered a compatibility problem between R3F and Next.js15. I decided to use Next.js14.2.16 since this version was the latest stable version.

npx create-next-app@14.2.16
npm install three @react-three/fiber

Setting up the application

I deleted everything in the page.js and global.css  and added our own HTML and CSS, to start with a nice blank application.

Setting up the component

"use client";

 

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

 

export default function FirstApp() {

  return (

    <Canvas>

      <mesh>

        <boxGeometry args={[2, 2, 2]} />

        <meshPhongMaterial />

      </mesh>

      <ambientLight intensity={0.1} />

      <directionalLight position={[0, 0, 5]} color="red" />

    </Canvas>

  );

}

 

Import into Page.js

import FirstApp from "./components/FirstApp/FirstApp";

 

export default function Home() {

  return (

    <div className="">

      <FirstApp />

    </div>

  );

}

 

This resulted in the following:

 

Making a basic moving mesh

Instead of using mesh in my FirstApp component I decided to make a component for it called Box.jsx. This will make it easier to easier to duplicate and more readable if I decide to replace it with a different component.

To add some interactivity to the application I’ve added hover and click functions to the code to change the scale and color of the mesh.

 "use client";

 

import { useRef, useState } from "react";

import { useFrame } from "@react-three/fiber";

 

export default function Box(props) {

  const ref = useRef();

  const [hovered, hover] = useState(false);

  const [clicked, click] = useState(false);

 

  useFrame((_, delta) => (ref.current.rotation.x += delta)); // Rotates the box along the x-axis

 

  return (

    <mesh

      {...props}

      ref={ref}

      scale={clicked ? 2 : 1}

      onClick={() => click(!clicked)}

      onPointerOver={() => hover(true)}

      onPointerOut={() => hover(false)}

    >

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

      <meshStandardMaterial color={hovered ? "hotpink" : "orange"} />

    </mesh>

  );

}

 

Updating FirstApp.jsx

Now that I’ve made the box component I can add multiple Box components. To make sure they are distinct I can change the positions of each component.

"use client";

 

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

import Box from "../Box/Box";

 

export default function FirstApp() {

  return (

    <Canvas>

      <ambientLight intensity={0.5} />

      // penumbra is the softness of the lighting

      <spotLight position={[10, 10, 10]} angle={0.45} penumbra={0.1} />

      <Box position={[-2, -2, -2]} />

      <Box position={[2, -2, -2]} />

    </Canvas>

  );

}

 

Result

Additionally I gave the body some additional styling.

 

Using React-Three/drei

To include more interactivity in the application I tried out some React-Three/drei. I started out with pasting this line in the terminal.

npm install @react-three/drei

Then I imported the components: OrbitControls, Text and Environment.

Updating FirstApp.jsx

"use client";

 

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

import { OrbitControls, Text, Environment } from "@react-three/drei";

import Box from "../Box/Box";

 

export default function FirstApp() {

  return (

    <Canvas>

      <ambientLight intensity={0.5} />

      // penumbra is the softness of the lighting

      <spotLight position={[10, 10, 10]} angle={0.45} penumbra={0.1} />

      <Box position={[-2, -2, -2]} />

      <Box position={[2, -2, -2]} />

      <OrbitControls />

      <Text position={[0, 2, 0]} fontSize={0.5} color="white">

        Hello, Drei!

      </Text>

      <Environment preset="apartment" background />;

    </Canvas>

  );

}

Result

Trying out different kinds of functions

To make it more interesting, I tried out some geometry, positioning, and RenderTexture. Tweaking around with the functions resulted in the following.

import React, { useRef } from "react";
import { useFrame } from "@react-three/fiber";
import { RenderTexture, PerspectiveCamera, Text } from "@react-three/drei";

export default function Example() {
  const textRef = useRef<THREE.Mesh>(null!);
  const dodecaRef = useRef<THREE.Mesh>(null!);

  // Animates the text
  useFrame((state) => {
    if (textRef.current) {
      textRef.current.position.x = Math.sin(state.clock.elapsedTime) * 4;
    }
  });

  return (
    <mesh>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial>
        {/* RenderTexture with Text and Dodecahedron */}
        <RenderTexture attach="map" anisotropy={16}>
          <color attach="background" args={["red"]} />
          <PerspectiveCamera
            makeDefault
            manual
            aspect={1 / 1}
            position={[0, 0, 5]}
          />
          <ambientLight intensity={0.5} />
          <directionalLight position={[5, 5, 5]} />
          {/* Animated Text */}
          <Text
            fontSize={2}
            color="white"
            ref={textRef}
            anchorX="center"
            anchorY="middle"
          >
            Hard to read?
          </Text>
          {/* Dodecahedron inside RenderTexture */}
          <mesh ref={dodecaRef} position={[0, 0, 0]} scale={1}>
            <dodecahedronGeometry args={[1, 0]} />
            <meshStandardMaterial color="orange" />
          </mesh>
        </RenderTexture>
      </meshStandardMaterial>
    </mesh>
  );
}

Adjusting the dodecahedron

I didn’t like the dodecahedron inside the face of the cubes; instead, I tried to have them floating around the cube. I added another mesh group and useFrame for the dodecahedrons. For the useFrame I've let an AI give me a random animation that could swirl around the cube. Resulting in this:

"use client";

import { useRef, useState } from "react";
import { useFrame } from "@react-three/fiber";
import { RenderTexture, PerspectiveCamera, Text } from "@react-three/drei";

export default function Cube(props) {
  const cubeRef = useRef();
  const dodecaGroupRef = useRef();
  const textRef = useRef();
  const [hovered, hover] = useState(false);
  const [clicked, click] = useState(false);

  useFrame((_, delta) => {
    if (cubeRef.current) {
      cubeRef.current.rotation.y += delta;
      cubeRef.current.rotation.z += delta * 0.5;
    }
  });

  useFrame((state) => {
    if (dodecaGroupRef.current) {
      const time = state.clock.elapsedTime;
      dodecaGroupRef.current.children.forEach((child, index) => {
        const angle = (index / 6) * Math.PI * 2;
        child.position.x = Math.cos(angle + time) * 2;
        child.position.y = Math.sin(angle + time) * 2;
        child.position.z = Math.sin(angle + time) * 2;
        child.rotation.x += 0.02;
        child.rotation.y += 0.02;
      });
    }
  });

  useFrame((state) => {
    if (textRef.current) {
      textRef.current.position.x = Math.sin(state.clock.elapsedTime) * 4;
    }
  });

  return (
    <group {...props} ref={cubeRef}>
      <mesh
        scale={clicked ? 1.5 : 1}
        onClick={() => click(!clicked)}
        onPointerOver={() => hover(true)}
        onPointerOut={() => hover(false)}
      >
        <boxGeometry args={[1, 1, 1]} />
        <meshStandardMaterial>
          <RenderTexture attach="map" anisotropy={16}>
            <color attach="background" args={["red"]} />
            <PerspectiveCamera
              makeDefault
              manual
              aspect={1 / 1}
              position={[0, 0, 5]}
            />
            <ambientLight intensity={0.5} />
            <Text
              fontSize={2}
              color="white"
              ref={textRef}
              anchorX="center"
              anchorY="middle"
            >
              Hard to read?
            </Text>
          </RenderTexture>
        </meshStandardMaterial>
      </mesh>

      <group ref={dodecaGroupRef}>
        {Array.from({ length: 6 }).map((_, index) => (
          <mesh key={index}>
            <dodecahedronGeometry args={[0.3, 0]} />
            <meshStandardMaterial color="orange" />
          </mesh>
        ))}
      </group>
    </group>
  );
}

Conclusion

So that's it for my first 3D project. This was just me trying out random stuff to see how it's like. I hope to try out much more within these libraries and much more complicated ones in the future. For now, let me know what kind of projects you would like to see, which one you've been working on yourself, or which you want to try out in the future! 🙂

Loading comments...