TECH3

LIGHTS, MODELS ... ACTION!​​

05                                        2025-2026

In dit onderdeel duiken we dieper in de praktische kant van onze 3D-wereld. We leren hoe we met debug tools inzicht krijgen in onze scene. Hoe we een realistische omgeving en belichting opzetten, en hoe we onze eerste 3D modellen correct inladen.

LIGHTS, MODELS ... ACTION

DEBUG

01

01

DEBUG

LEVA

Leva is een krachtige en gebruiksvriendelijke debugtool voor React Three Fiber (R3F). Met Leva kun je in real time waarden aanpassen — zoals posities, kleuren, lichtintensiteiten of materiaalinstellingen — en direct het effect daarvan in je 3D-scene zien. 

Kort gezegd: Leva maakt het makkelijker om te spelen, te tweaken en te begrijpen wat er in je scene gebeurt.

01

DEBUG

INSTALL

Om Leva te installeren gebruik je volgende commando.

npm install leva

Nadien importeer je de UseControls hook van Leva om gebruik te maken hiervan.

import { useControls } from 'leva'

01

DEBUG

LET'S TWEAK

Als eerste stap maken we een eenvoudige tweak waarmee je de positie van een object kunt aanpassen.

export default function Experience()
{
    const controls = useControls({ position: - 2 })
    console.log(controls.position)

    // ...
}

Gebruik de useControls hook als volgt in Experience.tsx

Een alternatieve (en veelgebruikte) aanpak is om de position te destructuren.

export default function Experience()
{
    const { position } = useControls({ position: - 2 })

    console.log(position)

    // ...
}

01

DEBUG

RANGE

Laten we de position toepassen op de x-as van het object. Wanneer je de waarde aanpast, verplaatst het object zich naar de nieuwe positie. Dat is al handig ... maar nog beter: je kunt ook een bereik (range) instellen om variatie toe te voegen.

Plaats de position op het object (x-as)

Geef de position een range door useControls aan te passen naar volgende.

const { position } = useControls({
    position:
    {
        value: - 2,
        min: - 4,
        max: 4,
        step: 0.01
    }
})
<mesh position-x={ position }>

We krijgen nu sliders te zien die makkelijk in gebruik zijn! Top!

01

DEBUG

VECTORS

Tot nu toe hebben we enkel de x-as aangepast. Maar wat als je ook de y of z-as wilt beïnvloeden? Je hoeft geen extra controls aan te maken — met een Vector2 (voor x en y) of Vector3 (voor x, y en z) kun je meerdere assen tegelijk beheren.

Verander de controls naar een Vec2.

Vergeet zeker niet de position ook aan te passen bij het object.

<mesh position={ [ position.x, position.y, 0 ] }>
const { position } = useControls({
    position:
    {
        value: { x: - 2, y: 0 },
        step: 0.01
    }
})

Doe nu hetzelfde voor de z-as.

01

DEBUG

COLOR

Tot nu toe hebben we enkel posities aangepast in de Leva Debug Panel.
Maar Leva biedt nog veel meer mogelijkheden, bijvoorbeeld het instellen en wijzigen van kleuren.

Voeg een color property toe aan je controls.

Destructure de color

<meshStandardMaterial color={ color } />
const { position } = useControls({
    // ...
    color: '#ff0000'
})
const { position, color } = useControls({
    // ...
})

Plaats deze nu op de Material van het object.

01

DEBUG

BOOLEAN

Soms wil je niet alleen waarden aanpassen, maar ook een object verbergen of tonen. Daarvoor heb je een toggle nodig. Gelukkig heeft Leva een ingebouwde boolean control waarmee je dit eenvoudig kunt doen.

Voeg een visible property toe aan je controls.

Plaats de visible op je mesh naar de leva waarde

<meshStandardMaterial color={ color } />
const { position, color, visible } = useControls({
    // ...
    visible: true
})
<mesh visible={ visible } position={ [ position.x, position.y, 0 ] }>

Plaats deze nu op de Material van het object.

01

DEBUG

INTERVAL

Een interval kun je zien als een uitbreiding van een range: in plaats van één bereik geef je nu een start en een eindwaarde op.

Voeg een interval property toe aan de controls.

Voor dit moment hebben we deze waarde nog niet nodig.
Daarom is het ook niet nodig om ze te destructuren.

const { position, color, visible } = useControls({
    // ...
    myInterval:
    {
        min: 0,
        max: 10,
        value: [4, 5],
    }
})

01

DEBUG

BUTTON

Naast sliders en toggles kun je in Leva ook buttons gebruiken. Voor dit voorbeeld laten we ze nog weg, maar we zullen ze later demonstreren.

Importeer eerst de button van leva

import { button, useControls } from 'leva'

Voeg een button property toe aan de controls.

const { position, color, visible } = useControls('sphere', {
	  // ...
	  clickMe: button(() => { console.log('ok') })
})

01

DEBUG

SELECT

In Leva kun je naast sliders, toggles en buttons ook dropdowns gebruiken.
Bijvoorbeeld om een material te kiezen. Dit stel je in via de options-property.

Voeg een options property toe aan de controls

const { position, color, visible } = useControls('sphere', {
    // ...
    choice: { options: [ 'a', 'b', 'c' ] }
})

01

DEBUG

FOLDERS

Naarmate je meer tweaks toevoegt, kan het lastig worden om het overzicht te bewaren. Met folders kun je controls logisch groeperen, zodat je snel vindt wat je zoekt en je project overzichtelijk blijft.

Voeg een folder toe door een string te plaatsen als de eerste parameter.

const { position, color, visible } = useControls('sphere', {
   //...
})

01

DEBUG

FOLDERS

Maak nu ook een folder aan voor 'cube' die de scale aanpast

const { scale } = useControls('cube', {
    scale:
    {
        value: 1.5,
        step: 0.01,
        min: 0,
        max: 5
    }
})

01

DEBUG

CONFIGURE

import { Leva } from 'leva'

Leva biedt veel mogelijkheden om de interface en controls aan te passen.
Voor dit voorbeeld beperken we ons tot het collapsen van folders, maar er zijn nog veel andere settings die je kunt gebruiken. Raadpleeg de documentatie voor een volledig overzicht.

Importeer eerst Leva in index.tsx

Voeg het buiten je Canvas toe.

    <Leva />
        <Canvas>
        </Canvas>
        

Nu kan je de property collapsed op het Leva component plaatsen.

<Leva collapsed />

LIGHTS, MODELS ... ACTION

R3F PERF

02

02

R3F PERF

MONITORING

Soms wil je weten hoe je scene presteert: hoeveel frames per seconde, draw calls, en andere metrics. Met Perf uit de r3f-perf-bibliotheek kun je dit eenvoudig in de gaten houden.

Installeer eerst perf met npm install

npm install r3f-perf

Importeer in Experience.tsx de Perf component

import { Perf } from 'r3f-perf'

Voeg het nu toe aan je Experience

  return <>

        <Perf />

        {/* ... */}

    </>

02

R3F PERF

POSITIONING

Perf verschijnt standaard in de rechterbovenhoek van de viewport.
Omdat Leva daar al staat, is het handig om Perf te verplaatsen.
Dit kan eenvoudig door de position-property aan te passen.

Verander de Perf position naar top-left

<Perf position="top-left" />

We gaan een stapje verder en voegen een perfVisible property toe aan de controls.

const { perfVisible } = useControls({
    perfVisible: true
})

Vervolgens renderen we de perf conditional.

{ perfVisible && <Perf position="top-left" /> }

02

R3F PERF

PERF

Nu kan je makkelijk de stats zien van je scene en heb je je eigen PERF.

LIGHTS, MODELS ... ACTION

COLORS & LIGHT (HELPERS)

03

03

COLORS & LIGHT (HELPERS)

BACKGROUND

Zoals eerder besproken is de achtergrond van een canvas transparant, waardoor we deze eenvoudig met CSS kunnen stylen. Soms wil je echter de achtergrond direct in de scene aanpassen, zonder CSS te gebruiken.

We kunnen gebruik maken van setClearColor op de renderer

const created = ({ gl }) =>
{
    gl.setClearColor('#ff0000', 1)
}

<Canvas
    camera={ {
        fov: 45,
        near: 0.1,
        far: 200,
        position: [ - 4, 3, 6 ]
    } }
    onCreated={ created }
>
    <Experience />
</Canvas>

03

COLORS & LIGHT (HELPERS)

BACKGROUND

Of we passen het aan met de scene background

import * as THREE from 'three'

// ...

const created = ({ scene }) =>
{
    scene.background = new THREE.Color('#ff0000')
}

<Canvas
    camera={ {
        fov: 45,
        near: 0.1,
        far: 200,
        position: [ - 4, 3, 6 ]
    } }
    onCreated={ created }
>
    <Experience />
</Canvas>

03

COLORS & LIGHT (HELPERS)

BACKGROUND

Als laatste optie hebben we ook R3F color. Die is volgens mij de simpelste manier.

<Canvas
    camera={ {
        fov: 45,
        near: 0.1,
        far: 200,
        position: [ 1, 2, 6 ]
    } }
>
    <color args={ [ '#ff0000' ] } />
    <Experience />
</Canvas>

Deze moeten we linken aan de background en dat doen we met het attach attribuut.

<color args={ [ 'cornsilk' ] } attach="background" />

03

COLORS & LIGHT (HELPERS)

LIGHTS

Zoals eerder vermeld hier nog eens een lijst van alle lights die support zijn door R3F

  • <ambientLight />
  • <hemisphereLight />
  • <directionalLight />
  • <pointLight />
  • <rectAreaLight />
  • <spotLight />

03

COLORS & LIGHT (HELPERS)

HELPERS

Soms is het lastig om te zien waar je lichtbron precies staat of waar deze op schijnt. Gelukkig bieden helpers visuele ondersteuning: ze tonen de grootte, positie en andere kenmerken van het licht.

import * as THREE from "three";

export default function Experience()
{
     const directionalLightRef  = useRef<THREE.DirectionalLight>(null!)

    // ...
}

Maak eerst een reference naar je light (eigen keuze)

Plaats de reference op je light door het ref-attribuut

<directionalLight ref={ directionalLightRef } position={ [ 1, 2, 3 ] } intensity={ 4.5 } />

03

COLORS & LIGHT (HELPERS)

HELPERS

import { useHelper, OrbitControls } from '@react-three/drei'

Importeer nu de useHelper hook van Drei.

Voor de tweede parameter van onze helper hook zullen we gebruik moeten maken van de originele three library. Importeer deze dus als volgt.

export default function Experience()
{
    const directionalLight = useRef()
    useHelper(directionalLight, THREE.DirectionalLightHelper, 1, 'green')

    // ...
}
import * as THREE from 'three'

Roep nu de hook aan en geef je reference mee samen met de helper in kwestie.

LIGHTS, MODELS ... ACTION

IN THE SHADOWS

04

04

IN THE SHADOWS

ACTIVATE

Om je scene er realistischer uit te laten zien, is het toevoegen van schaduwen essentieel. We starten met de klassieke benadering van shadows en stappen vervolgens over naar de specifieke oplossingen in R3F.

Om een default shadow te activeren moeten we aan onze Canvas meegeven dat er shadows moeten zijn. Plaats de 'shadows' attribuut op het Canvas-element

<Canvas
    shadows
    camera={ {
        fov: 45,
        near: 0.1,
        far: 200,
        position: [ - 4, 3, 6 ]
    } }
>
    <Experience />
</Canvas>

04

IN THE SHADOWS

CAST

Voor een correcte shadow is het belangrijk dat je mesh weet wat hij moet doen: moet hij shadows casten, shadows ontvangen, of beide? Stel dit expliciet in.

Voeg castShadow toe aan de sphere mesh en cube mesh

<mesh castShadow position-x={ - 2 }>
    {/* ... */}
</mesh>

<mesh castShadow position-x={ 2 } scale={ 1.5 }>
    {/* ... */}
</mesh>

Voeg receiveShadow op de plane waar die op staan.

<mesh receiveShadow position-y={ - 1 } rotation-x={ - Math.PI * 0.5 } scale={ 10 }>
    {/* ... */}
</mesh>

Ook op onze lights moeten we vertellen of die shadows casten of niet

04

IN THE SHADOWS

BAKING

Zoals de term al aangeeft, bakken we de shadows in de scene. Dat wil zeggen dat de schaduwen vastgezet worden op frame 1 en daarna niet opnieuw berekend worden bij elk frame (Handig voor statische scènes).

Importeert BakeShadows van Drei en voeg deze toe aan de tsx.

import { BakeShadows } from '@react-three/drei'

export default function Experience()
{
    // ...

    return <>

        <BakeShadows />
        {/* ... */}

    </>
}

LET OP: Heb je een animatie dan zullen de shadows niet meebewegen (baked)

04

IN THE SHADOWS

CONFIGURATION

Naast casten en ontvangen kun je ook de kwaliteiten van shadows aanpassen. Zo kun je ze lichter maken, scherpere of zachtere randen geven, en andere eigenschappen verbeteren.

Voeg volgende attributen toe aan je directionalLight

<directionalLight
    ref={ directionalLight }
    position={ [ 1, 2, 3 ] }
    intensity={ 4.5 }
    castShadow
    shadow-mapSize={ [ 1024, 1024 ] }
/>

04

IN THE SHADOWS

CONFIGURATION

De shadow map wordt berekend via een orthographic camera, wat betekent dat we de grenzen van het schaduwbaken kunnen instellen met de eigenschappen top, right, bottom en left.

Pas de waarden aan als volgt 

<directionalLight
    ref={ directionalLight }
    position={ [ 1, 2, 3 ] }
    intensity={ 4.5 }
    castShadow
    shadow-mapSize={ [ 1024, 1024 ] }
    shadow-camera-near={ 1 }
    shadow-camera-far={ 10 }
    shadow-camera-top={ 2 }
    shadow-camera-right={ 2 }
    shadow-camera-bottom={ - 2 }
    shadow-camera-left={ - 2 }
/>

Wat merk je op?

04

IN THE SHADOWS

CONFIGURATION

Verhoog de waarden zodat de shadows niet worden afgekapt.

<directionalLight
    ref={ directionalLight }
    position={ [ 1, 2, 3 ] }
    intensity={ 4.5 }
    castShadow
    shadow-mapSize={ [ 1024, 1024 ] }
    shadow-camera-near={ 1 }
    shadow-camera-far={ 10 }
    shadow-camera-top={ 5 }
    shadow-camera-right={ 5 }
    shadow-camera-bottom={ - 5 }
    shadow-camera-left={ - 5 }
/>

Door het bereik van de shadow map te beperken, hoeft de engine minder te berekenen, wat de performance verbetert.
Afhankelijk van de complexiteit van je scene kan dit variëren, maar in ons voorbeeld is dit de ideale instelling.

04

IN THE SHADOWS

SOFT SHADOWS

Importeer de SoftShadows Component van Drei.

In veel scènes zijn de standaard shadows hard en onnatuurlijk.
Er bestaan meerdere oplossingen, maar we richten ons hier op de SoftShadows van Drei, die zachtere en realistischere schaduwen leveren.

import { SoftShadows, BakeShadows } from '@react-three/drei'

Voeg dit achter de BakeShadows toe.

export default function Experience()
{
    // ...
    return <>
        <BakeShadows />
        <SoftShadows size={ 25 } samples={ 10 } focus={ 0 } />
        {/* ... */}
    </>
}

04

IN THE SHADOWS

SUMMARY

🌑 Shadows zien er realistischer uit!
🔄 Zet BakeShadows uit om shadows te animeren.
⚡ Eerst gebruiken we BakeShadows omdat SoftShadows intensief zijn en de performance kunnen beïnvloeden.

Naast de reeds besproken shadow-methodes biedt Drei ook Accumulative Shadows en Contact Shadows. We gaan hier nu niet op in, maar je kunt deze technieken verkennen in de Drei-documentatie.

LIGHTS, MODELS ... ACTION

ENVIRONMENT (MAP)

05

05

ENVIRONMENT (MAP)

SKY

Om je scene een meer realistische en natuurlijke uitstraling te geven, kun je een skyBox toevoegen. Met de Sky-component van Drei gaat dit eenvoudig en snel.

Importeert de Sky component van Drei 

Voeg de Sky component toe aan de Experience

export default function Experience()
{
    // ...
    return <>
        {/* ... */}
        <Sky />
        {/* ... */}
    </>
}
import { Sky } from '@react-three/drei'

05

ENVIRONMENT (MAP)

SKY CONTROLS

Met Leva kunnen we de positie van de zon dynamisch aanpassen.
We gebruiken een Vector3 om de drie dimensies van de positie eenvoudig te beheren.

Maak de controls aan voor de sunPosition.

We kunnen nu die positie gebruiken in onze Sky component

<Sky sunPosition={ sunPosition } />
const { sunPosition } = useControls('sky', {
    sunPosition: { value: [ 1, 2, 3 ] as [number,number,number] }
})

Gebruiken van de sunPosition kunnen we nu ook toevoegen aan de directionalLight om realistischer beeld te vormen.

<directionalLight
    ref={ directionalLight }
    position={ sunPosition }
/>

05

ENVIRONMENT (MAP)

ENVIRONMENT

Om realistische omgevingsreflecties te maken, gebruiken we een environment map, opgebouwd uit zes afbeeldingen in een kubus.
HDRI-textures bieden een eenvoudig alternatief, omdat ze 360° informatie bevatten.

Importeer de Environment component van Drei

Plaats nu de files in de component. Eerst doen we het zonder hdr foto.

<Environment
        files={ [
            './environmentMaps/px.jpg',
            './environmentMaps/nx.jpg',
            './environmentMaps/py.jpg',
            './environmentMaps/ny.jpg',
            './environmentMaps/pz.jpg',
            './environmentMaps/nz.jpg',
        ] }
 />
import { Environment, Sky } from '@react-three/drei'

05

ENVIRONMENT (MAP)

ENVIRONMENT

Dankzij de environment map krijgt de scene realistisch omgevingslicht.
Met Leva kunnen we de intensiteit eenvoudig realtime aanpassen door de environmentIntensity van de scene te wijzigen.

Maak de control aan bij Leva

Om de scene te kunnen aanspreken maken we gebruik van de useThree hook.

import { useThree, useFrame } from '@react-three/fiber'

const { envMapIntensity } = useControls('environment map', {
    // ...
})
const scene = useThree(state => state.scene)
useEffect(() =>
{
    scene.environmentIntensity = envMapIntensity
}, [ envMapIntensity ])
const { envMapIntensity } = useControls('environment map', {
    envMapIntensity: { value: 2, min: 0, max: 12 }
})

05

ENVIRONMENT (MAP)

BACKGROUND

Om een foto als achtergrond van je scene te gebruiken, kun je het background-attribuut van de Environment-component instellen.

Plaats 'background' op de Environment component.

<Environment
    background
    files={ [
        './environmentMaps/2/px.jpg',
        './environmentMaps/2/nx.jpg',
        './environmentMaps/2/py.jpg',
        './environmentMaps/2/ny.jpg',
        './environmentMaps/2/pz.jpg',
        './environmentMaps/2/nz.jpg',
    ] }
/>

05

ENVIRONMENT (MAP)

HDRI

Met een HDRI kun je een scene verlichten zoals in de echte wereld, omdat het High Dynamic Range gebruikt. Dit betekent dat licht verschillende intensiteiten heeft — een zon is bijvoorbeeld veel feller dan een lamp.
Tip: Kijk niet in de zon... trust me!

Zoek een hdr file online en vervang de files array met de file.

<Environment
    background
    files="./environmentMaps/hdri-skies.hdr"
/>

LET OP: De intensity hangt af van de hdr dus pas aan waar nodig.

05

ENVIRONMENT (MAP)

PRESET

Om het instellen van verlichting te vergemakkelijken, biedt Drei enkele kant-en-klare presets. Deze presets kun je direct toepassen op je scene:

Plaats sunset op de Environment als preset met het 'preset' attribuut

<Environment
    background
    preset="sunset"
/>
  • apartment
  • city
  • dawn
  • forest
  • lobby
  • night
  • park
  • studio
  • sunset
  • warehouse

05

ENVIRONMENT (MAP)

CUSTOM

Wil je de scene exact naar wens verlichten?
Met custom environments kan dit eenvoudig: plaats een mesh in de Environment-component en pas deze aan zoals je wilt.

Plaats een rode plane in je scene (in Environment)

<Environment
    background
    preset="sunset"
>
    <mesh position-z={ - 2 } scale={ 2 }>
        <planeGeometry />
        <meshBasicMaterial color="red" />
    </mesh>
</Environment>

Verwijder de preset → volledige controle over je environment map.

05

ENVIRONMENT (MAP)

CUSTOM

Wanneer we een vaste kleur zoals rood kiezen, is de intensiteit vast en moeilijk aan te passen. Door in plaats daarvan een RGB-array te gebruiken, kunnen we de kleur en helderheid van het licht precies instellen.

Verander de rode plane naar een array met [1,0,0]

<Environment
    background
>
    <color args={ [ '#000000' ] } attach="background" />
    <mesh position-z={ - 5 } scale={ 10 }>
        <planeGeometry />
        <meshBasicMaterial color={ [ 1, 0, 0 ] } />
    </mesh>
</Environment>

Als je nu de kleur op [2,0,0] zet, wat gebeurt er dan?

05

ENVIRONMENT (MAP)

LIGHTFORMER

LightFormer laat ons vormen en kleuren van licht aanpassen, wat dezelfde effecten geeft als het direct aanpassen van de Environment.
Zo kunnen we dynamisch en precies verlichting toevoegen aan onze scene.

Importeer de LightFormer van Drei.

import { Lightformer } from '@react-three/drei'

Voeg het aan onze environment toe met dezelfde positie en scale als voordien.

<Environment
    background
>
    <color args={ [ '#000000' ] } attach="background" />
    <Lightformer position-z={ - 2 } scale={ 2 } />
</Environment>

05

ENVIRONMENT (MAP)

INTENSITY

Met LightFormer kun je de kleur en sterkte van je licht bepalen. Dit gebeurt via een attribuut, waardoor het instellen van verlichting flexibeler wordt.

Voeg volgende toe aan je lightformer

<Lightformer
    position-z={ - 2 }
    scale={ 2 }
    color="red"
    intensity={ 10 }
/>

We kunnen ook onze shapes kiezen van de lightformer. Verander het naar ring en pas de scale aan naar 5 om het beter te zien.

<Lightformer
    position-z={ - 2 }
    scale={ 5 }
    color="red"
    intensity={ 10 }
    form="ring"
/>

05

ENVIRONMENT (MAP)

RESOLUTION

Je kunt de resolutie van een environment map aanpassen.
Wanneer de background niet zichtbaar is, is een lage resolutie voldoende omdat alleen de lichtinformatie van de map wordt gebruikt.

Verander de resolutie naar 32 van de environment map met preset 'sunset'

<Environment
    background
    preset="sunset"
    resolution={ 32 }
>
    {/* ... */}
</Environment>

05

ENVIRONMENT (MAP)

GROUND

Bij gebruik van een environment map als achtergrond lijkt het alsof de objecten in de lucht zweven. Door ground projection te activeren, wordt het contact met de grond gesimuleerd, wat een realistischer effect geeft.

Voeg ground toe aan je environment met de volgende settings.

<Environment
    preset="sunset"
    ground={ {
        height: 7,
        radius: 28,
        scale: 100
    } }
>
    {/* ... */}
</Environment>

Het lijkt alsof de objecten in de grond zitten. Dat is omdat de ground op de 0 positie staat en zo je objecten erin zitten. Verhoog alle objecten op de y-as met 1.

05

ENVIRONMENT (MAP)

STAGE

In plaats van handmatig een environment te bouwen, kunnen we met Stage van Drei een kant-en-klare, esthetische setup gebruiken.
Zo besparen we tijd en krijgen we toch een professioneel uitziende scene.

Importeer de Stage component van Drei

import { Stage } from '@react-three/drei'

Wrap nu je componenten in de Stage Component.

<Stage>
    <mesh position-y={ 1 } position-x={ - 2 }>
        <sphereGeometry />
        <meshStandardMaterial color="orange" />
    </mesh>

    <mesh ref={ cube } position-y={ 1 } position-x={ 2 } scale={ 1.5 }>
        <boxGeometry />
        <meshStandardMaterial color="mediumpurple" />
    </mesh>
</Stage>

05

ENVIRONMENT (MAP)

STAGE

Super handig! We kunnen ook het shadow-attribuut aanpassen.
Bijvoorbeeld door een contact shadow toe te voegen, waarbij je opacity en blur kunt instellen voor extra controle. Daarnaast kun je ook een environment en een preset van de directionalLight koppelen.

Pas volgende attributen aan van de Stage Component

<Stage
    shadows={ { type: 'contact', opacity: 0.2, blur: 3 } }
    environment="sunset"
    preset="portrait"
    intensity={ envMapIntensity } 
>

LIGHTS, MODELS ... ACTION

MODELS

06

MODEL

In 3D-projecten laden we modellen meestal via .glb-bestanden, omdat ze breed ondersteund worden. R3F maakt dit makkelijk met de useLoader-hook, zodat je modellen efficiënt in je scene kunt plaatsen.

Importeer useLoader en de GLTFLoader van three (useLoader heeft deze nodig)

import { useLoader } from '@react-three/fiber'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'

06

MODELS

Nu kunnen we useLoader aanroepen in onze Experience

export default function Experience()
{
    const model = useLoader(GLTFLoader, './models/birbs.glb')
    console.log(model)

    // ...
}

MODEL

Het laden van een model met useLoader toont nog niets totdat het in de return wordt geplaatst. Omdat <Model /> niet direct werkt, gebruiken we een primitive, die als container voor objecten fungeert en ons model kan bevatten.

Voeg volgende toe aan je Experience 

export default function Experience()
{
    const model = useLoader(GLTFLoader, './models/birbs.glb')

    return <>

        {/* ... */}

        <primitive object={ model.scene } scale={1}/>

    </>
}

06

MODELS

Het kan zijn dat je de scale moet aanpassen omdat het model niet goed geschaald is.

DRACO

DRACO-modellen zijn gecomprimeerde 3D-modellen die minder opslagruimte vereisen en sneller laden. Compressie kan gedaan worden in tools zoals Blender, maar let op: je hebt een speciale loader nodig, GLTFLoader alleen werkt niet.

06

MODELS

Not this DRACO

DRACO

06

MODELS

Importeer de DRACOloader in je project

import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'

UseLoader heeft een functie waarin je de loader specifiek kan aanroepen. De Draco folder moet wel in je public folder aanwezig zijn (node modules)

const model = useLoader(
    GLTFLoader,
    './models/birbs-draco.glb',
    (loader) =>
    {
        const dracoLoader = new DRACOLoader()
        dracoLoader.setDecoderPath('./draco/')
        loader.setDRACOLoader(dracoLoader)
    }
)

LAZY LOADING

Wanneer een model groot is of de verbinding traag, zal de scene pas verschijnen wanneer alles geladen is. Dit kan frustrerend zijn voor de gebruiker. Met Suspense kun je de rendering laten doorgaan en eventueel een fallback tonen totdat het model klaar is.

We plaatsen onze model in een aparte component als volgt 

06

MODELS

import { useLoader } from '@react-three/fiber'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'

export default function Model({...props})
{
    const model = useLoader(
        GLTFLoader,
        './models/ship.glb',
        (loader) =>
        {
            const dracoLoader = new DRACOLoader()
            dracoLoader.setDecoderPath('./draco/')
            loader.setDRACOLoader(dracoLoader)
        }
    )

    return <primitive object={ model.scene } {...props} />
}

SUSPENSE

Nu kunnen we gebruikmaken van de Suspense-component van React.
Je kunt hierbij een fallback meegeven, zodat de gebruiker alvast iets te zien krijgt terwijl het model nog wordt geladen.

Importeer Suspense van react en plaats er je model tussen.

06

MODELS

import { Suspense } from 'react'

<Suspense>
     <Model scale={0.16} position-y={1} />
</Suspense>

Herlaad de pagina eens? (TIP: zet je netwerk op een tragere speed in de network tab)

Geef nu een fallback mee en herlaad de pagina opnieuw, wat zie je?

<Suspense
    fallback={ <mesh position-y={ 0.5 } scale={ [ 2, 3, 2 ] }><boxGeometry args={ [ 1, 1, 1, 2, 2, 2 ] } /><meshBasicMaterial wireframe color="red" /></mesh> }
>

SUSPENSE

Het rechtstreeks schrijven van een fallback in onze Suspense kan rommelig worden. Daarom plaatsen we de fallback in een apart component, zodat we deze herbruikbaar kunnen maken.

Uiteindelijk zou je Suspense er als volgt moeten uitzien.

06

MODELS

import Placeholder from './Placeholder.jsx'

<Suspense fallback={ <Placeholder /> }>
	<Model />
</Suspense>

Indien je props meegeeft aan je Placeholder dan kan je dit ook als volgt doen. 

<Suspense fallback={ <Placeholder position-y={ 0.5 } scale={ [ 2, 3, 2 ] } /> }>

USEGLTF

Nu zou het niet Drei zijn als ze het ons niet makkelijker maken.
Ze bieden de hook useGLTF, waarmee het laden van GLTF/GLB-modellen een heel stuk eenvoudiger wordt.

Importeer useGLTF van drei en laad je model in door je pad mee te geven.

06

MODELS

import { useGLTF } from '@react-three/drei'

export default function Model()
{
    const model = useGLTF('./models/birbs.glb')

    // ...
}

Zelfs onze draco modellen kunnen we erin plaatsen en het werkt (zelfs zonder draco in public)

const model = useGLTF('./models/birbs-draco.glb')

PRELOAD

In sommige scenario’s wil je dat een model onmiddellijk zichtbaar is zonder dat er eerst een loader of fallback wordt getoond. Door het model preloaden, wordt het volledig geladen bij de start van de applicatie.

In je Model component kan je de functie preload aanroepen. Dit gebeurt buiten de function. 

06

MODELS

export default function Model({ ...props })
{
    // ...
}

useGLTF.preload('./models/birbs-draco.glb')

Nu zal het model ongeacht altijd eerst laden bij het inladen van je scene. (Doe dit niet op alle modellen anders kan je lang wachten voor we kunnen starten)

CLONE

Wanneer je hetzelfde model meerdere keren in de scene wilt gebruiken, is het efficiënter om een clone te maken. Hierdoor wordt het model hergebruikt zonder extra rendering, wat prestaties bespaart.

Importeer Clone van drei en gebruik het ipv de primitive.

06

MODELS

import { Clone, useGLTF } from '@react-three/drei'


<Clone object={ model.scene } scale={ 0.35 } />

Vergeet je fragment niet en maak maar clones van je object zoveel je wil. 

export default function Model()
{
    // ...
    return <>
        <Clone object={ model.scene } scale={ 0.35 } position-x={ - 4 } />
        <Clone object={ model.scene } scale={ 0.35 } position-x={ 0 } />
        <Clone object={ model.scene } scale={ 0.35 } position-x={ 4 } />
    </>
}

GLTF COMPONENT

Het zou handig zijn als we een model gewoon ergens konden plaatsen en meteen de React-component terugkrijgen.
Dankzij Poimandres bestaat dit: een converting tool die je model omzet naar een component. Je moet wel nog enkele aanpassingen doen afhankelijk van je eigen project.

06

MODELS

LIGHTS, MODELS ... ACTION

ANIMATION

07

ANIMATION

Als een model animaties bevat, hoeven we deze niet handmatig te programmeren. Met useAnimations van Drei kunnen we direct de animaties activeren, beheren en synchroniseren in onze scene.

Eerst en vooral laad je een model in met een animatie op. 

07

ANIMATION

import { useGLTF } from '@react-three/drei'

export default function LadyBug()
{
    const ladybug = useGLTF('./models/lady_bug_bird.glb')
    console.log(ladybug)

    return <primitive
    object={ ladybug.scene }
    scale={ 5}
    position-y={ 0.5 }
    position-z={-1}
    
	/>
}

PLAY

Nu het model in de scene staat, kunnen we de animaties starten, pauzeren of resetten via de useAnimations-hook van Drei.

Importeer useAnimations van Drei  en kijk even in de console wat daarin zit.

07

ANIMATION

import { useAnimations, useGLTF } from '@react-three/drei'

export default function LadyBug()
{
    const ladybug = useGLTF('./models/lady_bug_bird.glb')
    console.log(ladybug)
}

Plaats nu de animatie in een animations variabele. 

export default function LadyBug()
{
    const ladybug = useGLTF('./models/lady_bug_bird.glb')
    const animations = useAnimations(ladybug.animations, ladybug.scene)
    console.log(animations)

}

PLAY

Wanneer we een model laden met animaties, worden deze automatisch omgezet naar AnimationAction-objecten. Om te zorgen dat de juiste animatie start nadat het model geladen is, gebruiken we useEffect.

Importeert useEffect en activeer de animatie. 

07

ANIMATION

export default function LadyBug()
{
    // ...

    useEffect(() =>
    {
        const action = animations.actions.Fly
        action.play()
    }, [])

    // ...
}

Cool! nu zien we onze animatie in actie.

CROSSFADE

Stel dat je in je scene tussen animaties wilt wisselen en dat niet abrupt wilt doen. Met crossFadeFrom kun je een vloeiende overgang maken tussen twee animaties. Zo oogt de overgang natuurlijker en professioneler.

Via een timeout gaan we de walk naar een run aanpassen en crossfade toepassen. 

07

ANIMATION

 useEffect(() => {
        animations.actions.Fly?.play()

        window.setTimeout(() => {
            animations.actions["Eye Roll"]?.play()
            animations.actions["Eye Roll"]?.crossFadeFrom(animations.actions.Fly!, 1)
        }, 2000)

    }, [])

LEVA

Ook dit kunnen we eenvoudig integreren in Leva. Zo kunnen we de animaties selecteren en testen via het debugpaneel.

Voeg controls toe voor de animations met een options (choice)

07

ANIMATION

Pas de action wel aan in de useEffect naar animationName

export default function LadyBug()
{
    const ladybug = useGLTF('./models/lady_bug_bird.glb')
    const animations = useAnimations(ladybug.animations, ladybug.scene)

    const { animationName } = useControls({
        animationName: { options: animations.names }
    })
    // ...
}
useEffect(() =>
{
    const action = animations.actions[animationName]
    action.play()
}, [])

LEVA

Als we in Leva een animatie aanklikken, moet onze useEffect weten dat er iets veranderd is. Door animationName toe te voegen aan dependencies, herkent React de wijziging en speelt de animatie af.

Geef de animationName mee als dependency

07

ANIMATION

Gebruik de cleanup function van useEffect om een fadeout toe te passen. 

useEffect(() =>
{
    //...
}, [ animationName ])
useEffect(() =>
{

    action.reset().fadeIn(0.5).play()
    return () =>
    {
 		action.fadeOut(0.5)
    }
}, [ animationName ])

TECH3/5 - Debug, Enviroment & Models

By Niels Minne

TECH3/5 - Debug, Enviroment & Models

  • 119