Se in ThreeJS: la libreria open source per il 3D sul web abbiamo visto i punti di forza della libreria più popolare per il 3D come, per esempio, l’essere altamente configurabile e personalizzabile oltre che stabile e compatibile con numerosi framework, e in Casi d’uso di progetti sul browser abbiamo fatto una panoramica sui benefici del 3D sul web, ormai indispensabile per configuratori online, mappe, progettazione, gaming e tanto altro, in questo articolo cerchiamo di capire come inserire degli oggetti 3D, interagire con essi e muoverci nella scena 3D.
Abbiamo già anticipato come creare una semplicissima scena 3D, il classico Hello World ma in chiave 3D, per testare il nostro ambiente di sviluppo e per comprendere come, con poche e semplici istruzioni, possiamo inizializzare un mondo virtuale con Three.js. Ora vediamo tutto più in dettaglio.
Sostituiamo il classico Hello World con un cubo 3D. Adesso esaminiamo il significato riga per riga del nostro script:
1import * as THREE from "three";
2
3const threeCanvas = document.querySelector("#threeCanvas");
4
5const scene = new THREE.Scene();
Così stiamo importando la libreria installata, salviamo una reference del nostro canvas sull’index.html e istanziamo una scena dalla libreria Three.js.
1const geometry = new THREE.BoxGeometry(10, 10);
2const material = new THREE.MeshBasicMaterial({
3 color: "red",
4 wireframe: true,
5});
6const box = new THREE.Mesh(geometry, material);
7scene.add(box);
In questo modo creo una Mesh. Una mesh rappresenta un oggetto 3D costituito da una geometria e un materiale applicato ad essa, ed è fondamentalmente una rete di punti (vertici) che vengono connessi per formare facce e bordi, realizzando così la forma dell'oggetto.
La geometria definisce la struttura dell'oggetto, come, ad esempio, la posizione dei vertici e la connessione tra di essi. Può essere costituita da punti, linee, triangoli o complessi poligoni. Nello script stiamo istanziando un BoxGeometry e passando alcuni parametri come l’altezza e la lunghezza.
Il materiale specifica come viene visualizzata la mesh sulla scena: può includere attributi come colore, texture, trasparenza, riflessione e molti altri. Ci sono tanti tipi di materiali in Three.js, ma per adesso ci basta utilizzare quello più basico di tutti, il MeshBasicMaterial, in cui passiamo il colore e la proprietà wireframe per visualizzare i vertici e punti del mio oggetto.
Quindi, in Three.js, una mesh combina geometria e materiale per creare un oggetto 3D che può essere visualizzato e con cui possiamo interagire all'interno di una scena.
1// Camera
2const camera = new THREE.PerspectiveCamera();
3camera.position.setZ(30); //move camera backward
4
5// Renderer
6const renderer = new THREE.WebGLRenderer({
7 canvas: threeCanvas,
8});
9renderer.setSize(window.innerWidth, window.innerHeight); //take all screen
10renderer.render(scene, camera);
Una scena, però, rimane buia se non inseriamo una camera all’interno del nostro ambiente 3D. In questo script istanziamo quindi una camera che simula la visione dell’occhio umano discriminando oggetti lontani e vicini e cambiamo la sua posizione dell’asse Z per essere dietro rispetto al mio cubo, in maniera tale da inquadrarlo.
Infine, devo istanziare l’oggetto più importante su Three.js, ovvero il Renderer, un componente fondamentale che gestisce (come si intuisce dal nome :D) il processo di rendering, cioè la generazione delle immagini 2D che rappresentano la scena 3D sullo schermo.
Ricordatevi, infatti, che anche se le informazioni comprendono 3 dimensioni, il risultato finale deve poi essere “disegnato” sui nostri schermi che sono, appunto, in 2D. Three.js utilizza quindi tante “simpatiche” regole matematiche per proiettare questi dati sul nostro schermo. Il Renderer si occupa di trasformare i dati della scena, come la geometria e i materiali delle mesh, in immagini visibili. Gestisce l'illuminazione, l’ombra, l'applicazione delle texture e altri effetti visivi per ottenere il risultato desiderato.
Questo processo avanzato è il punto di forza di questa libreria. Potremmo calcolare tutto noi utilizzando semplicemente il canvas di html5, con tanta matematica e tantissima pazienza, ma non ha senso reinventare la ruota quando c’è già una community open source che ci pensa. (Piuttosto dobbiamo contribuire a migliorare ancor di più la libreria! :D).
È importante notare che Three.js supporta anche altri tipi di Renderer, come il CSS2DRenderer per il rendering 2D o il SVGRenderer per il rendering basato su SVG, ma il WebGLRenderer è il più comune e versatile da usare mentre aspettiamo il (non tanto) futuro WebGPU.
Vediamo adesso come è possibile manipolare questi oggetti per muoverli, spostarli e ruotarli.
Quasi tutte le entità di Three.js, incluse le mesh, ereditano da un padre comune chiamato Object3D. Questo oggetto fornisce funzionalità per la trasformazione, la gerarchia e la manipolazione degli oggetti nella scena 3D, grazie al fatto che include una serie di proprietà e metodi che consentono di effettuare trasformazioni come la traslazione, la rotazione e la scalatura degli oggetti.
Ecco un esempio di come utilizzare l'Object3D per applicare una trasformazione a un oggetto:
1// Crea una sfera blu
2const sphere = new THREE.Mesh(
3 new THREE.SphereGeometry(),
4 new THREE.MeshBasicMaterial({
5 color: "blue",
6 })
7);
8
9// Applica una traslazione all'oggetto
10sphere.position.set(x, y, z);
11
12// Applica una rotazione all'oggetto
13sphere.rotation.set(rx, ry, rz);
14
15// Applica una scalatura all'oggetto
16sphere.scale.set(sx, sy, sz);
Nell'esempio sopra, viene creato un nuovo oggetto “sfera” che eredita direttamente dalla classe Object3D di Three.js. Successivamente, vengono utilizzati i metodi position, rotation e scale per applicare rispettivamente una traslazione, una rotazione e una scalatura all'oggetto. È possibile specificare le coordinate x, y e z per impostare la posizione, i valori rx, ry e rz per impostare la rotazione in radianti e i valori sx, sy e sz per impostare la scalatura lungo gli assi.
Una volta definita la trasformazione, è possibile utilizzare la sfera come qualsiasi altro oggetto Three.js e aggiungerlo alla scena o alla gerarchia degli oggetti come desiderato. Stessa cosa è possibile fare non solo per le mesh, ma anche per la camera, particellari, gruppi di oggetti, luci, ecc., basta vedere nella documentazione l’ereditarietà dell’oggetto desiderato.
Per ora abbiamo lavorato solo a un singolo render di una scena, come se scattassimo una fotografia e quindi il risultato non può essere che una scena statica.
Per creare l’illusione di movimento, realizzando quindi delle animazioni, dobbiamo adottare una procedura simile allo stop motion: faccio un render (una foto), muovo l’oggetto interessato, ne faccio un altro, e così via ciclicamente all’infinito per dare un senso di movimento a delle immagini statiche, come per le pellicole dei film.
Logicamente, più render al secondo prendo, migliore sarà l’illusione del movimento. Lo standard oggi è mirare ad avere 60 render al secondo (FPS) per creare un senso di fluidità delle immagini.
Riscriviamo la nostra scena come segue:
1import * as THREE from "three";
2
3const threeCanvas = document.querySelector("#threeCanvas");
4
5const scene = new THREE.Scene();
6
7// Box
8const geometry = new THREE.BoxGeometry(5, 5, 5);
9const material = new THREE.MeshBasicMaterial({
10 color: "red",
11});
12const box = new THREE.Mesh(geometry, material);
13scene.add(box);
14
15// Camera
16const camera = new THREE.PerspectiveCamera();
17camera.position.setZ(30);
18
19// Renderer
20const renderer = new THREE.WebGLRenderer({
21 canvas: threeCanvas,
22});
23renderer.setSize(window.innerWidth, window.innerHeight);
24
25//Animation
26const update = () => {
27 box.rotation.y += 0.05;
28 box.rotation.z += 0.02;
29
30 renderer.render(scene, camera);
31 requestAnimationFrame(update); //call next frame
32};
33
34update(); //call first frame
Come vedete, abbiamo un'ulteriore sezione in fondo allo script che gestisce l’animazione della nostra scena. Il metodo update quando viene chiamato nell’ultima linea dello script si occupa di ruotare di poco il nostro oggetto e poi di catturare un frame con il metodo render.
Ma la particolarità sta nel fatto che stiamo chiamando un nuovo metodo, che è incluso dentro il nostro browser, ovvero il requestAnimationFrame che prende il metodo update stesso come parametro. Questa funzione chiamerà la callback passata come argomento in maniera ricorsiva, tarandosi in base alla potenza della macchina e al refresh rate del monitor, quindi eseguendo la callback subito appena il browser è libero di farlo. Richiamando l’update più volte verrà quindi calcolata la nuova rotazione dell’oggetto e ri-catturato un nuovo frame, dando la sensazione di movimento.
Siamo quindi sulla buona strada per creare il prossimo film Pixar 😀.
Avete notato però che il nostro punto di vista è piuttosto statico? Se siamo dentro una scena 3D in realtime, dobbiamo dare massima libertà di esplorazione ai nostri utenti. Usiamo quindi una funzionalità dentro Three.js per ruotare la nostra camera col mouse: l’OrbitControls.
L’OrbitControls è una classe che fornisce un controllo interattivo per la navigazione e la manipolazione della telecamera nella scena 3D. Con OrbitControls, è possibile ruotare la telecamera attorno a un punto di interesse, zoomare, fare una panoramica e interagire con la scena utilizzando il mouse o i dispositivi di input.
Per fare ciò basta importare questo controller e istanziarlo prima del render della nostra scena (fuori dall’update):
1import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
2…
3new OrbitControls(camera, threeCanvas);
Nell'esempio sopra, dopo aver importato Three.js e OrbitControls, viene creata un'istanza di OrbitControls passando la telecamera e la reference del canvas come argomenti della funzione.
Ora smettiamo di vedere tutto grigio e diamo un pò di colore ai nostri oggetti con le texture! Le texture sono delle immagini che coprono la superficie delle facce delle geometrie, quindi gli danno un colore, una trama o un motivo ma possibilmente anche diversi effetti e deformazioni.
Prendiamo una texture gratuita dal sito 3D textures e aggiungiamola al progetto.
È importante ricordare che diversi tipi di texture possono anche applicare diversi effetti e distorsioni all’apparenza della geometria, quindi l’applicazione delle texture non riguarda esclusivamente il colore.
Vediamo, ad esempio, quelli che possiamo applicare ad una semplice geometria di una porta:
Le texture possono avere diversi tipi di “mappe” che differiscono nel loro funzionamento:
Importiamo dunque la più semplice di tutte, la base color:
1import BaseColor from "./textures/gravel/Gravel_001_BaseColor.jpg";
2…
3const textureLoader = new THREE.TextureLoader();
4const texture = textureLoader.load(BaseColor);
5
6// Box
7const box = new THREE.Mesh(
8 new THREE.BoxGeometry(5, 5, 5),
9 new THREE.MeshBasicMaterial({
10 map: texture,
11 })
12);
13scene.add(box);
Come noterete, abbiamo usato la classe TextureLoader che consente di caricare e utilizzare texture per applicarle alle superfici degli oggetti nella scena 3D. Essa ci permette anche di passare una callback per notificare quando il caricamento è pronto.
Per concludere questa parte, usiamo la stessa logica delle texture per importare un intero modello 3D creato esternamente con software di modellazione come Blender, Maya, ecc.
Prendiamo un modello opensource gratuitamente da questa repo Github e scarichiamo il relativo oggetto GLTF; questo è un formato aperto, frutto di una collaborazione di diverse aziende software tra cui Google, NVIDIA, AMD, Nintendo, Apple, WebGL, Vulkan, ecc. Supporta diversi tipi di dati tra cui geometrie, camere, luce, animazioni, fino a intere scene. Inoltre, è perfetto per il web perché è focalizzato sull’ottimizzazione delle dimensioni dei file e avere quindi una veloce trasmissione.
Three.js supporta comunque dei loader per altri tipi di formato come FBX, Collada e Obj, ma viene sempre consigliato di stabilire un workflow glTF based per i nostri progetti.
1import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
2…
3const gltfLoader = new GLTFLoader();
4gltfLoader.load("./models/Duck/glTF/Duck.gltf", (gltf) => {
5 scene.add(gltf.scene.children[0]);
6});
Se tutto procede correttamente, completato il caricamento del nostro modello, verrà inserito in scena e renderizzato come segue:
Questo oggetto è a sua volta figlio di Object3D, quindi possiamo applicare tutte le regole prima citate per ruotarlo, trasformarlo e…se non vi risulta qualcosa potete sempre spiegargli quello che avete fatto e sicuramente il problema verrà a galla! :D
Se avete seguito le nostre istruzioni, però, non ne avrete di bisogno.
Fateci sapere sui social i vostri risultati e, soprattutto, non perdete l’ultimo appuntamento con Three.js dove vedremo come personalizzare una scena 3D con luci, ombre e tanto altro ;)
Programmatore a tutto tondo, ha avuto esperienza dal mondo dello sviluppo di applicazioni 3D (videogames, virtual reality e augmented reality) al mondo dello sviluppo per il web.
Appassionato di tecnologia e informatica, dopo gli studi ha fatto molteplici esperienze internazionali per poi tornare nella sua amata Sicilia. Lavora principalmente con JavaScript/TypeScript, React.js/Next.js e Node.js lato web, Unity/C#, Unreal Engine e Three.js lato 3D.
Ha a cuore la condivisione delle skills e delle conoscenze, e gli piace dare una mano concreta nelle community.
Se sei interessato a migliorare le competenze del tuo team (anche su ThreeJS) dai un’occhiata ai nostri corsi per aziende.
Anche se - semplicemente - vuoi prendere un caffè con noi o vedere la nostra collezione di Action Figures scrivici tramite questo form.