logologo

Three-D

☕Sign in

    Recent Post:

    Categories:

    nextjsjavascriptthreejshonoreactjs
    featured post image

    snippets

    featured post image

    What is Currying?

    featured post image

    What is an IIFE?

    Creating a 3D Interactive Blob-2 with Three.js and Nextjs

    July 13, 2024

    463

    image

    Think you know it all?

    Take quiz!

    In this blog post, I'll walk you through how I created an interactive 3D section on my blog using Three.js, @react-three/fiber, @react-three/drei, and @react-three/postprocessing. This project showcases a dynamic and engaging 3D scene featuring a rotating main sphere and animated background boxes. We'll go through the setup, components, and rendering process step by step.

    Setting Up the Project

    To start, make sure you have the following dependencies installed in your project:

    code

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

    Creating the Main Components

    The initialPositions array defines the starting positions of the spheres.

    1. MainSphere Component

    The MainSphere component is responsible for rendering the main sphere that rotates based on mouse movements. Here’s how it works:

    code

    'use client';
    
    import * as THREE from 'three';
    import React, { useRef } from 'react';
    import { useFrame } from '@react-three/fiber';
    import { Extrude } from '@react-three/drei';
    
    function MainSphere({ material }: { material: THREE.MeshStandardMaterial }) {
      const main = useRef<THREE.Mesh>(null);
    
      // main sphere rotates following the mouse position
      useFrame(({ clock, mouse }) => {
        if (main.current) {
          main.current.rotation.z = clock.getElapsedTime();
          main.current.rotation.y = THREE.MathUtils.lerp(main.current.rotation.y, mouse.x * Math.PI, 0.1);
          main.current.rotation.x = THREE.MathUtils.lerp(main.current.rotation.x, mouse.y * Math.PI, 0.1);
        }
      });
    
      return (
        <group>
          <Extrude ref={main} material={material} position={[0, 0, 0]} />
        </group>
      );
    }
    
    export default MainSphere;
    

    useRef : creates a reference to the mesh, allowing us to manipulate it directly.

    useFrame : is a hook from @react-three/fiber that runs on every frame. It updates the sphere’s rotation based on the elapsed time and the mouse position.

    THREE.MathUtils.lerp : smoothly interpolates the sphere's rotation towards the mouse position, creating a smooth, natural movement.

    2. Instances Component

    The Instances component creates multiple instances of boxes that move upwards continuously. It also includes the MainSphere component.

    code

    import React, { useState } from 'react';
    import { useFrame } from '@react-three/fiber';
    import { Box } from '@react-three/drei';
    import MainSphere from './MainSphere';
    
    function Instances({ material }: { material: THREE.MeshStandardMaterial }) {
      const [sphereRefs] = useState<Array<THREE.Mesh | null>>(() => []);
    
      const initialPositions = [
        [-4, 20, -12],
        [-10, 12, -4],
        [-11, -12, -23],
        [-16, -6, -10],
        [12, -2, -3],
        [13, 4, -12],
        [14, -2, -23],
        [8, 10, -20],
      ];
    
      // smaller spheres movement
      useFrame(() => {
        sphereRefs.forEach((el) => {
          if (el) {
            el.position.y += 0.02;
            if (el.position.y > 19) el.position.y = -18;
            el.rotation.x += 0.06;
            el.rotation.y += 0.06;
            el.rotation.z += 0.02;
          }
        });
      });
    
      return (
        <>
          <MainSphere material={material} />
          {initialPositions.map((pos, i) => (
            <Box
              args={[1, 4]}
              position={[pos[0], pos[1], pos[2]]}
              material={material}
              key={i}
              ref={(ref: THREE.Mesh | null) => {
                sphereRefs[i] = ref;
              }}
            />
          ))}
        </>
      );
    }
    
    export default Instances;
    

    Key Points:

    useState initializes an array to store references to the spheres (boxes).

    useFrame : animates the spheres' positions and rotations, creating a continuous upward movement. When a sphere moves out of view, it wraps around to the bottom.

    Scene Component

    The Scene component sets up the environment, loads textures, and manages the main material used for rendering.

    code

    import React, { useState } from 'react';
    import { useTexture, useCubeTexture, MeshDistortMaterial } from '@react-three/drei';
    import Instances from './Instances';
    
    interface DistortMaterialImpl extends ShaderMaterial {}
    
    function Scene() {
      const bumpMap = useTexture('/bump.png');
      const envMap = useCubeTexture(
        ['satyendra.png', 'satyendra.png', 'satyendra.png', 'satyendra.png', 'satyendra.png', 'satyendra.png'],
        { path: '/profile/' },
      );
    
      const [material, setMaterial] = useState<THREE.MeshStandardMaterial | null>(null);
    
      return (
        <>
          <MeshDistortMaterial
            ref={setMaterial}
            envMap={envMap}
            roughness={0.1}
          />
          {material && <Instances material={material} />}
        </>
      );
    }
    
    export default Scene;
    

    Key Points:

    `useTexture` and `useCubeTexture` load textures for bump mapping and environment mapping, respectively.

    `MeshDistortMaterial ` is a custom material that distorts the mesh and applies the loaded textures.

    `useState` manages the material state, ensuring it is available before rendering the `Instances` component.

    Rendering the Scene

    Finally, we render the scene using the `Canvas` component from @react-three/fiber and apply post-processing effects with `EffectComposer`.

    code

    import React from 'react';
    import { Canvas } from '@react-three/fiber';
    import { EffectComposer } from '@react-three/postprocessing';
    import { Html } from '@react-three/drei';
    import Scene from './Scene';
    
    export default function BlobComponent() {
      return (
        <div className="h-screen max-w-screen-2xl mx-auto">
          <Canvas camera={{ position: [1.2, 3.2, 3.2] }} gl={{ powerPreference: 'high-performance' }}>
            <EffectComposer multisampling={0} enableNormalPass={true}>
              <React.Suspense fallback={<Html center>Loading...</Html>}>
                <Scene />
              </React.Suspense>
            </EffectComposer>
          </Canvas>
        </div>
      );
    }
    

    Key Points:

    `Canvas` is the root component for rendering the 3D scene. It takes various props to configure the rendering context, such as camera position and WebGL settings.

    `EffectComposer` is used to apply post-processing effects to the scene, enhancing the visual output.

    `Suspense` handles the loading state, displaying a loading message until the scene is fully loaded.