[#_lesson_23_models_and_light] == Lección 23: Modelos y luz image::learn/23-models.png[La lección terminada, 480] Las cajas y las esferas te llevarán lejos, pero tarde o temprano el juego quiere un dragón. Un *modelo* es una forma que ha construido otra persona en un programa de modelado: cientos de triángulos, sus propios colores y superficies, a veces un esqueleto y unos movimientos que lo acompañan, todo en un archivo. Cargarlo son dos líneas. Conseguir que parezca algo es cuestión de luz, y la luz es la mayor parte de esta lección. No necesitas arte ni descargas. La primera vez que ejecutaste Singe, desempaquetó una carpeta llamada `Singe` en tu carpeta de trabajo, y dentro hay dos modelos: `DragonModel.glb`, el dragón de la pantalla de título del propio motor, y `SingeText.glb`, las letras que hay debajo. Esta lección usa el dragón. [#_the_scene_around_it] === La escena a su alrededor Un modelo necesita un sitio donde apoyarse. Crea una carpeta, crea dentro `dragon.singe` y empieza con un suelo y una cámara. Si has hecho la lección veintidós, nada de esto es nuevo; si no, es una escena encendida, una losa plana sobre la que apoyarse y un punto desde el que mirar. [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 ---- Ejecútalo con `Singe -R dragon`. Una losa gris, vista un poco desde arriba, sobre un fondo azul oscuro. La losa mide veinticuatro unidades de lado y algo menos de media unidad de grosor, y colocarla en `y = -0.2` pone su cara de arriba exactamente en `y = 0`, lo que hace que sea fácil pensar en todo lo que coloques después: el suelo es cero. [#_loading_the_dragon] === Cargar el dragón Dos líneas. [source,lua] ---- local dragonModel = modelLoad("Singe/DragonModel.glb") local dragon = modelInstance(dragonModel) ---- Ponlas después del suelo y guarda. Aparece un dragón, y es muchísimo más grande de lo que cabe en la imagen -- estás mirando la mitad de su cuerpo. [#_what_just_happened_19] === Qué acaba de pasar Las dos líneas hacen trabajos distintos, y la diferencia importa. `modelLoad` lee el archivo, le pasa las formas y sus superficies a la tarjeta gráfica y lo deja todo listo. Es la llamada lenta. Hazla una vez, cuando empieza el juego, y nunca en mitad de la partida. `modelInstance` coloca una copia en la escena y te devuelve un nodo -- un nodo normal, del mismo tipo que te da `nodeNew`, que colocas, giras, escalas y borras con las llamadas que ya conoces. Instanciar es la llamada barata. Carga un árbol, instáncialo cuarenta veces y tendrás un bosque, con una sola copia de las formas en la tarjeta gráfica y cuarenta sitios donde se dibujan. Lo que viene con el modelo es todo lo que había en el archivo: las formas, las superficies y sus colores, cómo están colocadas las partes, el esqueleto si lo hay y los movimientos si los hay. No construyes materiales para un modelo cargado; ya ha traído los suyos. [#_scale_and_the_first_thing_that_goes_wrong] === La escala, y lo primero que sale mal El dragón mide unas siete unidades y media de alto. Tu cámara está nueve unidades más atrás. Por eso llena la ventana. [source,lua] ---- nodeSetScale(dragon, 0.5) ---- Añade eso debajo de la línea `modelInstance`. Ahora mide menos de cuatro unidades de alto, se apoya en el suelo y cabe en el plano. Esto no es un detalle; es el primer problema más común con un modelo, y tiene dos caras. Un modelo construido en centímetros llega cien veces más grande de la cuenta y ves una superficie enorme llenando la pantalla, lo que suele parecer «mi escena está rota» más que «estoy dentro del cuello del dragón». Un modelo construido en alguna otra unidad llega tan pequeño que es un punto, o invisible contra el fondo, y das por hecho que el archivo no se ha cargado. El arreglo es siempre el mismo: una escala en el nodo de la instancia. No en las partes, ni en el programa de modelado si puedes evitarlo, sino un único `nodeSetScale` en el nodo que te ha dado `modelInstance`, elegido probando un número y mirando. `0.01` y `100` son respuestas perfectamente normales las dos. Cuando no sepas lo grande que es algo, búscate una regla. Pon una caja de una unidad a su lado: [source,lua] ---- local ruler = nodeNew() nodeSetMesh(ruler, meshBox(1, 1, 1), stone) nodeSetPosition(ruler, 3, 0.5, 0) ---- Un objeto del tamaño de un cajón al lado del modelo te dice de un vistazo si el modelo es un dragón o una montaña. Quita la regla cuando termines. Mientras lo colocas, gíralo para que te mire: [source,lua] ---- nodeSetRotation(dragon, 0, -60, 0) ---- El dragón se modeló mirando a lo largo de la X positiva, que es hacia la derecha, así que empieza de perfil. Girarlo alrededor de Y lo vuelve hacia la cámara. El «frente» de un modelo es la dirección que haya elegido quien lo construyó, y para averiguarlo hay que girarlo y mirar. [#_light] === Luz Ejecuta lo que tienes ahora. El dragón está ahí, y es deprimente: una forma plana y apagada, sin reflejos y sin sombra. Eso es porque la única luz de tu escena es la ambiente, el débil resplandor que llega de todas partes y que fija `sceneSetAmbient`. Esta es la decepción más común del 3D, y le pasa a todo el mundo: el modelo se ha cargado perfectamente y la escena está negra, o casi, porque no le da ninguna luz. Antes de sospechar del archivo, pon una luz. Hay tres tipos, y cada una es un nodo, así que las colocas y las apuntas con `nodeSetPosition` y `nodeLookAt` como cualquier otra cosa. [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` es un sol. Se trata como si estuviera infinitamente lejos, así que solo cuenta la dirección en la que brilla y todo lo que hay en la escena recibe la misma cantidad. Su posición está ahí para que `nodeLookAt` la apunte. Esta es la luz que le da a una escena su forma principal, y la mayoría de las escenas quieren exactamente una. `lightSetIntensity` es el brillo. En una luz direccional, `1` lleva hasta el blanco una superficie blanca que la tiene de frente, así que `1.8` es un día luminoso. `lightSetColor` la tiñe: un blanco un poco cálido para el sol, un azul frío para la luz de luna, y la sección Luz de la referencia para el resto. `lightSetShadow(sun, true)` es lo que hace que el dragón se apoye en el suelo en lugar de flotar encima. Las sombras están desactivadas por defecto porque cada luz que proyecta sombras cuesta otra pasada por toda la escena en cada fotograma. Actívala en la única luz que importa y déjala desactivada en las demás; una máquina pequeña te lo agradecerá. [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` es una bombilla. Está en un sitio, brilla en todas las direcciones y se debilita con la distancia. Ese debilitamiento es la razón de que el número sea `14` y no `1`: la intensidad de una luz puntual es su brillo en la propia luz, y para cuando ha recorrido unas cuantas unidades no queda mucho. Una bombilla que ilumina una habitación suele querer algo de unas decenas. Si una luz puntual parece no hacer nada, casi siempre es por eso. `lightSetRange` dice hasta dónde llega antes de apagarse del todo, lo que evita que una lámpara de una habitación ilumine débilmente la siguiente, y no cuesta nada ponerlo. El tercer tipo es `LIGHT_SPOT`, un cono que apunta a lo largo de la Z negativa del propio nodo, con `lightSetCone` para el ángulo del centro brillante y del borde suave: una linterna, un faro, un reflector. Pueden brillar hasta ocho luces a la vez, que es más de lo que necesitan la mayoría de las escenas. Si quieres más, lo que quieres es menos. Ejecútalo ahora. Un sol casi blanco desde la izquierda, un resplandor naranja cálido desde la derecha, una sombra en la losa y un dragón con una parte de delante y otra de detrás. [#_materials_at_the_level_you_need_today] === Materiales, al nivel que necesitas hoy El dragón ha traído sus propios materiales, y son tres: un metal oscuro, un metal champán más claro y un cristal pálido en el que se ve un poco hacia dentro. La mayoría de sus partes están hechas de más de uno. No tienes que tocar nada de eso, y casi siempre no deberías. Cuando sí quieras cambiar el aspecto de una parte de un modelo, creas un material como en la lección veintidós y se lo pones a la parte: [source,lua] ---- local red = materialNew() materialSetColor(red, 220, 50, 40) materialSetRoughness(red, 0.4) nodeSetMaterial(nodeFind("crest", dragon), red) ---- `nodeFind(name, root)` busca en una instancia de modelo una parte por el nombre que tenía en el archivo y te devuelve su nodo, o `nil` cuando no hay ningún nombre así. Así es como llegas al interior de un modelo: una torreta, una rueda, una mano de la que colgar una antorcha, o la cresta del dragón. La cresta es una sola pieza de un solo material, así que su nodo lleva la forma en sí y la línea de arriba la vuelve a pintar. Una parte construida con varios materiales se guarda como un nodo con un hijo por pieza, y ponerle un material al padre no cambia nada que se pueda ver. Cuando un `nodeSetMaterial` parece no hacer nada, es por eso, y `nodeGetChildren` te enseñará lo que hay debajo. Cuatro números describen la mayoría de las superficies. El color, con `materialSetColor`. `materialSetRoughness`, de `0` para un espejo a `1` para la tiza. `materialSetMetallic`, que es `0` para casi todo -- pintura, madera, plástico, piel -- y `1` para el metal de verdad, y los valores intermedios sirven de poco. Y `materialSetEmissive`, que hace que una superficie brille con su propio color, para pantallas, lámparas y hierro al rojo, sin iluminar nada a su alrededor. Una cosa que debes saber del metal: una superficie metálica te muestra sus reflejos en lugar de un color propio, así que en una escena sin nada que reflejar se ve oscura y apagada. Por eso el metal oscuro del dragón necesita el sol para cobrar vida. El arreglo de verdad es un cielo (`sceneSetSky`), que envuelve la escena en un panorama que a la vez se ve detrás de todo y lo ilumina; la sección «El aspecto del fotograma» de la referencia lo explica. [#_movement_the_model_brings_with_it] === El movimiento que trae el modelo Muchos modelos llevan animaciones: un paseo, una carrera, una pose de reposo, una puerta que se abre. Se guardan en el archivo, y reproducir una es una sola llamada sobre el nodo raíz de la instancia. [source,lua] ---- animationPlay(dragon, "Walk", true) ---- El segundo argumento es el nombre del clip tal como lo guarda el archivo, o su número. `true` significa repetirlo para siempre. Dos números opcionales más fijan la velocidad y el fundido cruzado: `animationPlay(hero, "Run", true, 1.2, 0.4)` reproduce la carrera una quinta parte más rápido y pasa a ella fundiéndose durante cuatro décimas de segundo, así que el paseo se convierte en carrera sin un salto brusco. `animationStop`, `animationPause` y `animationIsPlaying` hacen lo que dicen sus nombres. No puedes ejecutar esa línea todavía, y esta es la razón sincera: el dragón de tu carpeta `Singe` no tiene ninguna animación. Es un modelo, no una actuación. Pedir un clip que no existe detiene el juego con un error, así que nunca des nada por hecho -- pregunta: [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` te devuelve una tabla con los nombres de los clips del archivo, en orden, así que `clips[1]` es el primero y `#clips` es cuántos hay -- las llamadas de tablas de la lección seis, sobre una tabla que ha llenado el motor. Ejecútalo y mira la consola: aparece el mensaje, porque esa cuenta es cero. Coge la costumbre de hacer esa comprobación con cualquier modelo que no hayas hecho tú. Cuesta tres líneas y convierte «el juego murió al arrancar» en una frase en el registro. [#_moving_the_parts_yourself] === Mover las partes tú mismo Un modelo sin animaciones no es una estatua. Sus partes son nodos, y los nodos giran. [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 ---- Guárdalo. Las alas baten, el dragón gira despacio sobre sí mismo, y la sombra del suelo bate con ellas. `singeGetTicks` es el número de milisegundos desde que arrancó el motor, así que sube sin parar para siempre. `math.sin` toma ese número que sube y devuelve un valor que se desliza suavemente de `-1` a `1` y vuelta, un aleteo completo más o menos cada segundo y medio con este divisor. Multiplicar por `22` lo convierte en un ángulo de veintidós grados hacia cada lado. Dividir por un número mayor hace el aleteo más lento; multiplicar por un número mayor lo hace más amplio. Esa línea es la animación más barata que existe, y vale la pena llevarla siempre en el bolsillo. Las alas giran alrededor de su propio eje X, y giran alrededor del hombro y no del centro del animal, porque quien construyó el archivo puso el nodo de cada parte donde va la articulación. Un modelo que no se construyó así hará girar sus partes alrededor del centro del modelo, y el arreglo es mover el nodo de la parte, cosa que explica la sección Nodo de la referencia. Fíjate en que el giro de todo el dragón no interfiere con el giro de las alas. Las partes cuelgan de la raíz de la instancia, así que girar la raíz se lo lleva todo consigo, y la rotación propia de cada ala va encima de eso. Ese montaje -- una cosa dentro de una cosa dentro de una cosa, cada una moviéndose en el mundo de su padre -- es para lo que sirve un árbol de escena. [#_your_own_models] === Tus propios modelos Cuando hagas los tuyos, expórtalos desde Blender -- o Maya, o 3ds Max, o cualquier otro programa moderno -- como *glTF 2.0 binario*, que es un archivo `.glb`. Es la opción por defecto de Blender y es el único formato de modelo que lee Singe. La única regla es que el archivo tiene que ser autosuficiente: las formas, las imágenes que llevan encima y los movimientos, todo dentro del único `.glb`. Un archivo que hace referencia a un `.bin` o a un `.png` que está a su lado se rechaza, con un mensaje que nombra lo que quería. El exportador de Blender hace lo correcto con sus ajustes por defecto. No todo lo que puedes construir sobrevive al viaje. La sección «Modelos y animación» de la referencia, en el capítulo «Escenas 3D», es la lista: lo que se lee, lo que se ignora sin avisar y los límites (un esqueleto puede tener hasta ciento veintiocho articulaciones, por ejemplo). Léela una vez antes de pasarte un fin de semana con un modelo, no después. [#_try_it_23] === Pruébalo . *Apaga las luces.* Comenta todo el bloque del `sun` poniendo `--` al principio de cada línea, y ejecútalo. Luego haz lo mismo con `sceneSetAmbient`. Recuerda cómo se ve, porque un día te pasará sin querer. . *Mueve el sol.* Cambia `nodeLookAt(sun, 0, 1.5, 0)` por `nodeLookAt(sun, 0, 20, 0)` para que el sol apunte por encima de la cabeza del dragón. Mira cómo se van la sombra y los reflejos. Apuntar una luz no es decoración. . *Calienta la antorcha.* Sube la intensidad de la luz puntual de `14` a `60` y baja su alcance a `4`. Luego muévela a `(0, 6, 0)`, encima del dragón. Una luz puntual es una bombilla, y tú eliges dónde cuelga la bombilla. . *Instáncialo dos veces.* Llama a `modelInstance(dragonModel)` una segunda vez, guárdalo en otra variable, escálalo y ponlo en `(-4, 0, -3)`. Una carga, dos dragones. Luego prueba en cambio a cargar el modelo dos veces y entiende por qué no deberías. . *Busca otra parte.* Las partes del dragón se llaman `head`, `jaw`, `neck`, `tail`, `body`, `crest`, `hornL`, `hornR`, `wingL`, `wingR`, `foreLegL`, `foreLegR`, `hindLegL` y `hindLegR`. Haz que la cola se balancee con su propio `math.sin` a una velocidad distinta de la de las alas. Luego prueba `nodeFind("wings", dragon)` y mira lo que hace un nombre equivocado cuando le pasas el `nil` directamente a `nodeSetRotation`. [#_break_it_on_purpose_22] === Rómpelo a propósito Cambia el nombre del archivo a `Singe/Dragonmodel.glb` -- una sola mayúscula de diferencia -- y ejecútalo. Singe se detiene antes de que arranque el juego: ---- 13:modelLoad: Unable to read Singe/Dragonmodel.glb. ---- La línea, la llamada y la queja, en ese orden. «Unable to read» significa que el motor ha buscado y no ha encontrado nada, y eso casi siempre es una de tres cosas: el nombre está mal escrito, las mayúsculas están mal o el archivo no está donde dijiste. En Windows se te habrían perdonado las mayúsculas, y entonces tu juego fallaría a todo el que use Linux, que es un fallo peor que este. Escribe los nombres exactamente. El mismo mensaje con un nombre de archivo correcto significa que el archivo no está donde lo buscó el motor. La carpeta `Singe` se crea en la carpeta *desde* la que ejecutas el motor, así que si arrancaste Singe desde otro sitio, `Singe/DragonModel.glb` también apunta a otro sitio. [#_what_you_learned_23] === Lo que has aprendido * `modelLoad` lee un `.glb` una vez; `modelInstance` coloca copias de él tantas veces como quieras. * La raíz de una instancia es un nodo normal: colócala, gírala, escálala y bórrala como cualquier otro. * Un modelo trae consigo sus propias formas y materiales. * Un modelo llega al tamaño que usó su autor, y por eso un `nodeSetScale` en la instancia forma parte de colocarlo. * Una escena sin luz está negra o casi, y es lo primero que hay que comprobar cuando un modelo se ve mal. * Una luz direccional es un sol, una luz puntual es una bombilla que se debilita con la distancia y necesita una intensidad mucho mayor, y un foco es un cono. * Las sombras están desactivadas hasta que `lightSetShadow` las activa, y cada una tiene su coste. * `nodeFind` llega a una parte de un modelo por su nombre en el archivo. * `modelGetAnimations` dice qué puede reproducir un modelo, y pedir un clip que no existe es un error. * Un modelo sin animaciones se puede mover igualmente, parte a parte. [#_next_time_23] === La próxima vez Todo lo que hay en esta escena se mueve porque tú se lo has dicho, fotograma a fotograma. La próxima lección le pasa ese trabajo a un motor de física: cajas que caen, ruedan, se apilan y se tiran unas a otras sin que escribas ni una línea de aritmética -- y la pregunta más difícil de cuándo deberías dejarle. Esa es la lección veinticuatro.