Recent Post:
Categories:
July 13, 2024
159
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.
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
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.
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.
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.
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.