471 lines
21 KiB
Text
471 lines
21 KiB
Text
[#_lesson_23_models_and_light]
|
||
== Leçon 23 : Modèles et lumière
|
||
|
||
image::learn/23-models.png[La leçon terminée, 480]
|
||
|
||
Les boîtes et les sphères vous mèneront loin, mais tôt ou tard le jeu voudra un
|
||
dragon. Un *modèle* est une forme que quelqu'un d'autre a construite dans un
|
||
logiciel de modélisation : des centaines de triangles, ses propres couleurs et
|
||
surfaces, parfois un squelette et des mouvements qui vont avec, le tout dans un
|
||
seul fichier. En charger un, c'est deux lignes. Lui donner l'air de quelque
|
||
chose, c'est de la lumière, et la lumière occupe l'essentiel de cette leçon.
|
||
|
||
Vous n'avez besoin d'aucun graphisme et d'aucun téléchargement. La toute
|
||
première fois que vous avez lancé Singe, il a extrait un dossier appelé `Singe`
|
||
dans votre dossier de travail, et deux modèles s'y trouvent :
|
||
`DragonModel.glb`, le dragon de l'écran titre du moteur lui-même, et
|
||
`SingeText.glb`, le lettrage en dessous. Cette leçon se sert du dragon.
|
||
|
||
[#_the_scene_around_it]
|
||
=== La scène autour
|
||
|
||
Un modèle a besoin d'un endroit où se tenir. Créez un dossier, créez-y
|
||
`dragon.singe`, et commencez par un sol et une caméra. Si vous avez fait la
|
||
leçon vingt-deux, rien de tout ça n'est nouveau ; sinon, c'est une scène
|
||
allumée, une dalle plate où se tenir, et un point d'où regarder.
|
||
|
||
[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
|
||
----
|
||
|
||
Lancez-le avec `Singe -R dragon`. Une dalle grise, vue légèrement de haut, sur
|
||
un fond bleu foncé. La dalle fait vingt-quatre unités de côté et un peu moins
|
||
d'une demi-unité d'épaisseur, et la poser à `y = -0.2` met sa surface du dessus
|
||
exactement à `y = 0`, ce qui rend facile de raisonner sur tout ce que vous
|
||
placerez ensuite : le sol, c'est zéro.
|
||
|
||
[#_loading_the_dragon]
|
||
=== Charger le dragon
|
||
|
||
Deux lignes.
|
||
|
||
[source,lua]
|
||
----
|
||
local dragonModel = modelLoad("Singe/DragonModel.glb")
|
||
local dragon = modelInstance(dragonModel)
|
||
----
|
||
|
||
Mettez-les après le sol et enregistrez. Un dragon apparaît, et il est bien trop
|
||
grand pour l'image -- vous regardez le milieu de son corps.
|
||
|
||
[#_what_just_happened_19]
|
||
=== Ce qui vient de se passer
|
||
|
||
Les deux lignes font des travaux différents, et la différence compte.
|
||
|
||
`modelLoad` lit le fichier, confie les formes et leurs surfaces à la carte
|
||
graphique, et garde le tout prêt. C'est l'appel lent. Faites-le une fois, quand
|
||
le jeu démarre, et jamais en pleine partie.
|
||
|
||
`modelInstance` place une copie dans la scène et vous renvoie un nœud -- un
|
||
nœud ordinaire, du même genre que ce que vous donne `nodeNew`, que vous
|
||
placez, tournez, mettez à l'échelle et supprimez avec les appels que vous
|
||
connaissez déjà. Instancier, c'est l'appel bon marché. Chargez un arbre,
|
||
instanciez-le quarante fois, et vous avez un bois, avec une seule copie des
|
||
formes sur la carte graphique et quarante endroits où elles sont dessinées.
|
||
|
||
Ce qui vient avec le modèle, c'est tout ce que contenait le fichier : les
|
||
formes, les surfaces et leurs couleurs, la façon dont les parties sont
|
||
agencées, l'éventuel squelette et les éventuels mouvements. Vous ne
|
||
construisez pas de matériaux pour un modèle chargé ; il a apporté les siens.
|
||
|
||
[#_scale_and_the_first_thing_that_goes_wrong]
|
||
=== L'échelle, et le premier problème
|
||
|
||
Le dragon mesure environ sept unités et demie de haut. Votre caméra est à neuf
|
||
unités en arrière. Voilà pourquoi il remplit la fenêtre.
|
||
|
||
[source,lua]
|
||
----
|
||
nodeSetScale(dragon, 0.5)
|
||
----
|
||
|
||
Ajoutez ça sous la ligne `modelInstance`. Maintenant il mesure moins de quatre
|
||
unités, il se tient sur le sol, et il tient dans le cadre.
|
||
|
||
Ce n'est pas un détail ; c'est le premier problème le plus courant avec un
|
||
modèle, et il a deux visages. Un modèle construit en centimètres arrive cent
|
||
fois trop grand et vous voyez une seule surface énorme qui remplit l'écran, ce
|
||
qui se lit en général comme « ma scène est cassée » plutôt que comme « je suis
|
||
dans le cou du dragon ». Un modèle construit dans une autre unité arrive si
|
||
petit que c'est un point, ou qu'il est invisible sur le fond, et vous supposez
|
||
que le fichier ne s'est pas chargé.
|
||
|
||
La solution est toujours la même : une échelle sur le nœud de l'instance. Pas
|
||
sur les parties, pas dans le logiciel de modélisation si vous pouvez l'éviter,
|
||
mais un seul `nodeSetScale` sur le nœud que `modelInstance` vous a donné,
|
||
choisi en essayant un nombre et en regardant. `0.01` et `100` sont des réponses
|
||
tout à fait ordinaires.
|
||
|
||
Quand vous n'arrivez pas à savoir quelle taille fait une chose, donnez-vous une
|
||
règle. Mettez une boîte d'une unité à côté :
|
||
|
||
[source,lua]
|
||
----
|
||
local ruler = nodeNew()
|
||
nodeSetMesh(ruler, meshBox(1, 1, 1), stone)
|
||
nodeSetPosition(ruler, 3, 0.5, 0)
|
||
----
|
||
|
||
Un objet de la taille d'une caisse à côté du modèle vous dit d'un coup d'œil
|
||
si le modèle est un dragon ou une montagne. Retirez la règle quand vous avez
|
||
fini.
|
||
|
||
Pendant que vous le placez, tournez-le vers vous :
|
||
|
||
[source,lua]
|
||
----
|
||
nodeSetRotation(dragon, 0, -60, 0)
|
||
----
|
||
|
||
Le dragon a été modélisé tourné vers les X positifs, c'est-à-dire vers la
|
||
droite, donc il commence de profil. Le tourner autour de Y le ramène vers la
|
||
caméra. L'« avant » d'un modèle, c'est la direction qu'a choisie la personne
|
||
qui l'a construit, et pour le savoir il faut le tourner et regarder.
|
||
|
||
[#_light]
|
||
=== La lumière
|
||
|
||
Lancez ce que vous avez maintenant. Le dragon est là, et il est morne : une
|
||
forme plate et terne, sans reflets et sans ombre. C'est parce que la seule
|
||
lumière de votre scène est la lumière ambiante, la faible lueur venue de
|
||
partout que règle `sceneSetAmbient`.
|
||
|
||
C'est la déception la plus courante en 3D, et elle arrive à tout le monde : le
|
||
modèle s'est chargé parfaitement et la scène est noire, ou presque, parce que
|
||
rien ne l'éclaire. Avant de soupçonner le fichier, mettez une lumière.
|
||
|
||
Il y en a trois sortes, et chacune est un nœud, donc vous les placez et les
|
||
orientez avec `nodeSetPosition` et `nodeLookAt` comme n'importe quoi d'autre.
|
||
|
||
[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` est un soleil. Il est traité comme s'il était infiniment
|
||
loin, donc seule compte la direction dans laquelle il brille, et tout dans la
|
||
scène en reçoit la même quantité. Sa position est là pour que `nodeLookAt`
|
||
puisse l'orienter. C'est la lumière qui donne à une scène sa forme principale,
|
||
et la plupart des scènes en veulent exactement une.
|
||
|
||
`lightSetIntensity`, c'est la luminosité. Pour une lumière directionnelle, `1`
|
||
éclaire une surface blanche qui lui fait face jusqu'au blanc, donc `1.8`, c'est
|
||
une journée lumineuse. `lightSetColor` la teinte : un blanc légèrement chaud
|
||
pour le soleil, un bleu froid pour le clair de lune, et la section Lumière de
|
||
la référence pour le reste.
|
||
|
||
`lightSetShadow(sun, true)` est ce qui fait que le dragon est posé sur le sol
|
||
au lieu de flotter au-dessus. Les ombres sont éteintes par défaut parce que
|
||
chaque lumière qui projette des ombres coûte une passe de plus sur toute la
|
||
scène à chaque image. Allumez-les pour la seule lumière qui compte et
|
||
laissez-les éteintes pour les autres ; une petite machine vous remerciera.
|
||
|
||
[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` est une ampoule. Elle se trouve à un endroit, brille dans toutes
|
||
les directions, et faiblit avec la distance. C'est à cause de cet
|
||
affaiblissement que le nombre est `14` et pas `1` : l'intensité d'une lumière
|
||
ponctuelle est sa luminosité à l'endroit même de la lumière, et une fois
|
||
qu'elle a traversé quelques unités, il n'en reste plus grand-chose. Une ampoule
|
||
qui éclaire une pièce demande en général quelque chose dans les dizaines. Si
|
||
une lumière ponctuelle semble ne rien faire, c'est presque toujours pour cette
|
||
raison.
|
||
|
||
`lightSetRange` dit jusqu'où elle porte avant de s'arrêter complètement, ce qui
|
||
empêche une lampe d'une pièce d'éclairer faiblement la suivante, et ça ne coûte
|
||
rien à régler.
|
||
|
||
La troisième sorte est `LIGHT_SPOT`, un cône pointé le long du Z négatif du
|
||
nœud lui-même, avec `lightSetCone` pour l'angle du milieu brillant et du bord
|
||
adouci : une lampe torche, un phare, un projecteur. Jusqu'à huit lumières
|
||
brillent à la fois, ce qui est plus qu'il n'en faut à la plupart des scènes. Si
|
||
vous en voulez plus, c'est que vous en voulez moins.
|
||
|
||
Lancez-le maintenant. Un soleil presque blanc venu de la gauche, une lueur
|
||
orange et chaude venue de la droite, une ombre sur la dalle, et un dragon qui a
|
||
un devant et un derrière.
|
||
|
||
[#_materials_at_the_level_you_need_today]
|
||
=== Les matériaux, juste ce qu'il vous faut aujourd'hui
|
||
|
||
Le dragon a apporté ses propres matériaux, et il y en a trois : un métal
|
||
sombre, un métal plus pâle couleur champagne, et un verre pâle dans lequel on
|
||
voit un peu en profondeur. La plupart de ses parties sont taillées dans plus
|
||
d'un. Vous n'avez à toucher à rien de tout ça, et la plupart du temps, vous ne
|
||
devriez pas.
|
||
|
||
Quand vous voulez vraiment changer l'aspect d'une partie d'un modèle, vous
|
||
créez un matériau comme dans la leçon vingt-deux et vous le mettez sur la
|
||
partie :
|
||
|
||
[source,lua]
|
||
----
|
||
local red = materialNew()
|
||
materialSetColor(red, 220, 50, 40)
|
||
materialSetRoughness(red, 0.4)
|
||
nodeSetMaterial(nodeFind("crest", dragon), red)
|
||
----
|
||
|
||
`nodeFind(name, root)` cherche dans une instance de modèle une partie d'après
|
||
le nom qu'elle avait dans le fichier et renvoie son nœud, ou `nil` quand ce nom
|
||
n'existe pas. C'est comme ça qu'on atteint l'intérieur d'un modèle : une
|
||
tourelle, une roue, une main où accrocher une torche, ou la crête du dragon.
|
||
|
||
La crête est d'une seule pièce et d'un seul matériau, donc son nœud porte la
|
||
forme elle-même et la ligne ci-dessus la repeint. Une partie faite de plusieurs
|
||
matériaux est gardée sous forme d'un nœud avec un enfant par pièce, et mettre
|
||
un matériau sur le parent ne change rien de visible. Quand un
|
||
`nodeSetMaterial` semble ne rien faire, c'est pour ça, et `nodeGetChildren`
|
||
vous montrera ce qu'il y a en dessous.
|
||
|
||
Quatre nombres décrivent la plupart des surfaces. La couleur, avec
|
||
`materialSetColor`. `materialSetRoughness`, de `0` pour un miroir à `1` pour de
|
||
la craie. `materialSetMetallic`, qui vaut `0` pour presque tout -- la peinture,
|
||
le bois, le plastique, la peau -- et `1` pour du vrai métal, les valeurs
|
||
intermédiaires servant peu. Et `materialSetEmissive`, qui fait briller une
|
||
surface de sa propre couleur pour les écrans, les lampes et le fer chauffé au
|
||
rouge, sans rien éclairer autour.
|
||
|
||
Une chose à savoir sur le métal : une surface métallique vous montre ses
|
||
reflets plutôt qu'une couleur à elle, donc dans une scène où il n'y a rien à
|
||
refléter, elle paraît sombre et terne. C'est pour ça que le métal sombre du
|
||
dragon a besoin du soleil pour prendre vie. La vraie solution est un ciel
|
||
(`sceneSetSky`), qui enveloppe la scène dans un panorama qui à la fois
|
||
s'affiche derrière tout et l'éclaire ; la section « L'aspect de l'image » de la
|
||
référence en parle.
|
||
|
||
[#_movement_the_model_brings_with_it]
|
||
=== Les mouvements que le modèle apporte
|
||
|
||
Beaucoup de modèles transportent des animations : une marche, une course, une
|
||
attente, une porte qui s'ouvre. Elles sont stockées dans le fichier, et en
|
||
jouer une se fait en un seul appel sur le nœud racine de l'instance.
|
||
|
||
[source,lua]
|
||
----
|
||
animationPlay(dragon, "Walk", true)
|
||
----
|
||
|
||
Le deuxième argument est le nom du clip tel que le fichier le stocke, ou son
|
||
numéro. `true` veut dire le jouer en boucle pour toujours. Deux nombres
|
||
facultatifs de plus règlent la vitesse et le fondu enchaîné :
|
||
`animationPlay(hero, "Run", true, 1.2, 0.4)` joue la course un cinquième plus
|
||
vite et passe en fondu vers elle en quatre dixièmes de seconde, si bien qu'une
|
||
marche devient une course sans à-coup. `animationStop`, `animationPause` et
|
||
`animationIsPlaying` font ce que dit leur nom.
|
||
|
||
Vous ne pouvez pas encore lancer cette ligne, et voici la raison honnête : le
|
||
dragon de votre dossier `Singe` ne contient aucune animation. C'est un modèle,
|
||
pas une performance d'acteur. Demander un clip qui n'existe pas arrête le jeu
|
||
avec une erreur, donc ne supposez jamais -- demandez :
|
||
|
||
[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` renvoie une table des noms de clips du fichier, dans
|
||
l'ordre, donc `clips[1]` est le premier et `#clips` est leur nombre -- les
|
||
appels de table de la leçon six, sur une table que le moteur a remplie.
|
||
Lancez-le et regardez la console : le message apparaît, parce que ce nombre
|
||
vaut zéro.
|
||
|
||
Prenez l'habitude de cette vérification avec tout modèle que vous n'avez pas
|
||
fait vous-même. Elle coûte trois lignes et elle transforme « le jeu est mort
|
||
au démarrage » en une phrase dans le journal.
|
||
|
||
[#_moving_the_parts_yourself]
|
||
=== Bouger les parties vous-même
|
||
|
||
Un modèle sans animations n'est pas une statue. Ses parties sont des nœuds, et
|
||
les nœuds tournent.
|
||
|
||
[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
|
||
----
|
||
|
||
Enregistrez. Les ailes battent, le dragon tourne lentement sur place, et
|
||
l'ombre sur le sol bat avec elles.
|
||
|
||
`singeGetTicks` est le nombre de millisecondes depuis le démarrage du moteur,
|
||
donc il grimpe régulièrement, sans fin. `math.sin` prend ce nombre qui grimpe
|
||
et renvoie une valeur qui glisse en douceur de `-1` à `1` et retour, un
|
||
battement complet environ toutes les secondes et demie avec ce diviseur.
|
||
Multiplier par `22` en fait un angle de vingt-deux degrés de chaque côté.
|
||
Diviser par un nombre plus grand ralentit le battement ; multiplier par un
|
||
nombre plus grand l'élargit. Cette seule ligne est l'animation la moins chère
|
||
qui existe, et elle vaut la peine d'être gardée sous le coude.
|
||
|
||
Les ailes tournent autour de leur propre axe X, et elles tournent autour de
|
||
l'épaule plutôt qu'autour du milieu de l'animal, parce que la personne qui a
|
||
construit le fichier a mis le nœud de chaque partie là où se trouve
|
||
l'articulation. Un modèle qui n'a pas été construit ainsi fera tourner ses
|
||
parties autour du centre du modèle, et la solution est de déplacer le nœud de
|
||
la partie, ce dont parle la section Nœud de la référence.
|
||
|
||
Remarquez que le dragon entier qui tourne ne gêne pas les ailes qui tournent.
|
||
Les parties sont accrochées sous la racine de l'instance, donc tourner la
|
||
racine emporte tout avec elle, et la rotation propre de chaque aile s'ajoute
|
||
par-dessus. Cette disposition -- une chose dans une chose dans une chose,
|
||
chacune bougeant dans le monde de son parent -- est ce à quoi sert un arbre de
|
||
scène.
|
||
|
||
[#_your_own_models]
|
||
=== Vos propres modèles
|
||
|
||
Quand vous faites les vôtres, exportez depuis Blender -- ou Maya, ou 3ds Max,
|
||
ou n'importe quel autre logiciel moderne -- en *glTF 2.0 binaire*, c'est-à-dire
|
||
un fichier `.glb`. C'est le choix par défaut de Blender, et c'est le seul
|
||
format de modèle que Singe lit.
|
||
|
||
La seule règle est que le fichier doit se suffire à lui-même : les formes, les
|
||
images posées dessus et les mouvements, tous à l'intérieur de l'unique `.glb`.
|
||
Un fichier qui renvoie à un `.bin` ou à un `.png` posé à côté de lui est
|
||
refusé, avec un message qui nomme ce qu'il voulait. L'exportateur de Blender
|
||
fait ce qu'il faut avec ses réglages par défaut.
|
||
|
||
Tout ce que vous pouvez construire ne survit pas au voyage. La section
|
||
« Modèles et animation » de la Référence, dans le chapitre « Scènes 3D », en donne la
|
||
liste : ce qui est lu, ce qui est ignoré sans bruit, et les limites (un
|
||
squelette peut avoir jusqu'à cent vingt-huit articulations, par exemple).
|
||
Lisez-la une fois avant de passer un week-end sur un modèle, pas après.
|
||
|
||
[#_try_it_23]
|
||
=== Essayez
|
||
|
||
. *Éteignez les lumières.* Mettez tout le bloc `sun` en commentaire en
|
||
ajoutant `--` au début de chaque ligne, et lancez. Puis faites de même avec
|
||
`sceneSetAmbient`. Retenez à quoi ça ressemble, parce qu'un jour ça vous
|
||
arrivera sans que vous l'ayez voulu.
|
||
. *Déplacez le soleil.* Changez `nodeLookAt(sun, 0, 1.5, 0)` en
|
||
`nodeLookAt(sun, 0, 20, 0)` pour que le soleil vise au-dessus de la tête du
|
||
dragon. Regardez l'ombre et les reflets s'en aller. Orienter une lumière
|
||
n'est pas de la décoration.
|
||
. *Réchauffez la torche.* Montez l'intensité de la lumière ponctuelle de `14` à
|
||
`60` et baissez sa portée à `4`. Puis mettez-la en `(0, 6, 0)`, au-dessus du
|
||
dragon. Une lumière ponctuelle est une ampoule, et c'est vous qui choisissez
|
||
où l'ampoule est suspendue.
|
||
. *Instanciez-le deux fois.* Appelez `modelInstance(dragonModel)` une deuxième
|
||
fois, gardez le résultat dans une autre variable, mettez-le à l'échelle, et
|
||
placez-le en `(-4, 0, -3)`. Un chargement, deux dragons. Puis essayez plutôt
|
||
de charger le modèle deux fois et comprenez pourquoi il ne faut pas.
|
||
. *Trouvez une autre partie.* Les parties du dragon s'appellent `head`, `jaw`,
|
||
`neck`, `tail`, `body`, `crest`, `hornL`, `hornR`, `wingL`, `wingR`,
|
||
`foreLegL`, `foreLegR`, `hindLegL` et `hindLegR`. Faites onduler la queue
|
||
avec son propre `math.sin` à une autre vitesse que les ailes. Puis essayez
|
||
`nodeFind("wings", dragon)` et voyez ce que fait un mauvais nom quand vous
|
||
passez directement le `nil` à `nodeSetRotation`.
|
||
|
||
[#_break_it_on_purpose_22]
|
||
=== Cassez-le exprès
|
||
|
||
Changez le nom du fichier en `Singe/Dragonmodel.glb` -- une seule majuscule --
|
||
et lancez. Singe s'arrête avant que le jeu démarre :
|
||
|
||
----
|
||
13:modelLoad: Unable to read Singe/Dragonmodel.glb.
|
||
----
|
||
|
||
La ligne, l'appel et la plainte, dans cet ordre. « Unable to read » veut dire
|
||
que le moteur a cherché et n'a rien trouvé, ce qui est presque toujours l'une
|
||
de trois choses : le nom est mal orthographié, les majuscules sont fausses, ou
|
||
le fichier n'est pas là où vous l'avez dit. Sous Windows, les majuscules vous
|
||
auraient été pardonnées, et votre jeu aurait ensuite échoué pour tout le monde
|
||
sous Linux, ce qui est un bug pire que celui-ci. Tapez les noms
|
||
exactement.
|
||
|
||
Le même message avec un vrai nom de fichier veut dire que le fichier n'est pas
|
||
là où le moteur a regardé. Le dossier `Singe` est créé dans le dossier *depuis*
|
||
lequel vous lancez le moteur, donc si vous avez démarré Singe depuis ailleurs,
|
||
`Singe/DragonModel.glb` pointe ailleurs aussi.
|
||
|
||
[#_what_you_learned_23]
|
||
=== Ce que vous avez appris
|
||
|
||
* `modelLoad` lit un `.glb` une fois ; `modelInstance` en place des copies
|
||
aussi souvent que vous voulez.
|
||
* La racine d'une instance est un nœud ordinaire : placez-la, tournez-la,
|
||
mettez-la à l'échelle et supprimez-la comme n'importe quel autre.
|
||
* Un modèle apporte avec lui ses propres formes et ses propres matériaux.
|
||
* Un modèle arrive à la taille qu'a utilisée son auteur, et c'est pour ça qu'un
|
||
`nodeSetScale` sur l'instance fait partie de sa mise en place.
|
||
* Une scène sans lumière est noire ou presque, et c'est la première chose à
|
||
vérifier quand un modèle a l'air faux.
|
||
* Une lumière directionnelle est un soleil, une lumière ponctuelle est une
|
||
ampoule qui faiblit avec la distance et demande une intensité bien plus
|
||
grande, et un spot est un cône.
|
||
* Les ombres sont éteintes tant que `lightSetShadow` ne les allume pas, et
|
||
chacune a un coût.
|
||
* `nodeFind` atteint une partie d'un modèle par le nom qu'elle a dans le
|
||
fichier.
|
||
* `modelGetAnimations` dit ce qu'un modèle peut jouer, et demander un clip qui
|
||
n'existe pas est une erreur.
|
||
* Un modèle sans animations peut quand même être bougé une partie à la fois.
|
||
|
||
[#_next_time_23]
|
||
=== La prochaine fois
|
||
|
||
Tout dans cette scène bouge parce que vous le lui avez dit, image par image. La
|
||
prochaine leçon confie ce travail à un moteur physique : des caisses qui
|
||
tombent, roulent, s'empilent et se renversent les unes les autres sans que vous
|
||
écriviez une ligne d'arithmétique -- et la question plus difficile de savoir
|
||
quand il faut le laisser faire. C'est la leçon vingt-quatre.
|