DEV Community

Uigla
Uigla

Posted on • Edited on

Three.js (fiber & drei) - 3D web3 serie

3D web3 Series
This is the 2nd post of 3D-web3 Series.

1 - Vite config and basic three.js
2 - Three.js (fiber & drei)
3 - Cannon physics
4 - Web3

Hey mate,

Drei pacakge is providing us ready-made abstractions for "Fiber"
Is a collection of useful helpers and fully functional DOM elements

npm i @react-three/drei

"Drei docs" has usseful index to check which functionalities are already available.

We'll be using:

  • From "Fiber": Canvas, useThree, useFrame
  • From "Drei": useGLTF, CameraShake, OrbitControls, Stars and Html
  • From "Three": Vector3

This post is divided in the following steps:
1_ Styling
2_ App "Canvas" with all the components
3_ Drei elements to create a background "sky", "stars"
4_ Importing 3D model into "canvas" (using "Suspense" and creation of ilumination)
5_ Creating two different "camera" uses

Step1_ Define "Canvas" viewport in App.css

.canvas {
  height: 100vh;
  width: 100vw;
}
Enter fullscreen mode Exit fullscreen mode

Step2_ Implement "Canvas" element imported from "fiber".
Comments in Code

import { Suspense, useState } from 'react';
import { Canvas } from '@react-three/fiber';
import { CameraShake, OrbitControls, Sky, Html } from '@react-three/drei';

import './App.css';
import { Lights } from './lights/Lights';
import CameraRig from './camera/CameraRig';
import Stars from './stars/Stars';
import GltfLoader from './gltf/GltfLoader';

const App = () => {

    const [cameraMode, setCameraMode] = useState(true);
    const [move, setmove] = useState(undefined);

    return (
        <div className='row'>
            <div className='col-lg-12 canvas'>
                <Canvas
                    camera={{
                        position: [0, 0, 0],
                        rotation: [0, 0, 0],
                    }}>
                    {/* Details in "Stars" component */}
                    <Stars />
                    {/* Drei component, creates a sky background (does not give ilumination to canvas elements) */}
                    <Sky distance={450000}
                        sunPosition={[2, 1, 10]}
                        inclination={0}
                        azimuth={0.25} />

                    {/* While importing gltf model, we will cath it in a "Suspense" */}
                    <Suspense fallback={null}>
                        {/* Our "GltfLoader" component takes a "position" and "cameraMove" setter to switch between two different camera controls*/}
                        <GltfLoader url={'chair/armchairYellow.gltf'} position={[0, -5, -15]} setCameraMode={() => { setCameraMode(!cameraMode) }} />
                    </Suspense>
                    {/* Several lights types are implemented in canvas */}
                    <Lights />

                    {/* switch between two different camera controls */}
                    {cameraMode ? <><OrbitControls
                        enableZoom={true}
                        enablePan={true}
                        enableRotate={true}
                    />
                        {/* HTML is added only in Orbitcontrols, because creates a conflict using with "CameraShake" or "CameraRig" */}
                        <Html
                            as='h4'
                            position={[0, 0, -15]}
                            transform={true}
                        >
                            <h4 className="htmlcanvas" >Click to switch between two different camera controls</h4>
                        </Html>
                    </>
                        :
                        <>
                            {/* Drei component, creates a balanced movement in our "camera" position */}
                            <CameraShake
                                yawFrequency={0.1}
                                pitchFrequency={0.1}
                                rollFrequency={0.1}
                            />
                            {/* Using "CameraRig", "camera" position reacts to our mouse movement */}
                            <CameraRig setmove={setmove} />
                        </>
                    }
                </Canvas>
            </div>
        </div>
    );
}

export default App
Enter fullscreen mode Exit fullscreen mode

Step3_ "sky", "stars" Drei elements to create a background

Stars.jsx
useRef to attach to "mesh" which is a built-in "DOM element" by fiber, which allows to manipulate the object inside.

import { Stars } from '@react-three/drei';
import { useFrame } from '@react-three/fiber';
import { useRef } from 'react';

const StarsBG = () => {

    const ref = useRef();
    const ref2 = useRef();

    useFrame(() => {
        ref.current.rotation.y += 0.001
        ref2.current.rotation.y -= 0.0005
    })

    return (
        <>

            <Stars
                ref={ref}
                radius={4}
                depth={100}
                count={5000}
                factor={3}
                saturation={0.9}
                fade
                speed={.1}
                color={3}
            />
            <Stars
                ref={ref2}
                radius={2}
                depth={200}
                count={5000}
                factor={3}
                saturation={0.9}
                fade
                speed={.2}
                color={3}
            />
        </>
    );
}

export default StarsBG;


Enter fullscreen mode Exit fullscreen mode

Sky component is already defined in our "Canvas"

<Sky distance={450000}
                        sunPosition={[2, 1, 10]}
                        inclination={0}
                        azimuth={0.25} />
Enter fullscreen mode Exit fullscreen mode

Step4_ Importing 3D model into "canvas" (using "Suspense" and creation of ilumination)

We'll be using "useGLTF" from "drei". Provide a url and "/draco-gltf" as parameters and define it in a "primitive" element

GltfLoader.jsx

Pass onClick atribute to call setCameraMode method. To switch between two different camera controls (explained in step5)

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

function GltfLoader(props) {
  const gltf = useGLTF(props.url, "/draco-gltf");
  useGLTF.preload(props.url);
  return (
    <primitive
      scale={0.05}
      object={gltf.scene}
      dispose={null}
      position={props.position}
      onClick={props.setCameraMode}
    />
  );
}
export default GltfLoader;

Enter fullscreen mode Exit fullscreen mode

Wrap it using "Suspense" to handle the process while importing gltf model

Now, create a ilumination using several fiber elements.
Use "useRef" to give rotation to the ilumination using "useFrame"

Lights.jsx

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

export const Lights = () => {

    const ref = useRef();
    useFrame(() => {
        // give rotation to the ilumination
        ref.current.rotation.y -= 0.005
    })

    return (
        <group
            ref={ref}>
            {/* Ambient Light illuminates lights for all objects */}
            <ambientLight intensity={0.3} />
            {/* Diretion light */}
            <directionalLight position={[10, 10, 5]} intensity={1} />
            <directionalLight
                castShadow
                position={[0, 10, 0]}
                intensity={1.5}
                shadow-mapSize-width={1024}
                shadow-mapSize-height={1024}
                shadow-camera-far={50}
                shadow-camera-left={-10}
                shadow-camera-right={10}
                shadow-camera-top={10}
                shadow-camera-bottom={-10}
            />
            {/* Spotlight Large overhead light */}
            <spotLight intensity={1} position={[1000, 0, 0]} castShadow />
        </group>
    );
};

Enter fullscreen mode Exit fullscreen mode

Step5_ Creating two different "camera" uses
We're going to use to states:
1_ move (to set camera movement each frame)
2_ cameraMode (to switch between two different camera controls)

Using if else in one line to switch mode
var variable = (condition) ? (true option1) : (else option2)

Option1_ we'll be using following elements from "drei" in our "Canvas":
"OrbitControls" - Give us camera control: rotation (mouse left botton) and position (mouse right botton)

"Html" - Enable us using basic DOM Nodes to provide a text

"Html" creates a conflict if we use with "CameraShake" or "CameraRig". That's why we are only showing text in option1

Option2_ we'll be using following elements:
"CameraShake" from "drei" - creates a balanced movement in our "camera" Vector3
"CameraRig" (created by us). But, first check the code.

This code is already in App.jsx "canvas" element

<Canvas>
...code
{cameraMove ? <><OrbitControls
                        enableZoom={true}
                        enablePan={true}
                        enableRotate={true}
                    />
                        {/* HTML is added only in Orbitcontrols, because creates a conflict using with "CameraShake" or "CameraRig" */}
                        <Html
                            as='h4'
                            position={[0, 0, -15]}
                            transform={true}
                        >
                            <h4 className="htmlcanvas" >Click to switch between two different camera controls</h4>
                        </Html>
                    </>
                        :
                        <>
                            {/* Drei component, creates a balanced movement in our "camera" position */}
                            <CameraShake
                                yawFrequency={0.1}
                                pitchFrequency={0.1}
                                rollFrequency={0.1}
                            />
                            {/* Using "CameraRig", "camera" position reacts to our mouse movement */}
                            <CameraRig setmove={setmove} />
                        </>
                    }
                </Canvas>
Enter fullscreen mode Exit fullscreen mode

Last, use "lerp" algorithm to do a linear interpolation in our camera "Vector3" imported from "three"

This code, allow us to move our camera mimicing our mouse movement

CameraRig.js

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

// Using "CameraRig", "camera" position reacts to our mouse movement
function CameraRig(props) {
  const vec = new Vector3();
  const { camera, mouse } = useThree();
  useFrame(() => {
    // using "lerp" algorithm to do a linear interpolation between three variables (x,y,z) and  given a fraction (0.05 in this case)
    camera.position.lerp(vec.set(mouse.x * 20, mouse.y * 20, mouse.z), 0.05);
    props.setmove(camera.position);
  });
}

export default CameraRig;

Enter fullscreen mode Exit fullscreen mode

Resume of components: App, Stars, GltfLoader, Lights, CameraRig

Code in sandBox
Does not render due to an issue of codeSandBox "ide".
error: Unexpected token < in JSON at position 0
For more info, check in stackoverslow or this post

I've deleted gltf model in order to show a quick pre-view

Image description

Web3 will be added in next post

I hope it has been helpful.

Top comments (1)

Collapse
 
mrmobin profile image
Mobin

Good Job !