WENOPAL

05 · Modelos 3D

Qué hace el motor con un modelo, cómo escribir los model del script, poner un objeto en la mano y en el suelo, y armar una prenda que se vea bien puesta.

Curso técnico · Build 42 · Actualizado el

Objetivo: entender qué hace el motor con un modelo (y qué no hace), escribir los model del script, poner un objeto en la mano y en el suelo, y armar una prenda que se vea bien puesta.

1. Qué hace el motor con un modelo

Esto define cómo trabajas, así que va primero (verificado en los shaders del juego):

  • Una sola textura difusa por modelo. El shader de objetos solo tiene la textura, un tinte y luces direccionales: no hay normal map, specular ni AO. Todo detalle fino tiene que estar pintado u horneado en la textura.
  • Las normales por vértice sí importan: la luz se calcula por normal interpolada. Suaves en lo orgánico, duplicadas (normal plana) solo en aristas duras.
  • Hay culling: si las caras quedan al revés, el modelo se ve "plano" o invisible por un lado.
  • La cámara está lejos y arriba: un objeto ocupa pocos píxeles. Contraste fuerte y formas grandes ganan a detalle fino.

Presupuesto (contado en los .x del juego): una taza tiene ~164 triángulos, un martillo ~300, el cuerpo entero del personaje 1.838. Un objeto de mano razonable: 150–1.500.

2. Formatos

  • .x (DirectX, texto): el que más usan nuestros mods; es legible y se puede generar con Python. Estructura: cabecera de plantillas, un Frame con su matriz, Mesh, MeshNormals, MeshMaterialList, MeshTextureCoords (v = 0 arriba) y, si tiene huesos, SkinWeights.
  • .fbx: lo que exporta Blender; lo usa Catimbas para la ropa con huesos.
  • Ambos van en media/models_X/. En los scripts la ruta se escribe sin extensión y relativa a models_X/.

Abre un .x de Tocomples Food (models_X/WorldItems/PapaPletoHand.x, 7.148 líneas) y localiza cada bloque. Es un buen ejercicio para dejar de verlo como caja negra.

3. El model del script

Del CompletoItaliano.txt de Tocomples Food:

model CompletoItaliano                      ← el modelo EN LA MANO
{
    mesh = WorldItems/CompletoItalianoHand,  ← models_X/WorldItems/CompletoItalianoHand.x
    texture = CompletoItaliano,              ← textures/CompletoItaliano.png
    scale = 1.0,
    attachment Bip01_Prop1                   ← cómo se agarra con la mano derecha
    {
        offset = 0.0057 0.0051 0.0246,
        rotate = 172.7064 35.0347 178.1460,
        scale = 0.7666,
    }
    attachment Bip01_Prop2 { ... mismo ... } ← mano izquierda
}
model CompletoItalianoGround                 ← el modelo EN EL SUELO
{
    mesh = WorldItems/CompletoItaliano,
    texture = CompletoItaliano,
    scale = 0.75,
}

Y en el ítem, con el módulo delante:

StaticModel = CompleteItaliano.CompletoItaliano,
WorldStaticModel = CompleteItaliano.CompletoItalianoGround,
primaryAnimMask = HoldingUmbrellaRight,      ← pose de la mano al sostenerlo
secondaryAnimMask = HoldingUmbrellaLeft,

Claves:

  • Mano y suelo son dos modelos distintos: el de suelo apoya en y = 0; el de mano tiene el punto de agarre en el origen.
  • Bip01_Prop1 / Bip01_Prop2 son los huesos de las manos del esqueleto. Los valores de offset/rotate/scale de arriba son los que usan todos nuestros objetos de mano (salen de BeerBottle.x del juego): úsalos de base.
  • Sin primaryAnimMask el objeto queda en la mano pero el brazo no lo "sostiene".

Ejes y escala

Metros, tamaño real, Y arriba, Z adelante. El juego carga los modelos convirtiéndolos a mano izquierda y el importador de .x invierte Z: si un modelo sale espejado o con las caras al revés, el problema casi siempre es este.

4. Modelos con huesos (skinned): ropa

Un modelo que se dobla con el cuerpo lleva pesos que lo atan a los huesos del esqueleto (Bip01_*). Dos caminos que ya funcionaron en nuestros mods:

  1. .x: tomar MaleBody.x del juego, reemplazar solo la malla y conservar huesos y matrices (en nuestros mods lo hace un script propio de rigging).
  2. FBX: partir de una plantilla con el esqueleto Bip01, cambiar la malla, copiar pesos y exportar con la misma configuración (así lo hace el generador de modelos de Catimbas).

Regla de pesos: un objeto rígido (zapato, casco) sigue un solo hueso; mezcla solo en la articulación. Si copias los pesos de la piel, se estira como goma.

Las tres piezas de una prenda

1. Ítem (script)            ClothingItem = Catimbas_Nike_Dunk_GreyFog, BodyLocation = base:shoes
2. Prenda visual (XML)      media/clothing/clothingItems/Catimbas_Nike_Dunk_GreyFog.xml
3. Registro                 media/fileGuidTable.xml  →  ruta del XML ↔ GUID

El XML real de una zapatilla de Catimbas:

xml
<clothingItem>
    <m_MaleModel>media\models_X\Skinned\Clothes\Catimbas\M_Catimbas_Low.fbx</m_MaleModel>
    <m_FemaleModel>media\models_X\Skinned\Clothes\Catimbas\F_Catimbas_Low.fbx</m_FemaleModel>
    <m_GUID>20669123-b8ac-5c8c-a1b3-38ad26e2e14e</m_GUID>
    <m_Static>false</m_Static>
    <m_Masks>8</m_Masks>
    <m_Masks>10</m_Masks>
    <m_MasksFolder>none</m_MasksFolder>        ← la malla tiene UV propio (sección 5)
    <textureChoices>clothes/catimbas/nike_dunk_greyfog</textureChoices>
</clothingItem>
  • El GUID no cambia nunca: si cambia, las partidas guardadas pierden la prenda. Genéralo determinístico desde el nombre (uuid5), como Catimbas.
  • textureChoices para prendas con modelo; m_BaseTextures solo para ropa que se pinta sobre el cuerpo (poleras).
  • Una malla, muchas texturas: las 89 zapatillas de Catimbas comparten pocas mallas y cambian solo la textura.

5. El error de los hoyos (m_MasksFolder)

El juego recorta cada prenda con máscaras dibujadas para el UV del cuerpo, para que no asome lo que queda debajo. Si tu malla tiene UV propio, los recortes caen al azar: la prenda se ve perfecta en el suelo y con "láminas que no cierran" puesta.

  • Malla con UV propio → <m_MasksFolder>none</m_MasksFolder>.
  • Malla del juego que usa el UV del cuerpo (pantalones, chaquetas) → no lo pongas.

Antes de encontrar esto se probó invertir caras y hacer la malla de doble cara: no era eso. Es el mejor ejemplo de por qué el módulo 15 insiste en probar puesto, en el juego.

6. Herramientas

No tienes que modelar a mano para practicar: se puede trabajar con generadores y un editor. Estos son los que usamos en nuestros mods:

HerramientaPara qué
El generador de .x de Terremoto (write_x, check_winding, to_hand)Generar .x desde Python y revisar que las caras miren afuera
Un modelador en el navegador (herramienta interna)Retocar, extruir, simplificar y guardar .x
Un lector propio de .x con huesosLeer .x con huesos y animaciones fuera del juego
bpy como librería (python3.11)Blender sin interfaz: importar, pesar, exportar FBX, hornear

Ejercicios

Ejercicio 5.1 · Leer un .x

Con PapaPletoHand.x de Tocomples Food: cuenta vértices y triángulos, encuentra el nombre de la textura, y di dónde está el origen respecto al objeto. Escribe un script de 30 líneas que lo haga por ti.

Listo cuando: tu script da los mismos números para tres modelos distintos que contar a mano uno de ellos.

Ejercicio 5.2 · Un objeto en la mano y en el suelo

Haz un modelo simple para la Sopaipilla (un disco con borde, 100–300 triángulos): versión de mano y versión de suelo, con su textura.

Listo cuando: se ve en el suelo apoyado (no flotando ni enterrado), y en la mano el personaje la sostiene bien en las dos manos.

Ejercicio 5.3 · Romper el modelo a propósito

  1. Invierte el orden de los vértices de todas las caras. ¿Qué ves?
  2. Pon scale = 10 en el modelo de suelo.
  3. Quita primaryAnimMask.
  4. Escribe el StaticModel sin módulo delante.

Anota síntoma → causa, como en el ejercicio 2.4.

Ejercicio 5.4 · Una prenda reusando una malla del juego

Haz CursoLab.GorroCurso: un gorro que reusa el modelo de un gorro del juego y solo cambia la textura (como Jockies, uno de nuestros mods). Necesitas ítem, XML con GUID determinístico y entrada en fileGuidTable.xml.

Listo cuando: te lo pones, se ve con tu textura, y sigue ahí después de guardar y cargar la partida.

Ejercicio 5.5 · El caso de los hoyos

Con una malla de UV propio (puedes tomar un zapato de Catimbas), quita <m_MasksFolder>none</m_MasksFolder> y pruébalo con pantalón y calcetines. Saca una captura del problema y otra arreglado.

Desafío 5

Genera desde Python un modelo de mano para un ítem de CursoLab con iluminación horneada en la textura (luz cenital calculada con las normales), y compáralo en el juego contra la misma textura sin hornear.


Anterior: 04 · Texturas · Siguiente: 06 · Lua en PZ (Kahlua)