Recent Post:
Categories:
July 28, 2024
174
Think you know it all?
Take quiz!1. MainSphere Component
In this blog post, I'll walk you through creating another interactive 3D section for my blog using Three.js, @react-three/fiber, @react-three/drei, and @react-three/postprocessing. This project features a rotating main sphere and animated background boxes, similar to my previous Blob-2 project. We'll cover the setup, components, and rendering process step by step.
First, make sure you have the necessary dependencies installed in your project:
code
npm install three @react-three/fiber @react-three/drei @react-three/postprocessing
The MainSphere component renders the main sphere that rotates based on mouse movements.
code
'use client';
import * as THREE from 'three';
import React, { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import { Dodecahedron } 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>
<Dodecahedron args={[1, 4]} ref={main} material={material} position={[0, 1, 0]} />
</group>
);
}
export default MainSphere;
Key Points:
useRef creates a reference to the mesh, allowing direct manipulation.
useFrame 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.
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>>(() => []);
// Initial positions for the background boxes
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],
];
// Animate each sphere's position and rotation
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 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, useRef } from 'react';
import { useTexture, useCubeTexture, MeshDistortMaterial } from '@react-three/drei';
import Instances from './Instances';
import { ShaderMaterial } from 'three';
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}
bumpMap={bumpMap}
roughness={0.1}
metalness={1}
bumpScale={0.005}
clearcoat={1}
clearcoatRoughness={1}
radius={1}
distort={0.4}
/>
{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, { Suspense } from 'react';
import { Canvas } from '@react-three/fiber';
import { EffectComposer, DepthOfField, Bloom, Noise, Vignette } from '@react-three/postprocessing';
import { Html } from '@react-three/drei';
import Scene from './Scene';
function BlobComponent() {
return (
<div className="h-screen max-w-screen-2xl mx-auto">
<div className="bg-background h-full">
<Canvas
camera={{ position: [1.2, 3.2, 3.2] }}
gl={{
powerPreference: 'high-performance',
alpha: false,
antialias: false,
stencil: false,
depth: false,
}}
>
<Suspense fallback={<Html center>Loading...</Html>}>
<Scene />
</Suspense>
<EffectComposer multisampling={0} enableNormalPass={true}>
<DepthOfField focusDistance={0} focalLength={0.02} bokehScale={2} height={480} />
<Bloom luminanceSmoothing={0.9} height={300} opacity={3} />
<Noise opacity={-0.025} />
<Vignette eskil={false} offset={0.1} darkness={1.1} />
</EffectComposer>
</Canvas>
</div>
</div>
);
}
const BlobPage = () => {
return (
<div className="min-h-screen flex flex-col gap-10">
<p>Blob</p>
<div className="absolute top-0 bottom-0 left-0 right-0">
<BlobComponent />
</div>
</div>
);
};
export default BlobPage;
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.