
Una nuvola bianca con gli occhi incassati, le gambe sottili e un bastone di legno. Respira, si guarda intorno, sorride e saluta, batte il bastone; e la faccia la guidi tu, con dieci pose.
Cosa c’è dentro
- Mesh
- 80.027 vertici, 159.988 triangoli
- Scheletro
- 16 ossa
- Animazione
vita, 10 secondi, in loop. Respira (le palle della nuvola si gonfiano a onda), batte le ciglia, guarda il bastone e poi dall’altra parte, sorride e saluta saltellando, batte due colpi col bastone, alza gli occhi al cielo. Si chiude su sé stessa.- Pose della faccia
occhi_sinistraguarda a sinistraocchi_destraguarda a destraocchi_suguarda in altoocchi_giuguarda in bassochiudichiude gli occhisorrisoocchi ad arco, felicistuporeocchi più grandirespiro_altosi gonfia la palla in altorespiro_spallesi gonfiano le spallerespiro_bassosi gonfiano le palle in basso
- Materiali
- Nuvola (bianco opaco, appena caldo), Occhi (marrone scuro lucido), Riflesso (bianco che si accende da solo), Arti (cioccolato satinato), Legno (noce chiaro)
- Peso
- 1,4 MB
Come si usa
Carica il GLB, fai partire l’animazione e, se vuoi, guida tu le pose della faccia: ogni chiave di forma ha un nome e un peso da 0 a 1.
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
// il modello: compresso con meshopt, serve il suo decoder
const loader = new GLTFLoader().setMeshoptDecoder(MeshoptDecoder);
const gltf = await loader.loadAsync('nuvola-col-bastone.glb');
scene.add(gltf.scene);
// l'animazione "vita": 10 secondi, in loop
const mixer = new THREE.AnimationMixer(gltf.scene);
mixer.clipAction(gltf.animations[0]).play();
// le pose della faccia: un peso da 0 a 1 per ogni chiave di forma
const faccia = [];
gltf.scene.traverse((m) => {
if (m.morphTargetDictionary && 'sorriso' in m.morphTargetDictionary) faccia.push(m);
});
const posa = (nome, peso) => faccia.forEach((m) => {
m.morphTargetInfluences[m.morphTargetDictionary[nome]] = peso;
});
// la luce: uno studio bianco, e il bianco resta bianco
renderer.toneMapping = THREE.NoToneMapping;
scene.environment = new THREE.PMREMGenerator(renderer)
.fromScene(new RoomEnvironment(), 0.04).texture;
const clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
mixer.update(clock.getDelta());
posa('occhi_destra', 0.6); // dopo il mixer: vince sulla clip
renderer.render(scene, camera);
});Com’è fatto
Dal disegno alla forma
Il contorno dell’illustrazione è fatto di sette archi: ognuno è stato misurato al decimo di pixel e diventa una sfera. Le sfere si uniscono con un raccordo che cambia con la profondità: vivo sul bordo, così le punte fra una palla e l’altra restano nette, e sempre più morbido verso la faccia, dove le pieghe svaniscono come nel disegno.
Liscia davvero
Ogni parte è una superficie matematica: la mesh nasce su una griglia fitta e poi ogni vertice viene riportato esattamente sulla superficie, con la normale presa dalla superficie stessa. Per il web la mesh è stata ridotta dove è piatta e tenuta fitta dove curva, poi riportata di nuovo sulla superficie: 80 mila vertici, e nessuna faccetta.
La faccia
Gli occhi stanno in un incavo con il bordo svasato, che prende la luce sotto e l’ombra sopra. Per le pose l’incavo si sposta insieme all’occhio tenendo la sua profondità rispetto alla pelle, così la nuvola resta esatta anche mentre guarda, chiude gli occhi o sorride.
Lo scheletro
Sedici ossa: il corpo, le gambe con i piedi piantati a terra (cinematica inversa, già cotta nell’animazione), il braccio che saluta, il bastone con la mano agganciata. La coreografia è scritta come funzioni del tempo e campionata a 24 fotogrammi al secondo.