[#_lesson_23_models_and_light] == Lezione 23: Modelli e luce image::learn/23-models.png[La lezione finita, 480] Cubi e sfere ti porteranno lontano, ma prima o poi il gioco vuole un drago. Un *modello* è una forma che qualcun altro ha costruito in un programma di modellazione: centinaia di triangoli, i suoi colori e le sue superfici, a volte uno scheletro e dei movimenti che vanno con esso, tutto in un solo file. Caricarne uno sono due righe. Fargli avere un aspetto decente è questione di luce, e la luce è gran parte di questa lezione. Non ti servono grafica né download. La primissima volta che hai eseguito Singe, il motore ha estratto una cartella chiamata `Singe` nella tua cartella di lavoro, e lì dentro ci sono due modelli: `DragonModel.glb`, il drago della schermata del titolo del motore, e `SingeText.glb`, la scritta che c'è sotto. Questa lezione usa il drago. [#_the_scene_around_it] === La scena attorno Un modello ha bisogno di un posto dove stare. Crea una cartella, crea `dragon.singe` al suo interno, e comincia con un pavimento e una telecamera. Se hai fatto la ventiduesima lezione qui non c'è niente di nuovo; se non l'hai fatta, è una scena accesa, una lastra piatta su cui stare e un punto da cui guardare. [source,lua] ---- sceneEnable(true) sceneSetBackground(10, 12, 20) sceneSetAmbient(28, 30, 38) local stone = materialNew() materialSetColor(stone, 105, 105, 115) materialSetRoughness(stone, 0.9) local floor = nodeNew() nodeSetMesh(floor, meshBox(24, 0.4, 24), stone) nodeSetPosition(floor, 0, -0.2, 0) local camera = nodeNew() nodeSetPosition(camera, 0, 2.6, 9) nodeLookAt(camera, 0, 1.7, 0) cameraSet(camera) cameraSetPerspective(55, 0.1, 200) function onOverlayUpdate() return OVERLAY_UPDATED end ---- Eseguilo con `Singe -R dragon`. Una lastra grigia, vista leggermente dall'alto, su uno sfondo blu scuro. La lastra è larga ventiquattro unità e spessa poco meno di mezza unità, e appoggiarla a `y = -0.2` mette la sua superficie superiore esattamente a `y = 0`, il che rende facile ragionare su tutto quello che metterai dopo: il pavimento è zero. [#_loading_the_dragon] === Caricare il drago Due righe. [source,lua] ---- local dragonModel = modelLoad("Singe/DragonModel.glb") local dragon = modelInstance(dragonModel) ---- Mettile dopo il pavimento e salva. Compare un drago, ed è decisamente troppo grande per l'inquadratura -- stai guardando la sua parte centrale. [#_what_just_happened_19] === Cos'è appena successo Le due righe fanno lavori diversi, e la differenza conta. `modelLoad` legge il file, passa le forme e le loro superfici alla scheda grafica, e tiene tutto pronto. È la chiamata lenta. Falla una volta, quando il gioco parte, e mai nel mezzo della partita. `modelInstance` mette una copia nella scena e ti restituisce un nodo -- un nodo normale, dello stesso tipo che ti dà `nodeNew`, che posizioni, ruoti, scali ed elimini con le chiamate che già conosci. Creare un'istanza è la chiamata economica. Carica un albero, creane quaranta istanze e hai un bosco, con una sola copia delle forme sulla scheda grafica e quaranta posti in cui vengono disegnate. Con il modello arriva tutto ciò che il file conteneva: le forme, le superfici e i loro colori, il modo in cui sono disposte le parti, l'eventuale scheletro e gli eventuali movimenti. Per un modello caricato non costruisci i materiali; si è portato i suoi. [#_scale_and_the_first_thing_that_goes_wrong] === La scala, e la prima cosa che va storta Il drago è alto circa sette unità e mezza. La tua telecamera è nove unità più indietro. Ecco perché riempie la finestra. [source,lua] ---- nodeSetScale(dragon, 0.5) ---- Aggiungila sotto la riga `modelInstance`. Adesso è alto meno di quattro unità, sta sul pavimento ed entra nell'inquadratura. Non è un dettaglio; è il primo problema più comune con un modello, e ha due facce. Un modello costruito in centimetri arriva cento volte troppo grande e vedi un'unica superficie enorme che riempie lo schermo, cosa che di solito sembra «la mia scena è rotta» invece di «sono dentro il collo del drago». Un modello costruito in qualche altra unità arriva così piccolo da essere un puntino, o invisibile contro lo sfondo, e pensi che il file non si sia caricato. La soluzione è sempre la stessa: una scala sul nodo dell'istanza. Non sulle parti, non nel programma di modellazione se puoi evitarlo, ma un solo `nodeSetScale` sul nodo che ti ha dato `modelInstance`, scelto provando un numero e guardando. `0.01` e `100` sono risposte perfettamente normali. Quando non riesci a capire quanto è grande una cosa, datti un righello. Mettile accanto un cubo di un'unità: [source,lua] ---- local ruler = nodeNew() nodeSetMesh(ruler, meshBox(1, 1, 1), stone) nodeSetPosition(ruler, 3, 0.5, 0) ---- Un oggetto grande quanto una cassa accanto al modello ti dice con un'occhiata se il modello è un drago o una montagna. Togli il righello quando hai finito. Mentre lo sistemi, giralo verso di te: [source,lua] ---- nodeSetRotation(dragon, 0, -60, 0) ---- Il drago è stato modellato rivolto lungo la X positiva, cioè verso destra, quindi parte di profilo. Girarlo attorno a Y lo porta verso la telecamera. Il «davanti» di un modello è la direzione che ha scelto chi l'ha costruito, quale che sia, e per scoprirla devi girarlo e guardare. [#_light] === La luce Esegui quello che hai adesso. Il drago c'è, ed è deprimente: una forma piatta e fioca, senza riflessi e senza ombra. È perché l'unica luce nella tua scena è quella ambiente, il debole chiarore da ogni direzione che imposta `sceneSetAmbient`. È la delusione più comune in assoluto nel 3D, e capita a tutti: il modello si è caricato perfettamente e la scena è nera, o quasi, perché niente lo illumina. Prima di sospettare del file, metti una luce. Ce ne sono di tre tipi, e ognuna è un nodo, quindi le posizioni e le punti con `nodeSetPosition` e `nodeLookAt` come tutto il resto. [source,lua] ---- local sun = lightNew(LIGHT_DIRECTIONAL) nodeSetPosition(sun, -6, 9, 7) nodeLookAt(sun, 0, 1.5, 0) lightSetColor(sun, 255, 244, 224) lightSetIntensity(sun, 1.8) lightSetShadow(sun, true) ---- `LIGHT_DIRECTIONAL` è un sole. Viene trattato come se fosse infinitamente lontano, quindi conta solo la direzione in cui splende e tutto nella scena ne riceve la stessa quantità. La sua posizione serve a `nodeLookAt` per puntarlo. È la luce che dà a una scena la sua forma principale, e la maggior parte delle scene ne vuole esattamente una. `lightSetIntensity` è la luminosità. Per una luce direzionale, `1` porta fino al bianco una superficie bianca rivolta dritta verso di lei, quindi `1.8` è una giornata luminosa. `lightSetColor` la tinge: un bianco un po' caldo per il sole, un blu freddo per la luce della luna, e per il resto la sezione sulle luci del riferimento. `lightSetShadow(sun, true)` è quello che fa stare il drago sul pavimento invece di farlo fluttuare sopra. Le ombre sono spente per impostazione predefinita perché ogni luce che proietta ombre costa un altro passaggio su tutta la scena a ogni fotogramma. Accendile per l'unica luce che conta e lasciale spente per le altre; una macchina piccola ti ringrazierà. [source,lua] ---- local torch = lightNew(LIGHT_POINT) nodeSetPosition(torch, 3.5, 1.2, 3.0) lightSetColor(torch, 255, 150, 70) lightSetIntensity(torch, 14) lightSetRange(torch, 12) ---- `LIGHT_POINT` è una lampadina. Sta in un punto, splende in tutte le direzioni e si affievolisce con la distanza. Quell'affievolirsi è il motivo per cui il numero è `14` e non `1`: l'intensità di una luce puntiforme è la sua luminosità nel punto in cui si trova la luce, e dopo poche unità non ne resta molta. Una lampadina che illumina una stanza di solito vuole qualcosa nell'ordine delle decine. Se una luce puntiforme sembra non fare niente, il motivo è quasi sempre questo. `lightSetRange` dice fin dove arriva prima di spegnersi del tutto, il che evita che una lampada in una stanza illumini debolmente quella accanto, e impostarlo non costa niente. Il terzo tipo è `LIGHT_SPOT`, un cono che punta lungo la Z negativa del nodo stesso, con `lightSetCone` per l'angolo del centro luminoso e del bordo sfumato: una torcia elettrica, un faro d'auto, un riflettore. Possono splendere fino a otto luci contemporaneamente, più di quante ne servano alla maggior parte delle scene. Se ne vuoi di più, in realtà ne vuoi di meno. Eseguilo adesso. Un sole quasi bianco da sinistra, un caldo bagliore arancione da destra, un'ombra sulla lastra, e un drago con un davanti e un dietro. [#_materials_at_the_level_you_need_today] === I materiali, quanto ti basta per oggi Il drago si è portato i suoi materiali, e sono tre: un metallo scuro, un metallo più chiaro color champagne, e un vetro chiaro in cui lo sguardo entra un poco. La maggior parte delle sue parti è ricavata da più di uno. Non devi toccare niente di tutto questo, e la maggior parte delle volte non dovresti. Quando vuoi davvero cambiare l'aspetto di una parte di un modello, crei un materiale come nella ventiduesima lezione e lo metti sulla parte: [source,lua] ---- local red = materialNew() materialSetColor(red, 220, 50, 40) materialSetRoughness(red, 0.4) nodeSetMaterial(nodeFind("crest", dragon), red) ---- `nodeFind(name, root)` cerca in un'istanza di un modello una parte con il nome che aveva nel file e ne restituisce il nodo, oppure `nil` quando quel nome non c'è. È così che arrivi dentro un modello: una torretta, una ruota, una mano a cui appendere una torcia, o la cresta del drago. La cresta è un pezzo solo di un solo materiale, quindi il suo nodo porta la forma stessa e la riga qui sopra la ridipinge. Una parte costruita con più materiali viene tenuta come un nodo con un figlio per ogni pezzo, e mettere un materiale sul genitore non cambia niente che tu possa vedere. Quando un `nodeSetMaterial` sembra non fare niente, il motivo è questo, e `nodeGetChildren` ti mostrerà che cosa c'è sotto. Quattro numeri descrivono la maggior parte delle superfici. Il colore, da `materialSetColor`. `materialSetRoughness`, da `0` per uno specchio a `1` per il gesso. `materialSetMetallic`, che è `0` per quasi tutto -- vernice, legno, plastica, pelle -- e `1` per il metallo vero, e i valori intermedi servono a poco. E `materialSetEmissive`, che fa brillare una superficie del suo colore, per schermi, lampade e ferro rovente, senza illuminare niente attorno. Una cosa da sapere sul metallo: una superficie metallica ti mostra i suoi riflessi invece di un colore suo, quindi in una scena senza niente da riflettere sembra scura e spenta. È per questo che il metallo scuro del drago ha bisogno del sole per prendere vita. La soluzione vera è un cielo (`sceneSetSky`), che avvolge la scena in un panorama che si vede dietro tutto e allo stesso tempo la illumina; ne parla «L'aspetto del fotogramma» nel riferimento. [#_movement_the_model_brings_with_it] === I movimenti che il modello si porta dietro Molti modelli contengono animazioni: una camminata, una corsa, una posa da fermo, una porta che si apre. Sono salvate nel file, e riprodurne una è una sola chiamata sul nodo radice dell'istanza. [source,lua] ---- animationPlay(dragon, "Walk", true) ---- Il secondo argomento è il nome della clip come lo salva il file, oppure il suo numero. `true` vuol dire ripeterla all'infinito. Altri due numeri facoltativi impostano la velocità e la dissolvenza incrociata: `animationPlay(hero, "Run", true, 1.2, 0.4)` riproduce la corsa un quinto più veloce e ci sfuma dentro in quattro decimi di secondo, così una camminata diventa una corsa senza scatti. `animationStop`, `animationPause` e `animationIsPlaying` fanno quello che dice il loro nome. Quella riga non puoi ancora eseguirla, ed ecco il motivo onesto: il drago nella tua cartella `Singe` non ha animazioni. È un modello, non uno spettacolo. Chiedere una clip che non c'è ferma il gioco con un errore, quindi non dare mai niente per scontato -- chiedi: [source,lua] ---- local clips = modelGetAnimations(dragonModel) if #clips > 0 then animationPlay(dragon, 1, true) else debugPrint("DragonModel.glb carries no animation clips. The wings are turned by hand.") end ---- `modelGetAnimations` restituisce una tabella con i nomi delle clip nel file, in ordine, quindi `clips[1]` è la prima e `#clips` è quante ce ne sono -- gli strumenti per le tabelle della sesta lezione, su una tabella che ha riempito il motore. Eseguilo e guarda la console: il messaggio compare, perché quel conteggio è zero. Prendi l'abitudine di fare quel controllo con ogni modello che non hai fatto tu. Costa tre righe e trasforma «il gioco è morto all'avvio» in una frase nel log. [#_moving_the_parts_yourself] === Muovere le parti da te Un modello senza animazioni non è una statua. Le sue parti sono nodi, e i nodi girano. [source,lua] ---- local wingLeft = nodeFind("wingL", dragon) local wingRight = nodeFind("wingR", dragon) ---- [source,lua] ---- function onOverlayUpdate() local flap = math.sin(singeGetTicks() / 260) * 22 nodeSetRotation(wingLeft, flap, 0, 0) nodeSetRotation(wingRight, -flap, 0, 0) nodeRotate(dragon, 0, 0.25, 0) overlayClear() overlayPrint(2, 2, "Singe/DragonModel.glb") overlayPrint(2, 4, "animation clips in the file: " .. #clips) return OVERLAY_UPDATED end ---- Salvalo. Le ali battono, il drago gira lentamente su se stesso, e l'ombra sul pavimento batte con loro. `singeGetTicks` è il numero di millisecondi da quando il motore è partito, quindi sale regolarmente per sempre. `math.sin` prende quel numero che sale e restituisce un valore che scivola dolcemente da `-1` a `1` e indietro, un battito completo circa ogni secondo e mezzo con questo divisore. Moltiplicare per `22` lo trasforma in un angolo di ventidue gradi da ogni parte. Dividere per un numero più grande rallenta il battito; moltiplicare per un numero più grande lo allarga. Quella riga da sola è l'animazione più economica che esista, e vale la pena tenerla in tasca. Le ali girano attorno al proprio asse X, e girano attorno alla spalla invece che attorno al centro dell'animale, perché chi ha costruito il file ha messo il nodo di ogni parte dove va l'articolazione. Un modello che non è stato costruito così farà girare le sue parti attorno al centro del modello, e la soluzione è spostare il nodo della parte, di cui parla la sezione sui nodi del riferimento. Nota che il girare di tutto il drago non disturba il girare delle ali. Le parti sono appese sotto la radice dell'istanza, quindi ruotare la radice si porta dietro tutto, e la rotazione propria di ogni ala si aggiunge sopra. Quella disposizione -- una cosa dentro una cosa dentro una cosa, ognuna che si muove nel mondo del suo genitore -- è ciò a cui serve un albero della scena. [#_your_own_models] === I tuoi modelli Quando ne fai di tuoi, esportali da Blender -- o da Maya, o da 3ds Max, o da qualsiasi altro programma moderno -- come *glTF 2.0 binario*, cioè un file `.glb`. È la scelta predefinita di Blender ed è l'unico formato di modelli che Singe legge. L'unica regola è che il file deve essere autosufficiente: le forme, le immagini sopra di esse e i movimenti, tutti dentro un solo `.glb`. Un file che fa riferimento a un `.bin` o a un `.png` che gli sta accanto viene rifiutato, con un messaggio che nomina quello che voleva. L'esportatore di Blender fa la cosa giusta con le sue impostazioni predefinite. Non tutto quello che puoi costruire sopravvive al viaggio. La sezione «Modelli e animazione» del Riferimento, nel capitolo «Scene 3D», è l'elenco: che cosa viene letto, che cosa viene ignorato senza dire niente, e i limiti (uno scheletro può avere fino a centoventotto giunti, per esempio). Leggila una volta prima di passare un fine settimana su un modello, non dopo. [#_try_it_23] === Prova tu . *Spegni le luci.* Commenta tutto il blocco `sun` mettendo `--` all'inizio di ogni riga, ed eseguilo. Poi fai lo stesso con `sceneSetAmbient`. Ricordati che aspetto ha, perché un giorno succederà senza che tu lo voglia. . *Sposta il sole.* Cambia `nodeLookAt(sun, 0, 1.5, 0)` in `nodeLookAt(sun, 0, 20, 0)` così il sole punta sopra la testa del drago. Guarda l'ombra e i riflessi sparire. Puntare una luce non è decorazione. . *Scalda la torcia.* Alza l'intensità della luce puntiforme da `14` a `60` e abbassa la sua portata a `4`. Poi spostala in `(0, 6, 0)`, sopra il drago. Una luce puntiforme è una lampadina, e stai scegliendo dove appenderla. . *Crea due istanze.* Chiama `modelInstance(dragonModel)` una seconda volta, tienila in un'altra variabile, scalala e mettila in `(-4, 0, -3)`. Un caricamento, due draghi. Poi prova invece a caricare il modello due volte e capisci perché non dovresti. . *Trova un'altra parte.* Le parti del drago si chiamano `head`, `jaw`, `neck`, `tail`, `body`, `crest`, `hornL`, `hornR`, `wingL`, `wingR`, `foreLegL`, `foreLegR`, `hindLegL` e `hindLegR`. Fai ondeggiare la coda con un suo `math.sin` a una velocità diversa da quella delle ali. Poi prova `nodeFind("wings", dragon)` e guarda che cosa fa un nome sbagliato quando passi quel `nil` dritto a `nodeSetRotation`. [#_break_it_on_purpose_22] === Rompilo apposta Cambia il nome del file in `Singe/Dragonmodel.glb` -- una sola maiuscola di differenza -- ed eseguilo. Singe si ferma prima che il gioco parta: ---- 13:modelLoad: Unable to read Singe/Dragonmodel.glb. ---- La riga, la chiamata e la lamentela, in quest'ordine. «Unable to read» vuol dire che il motore ha cercato e non ha trovato niente, il che è quasi sempre una di tre cose: il nome è scritto male, le maiuscole sono sbagliate, o il file non è dove hai detto. Su Windows le maiuscole sarebbero state perdonate e poi il tuo gioco non avrebbe funzionato per nessuno su Linux, che è un bug peggiore di questo. Scrivi i nomi esattamente. Lo stesso messaggio con un nome di file giusto vuol dire che il file non è dove l'ha cercato il motore. La cartella `Singe` viene creata nella cartella *da cui* esegui il motore, quindi se hai avviato Singe da un'altra parte, anche `Singe/DragonModel.glb` punta da un'altra parte. [#_what_you_learned_23] === Cosa hai imparato * `modelLoad` legge un `.glb` una volta; `modelInstance` ne mette delle copie tutte le volte che vuoi. * La radice di un'istanza è un nodo normale: posizionala, ruotala, scalala ed eliminala come qualsiasi altro. * Un modello si porta dietro le sue forme e i suoi materiali. * Un modello arriva alla dimensione che ha usato il suo autore, ed è per questo che un `nodeSetScale` sull'istanza fa parte del collocarlo. * Una scena senza luce è nera o quasi, ed è la prima cosa da controllare quando un modello ha un aspetto sbagliato. * Una luce direzionale è un sole, una luce puntiforme è una lampadina che si affievolisce con la distanza e vuole un'intensità molto più grande, e un faretto è un cono. * Le ombre sono spente finché `lightSetShadow` non le accende, e ognuna ha un costo. * `nodeFind` raggiunge una parte di un modello con il nome che ha nel file. * `modelGetAnimations` dice che cosa può riprodurre un modello, e chiedere una clip che non c'è è un errore. * Un modello senza animazioni si può comunque muovere una parte alla volta. [#_next_time_23] === La prossima volta Tutto in questa scena si muove perché gliel'hai detto tu, fotogramma per fotogramma. La prossima lezione passa quel lavoro a un motore fisico: casse che cadono, rotolano, si impilano e si fanno cadere a vicenda senza che tu scriva una riga di aritmetica -- e la domanda più difficile di quando dovresti lasciarglielo fare. È la ventiquattresima lezione.