Saltar a contenido

Tema 6.3 - Juego 3D: materiales, físicas, cámara e iluminación

Un juego 3D es lo mismo que el 2D con una dimensión más: objetos, física y una cámara que mira. Lo que cambia es que ahora la luz y los materiales deciden si el objeto se ve o se ve como un pegote.

🟢 Leyenda de repaso obligatorio: los textos con este fondo verde son imprescindibles y entran en todas las evaluaciones del módulo.

1. El árbol de la escena 3D

Fisica3D (Node3D)                ← script fisica3d.gd
├── Camara (Camera3D)            ← mira la escena desde (0, 4.2, 8.5)
├── Luz (DirectionalLight3D)     ← luz del sol, con sombras
├── Suelo (StaticBody3D)         ← no se mueve: el suelo
│   ├── Malla (MeshInstance3D)
│   └── Forma (CollisionShape3D) ← caja de 12 × 0,4 × 12
├── Caja (RigidBody3D)           ← cae por la gravedad
│   ├── Malla (MeshInstance3D)
│   └── Forma (CollisionShape3D) ← caja de 1 × 1 × 1
└── HUD (CanvasLayer)
    └── Altura (Label)           ← "altura de la caja: 3.20 m"

En 3D cada objeto necesita tres nodos: MeshInstance3D (lo que se ve), CollisionShape3D (lo que choca) y el cuerpo que lo agrupa.

2. Las tres familias de cuerpos en 3D

Nodo Quién lo mueve Uso
RigidBody3D la física (gravedad, impulsos) cajas, pelotas, escombros
CharacterBody3D tu código jugador en primera/tercera persona
StaticBody3D nadie suelo, paredes, rampas

RigidBody3D no se mueve con position: se mueve con apply_impulse() o dejándolo caer. Cambiarle la posición a mano rompe la simulación.

3. Propiedades físicas (CE e)

Un RigidBody3D se comporta según cuatro propiedades:

Propiedad Valor por defecto Qué controla
mass 1.0 kg cómo responde a los impulsos y a otras masas
gravity_scale 1.0 multiplicador de la gravedad (2.0 = cae el doble de rápido)
physics_material_override ninguno rebote (bounce) y rozamiento (friction)
linear_damp 0.0 frenado en el aire

La gravedad del proyecto es 9,8 m/s² en Y negativa. gravity_scale = 2 la duplica sin tocar la configuración del proyecto.

Para que la caja rebote hay que darle un material físico, no un material visual:

# Se crea un material físico y se le pone rebote 0,6 y rozamiento 0,3
var fisico := PhysicsMaterial.new()
fisico.bounce = 0.6          # 0 = no rebota, 1 = rebote perfecto
fisico.friction = 0.3        # 0 = patina como hielo
caja.physics_material_override = fisico

bounce y friction van en un PhysicsMaterial, no en el StandardMaterial3D. Confundirlos es el error clásico: el objeto se ve bien pero no rebota.

4. Materiales (CE d)

El aspecto final de la superficie lo decide el StandardMaterial3D:

Propiedad Qué hace Valor en nuestra caja
albedo_color color base azul (0.36, 0.62, 0.95)
metallic 0 = plástico, 1 = metal 0,4
roughness 0 = espejo, 1 = mate 0,25 (brillo suave)
emission_enabled convierte el objeto en fuente de luz desactivado
[sub_resource type="StandardMaterial3D" id="Material_caja"]
albedo_color = Color(0.36, 0.62, 0.95, 1)
metallic = 0.4
roughness = 0.25

[sub_resource type="BoxMesh" id="BoxMesh_caja"]
size = Vector3(1, 1, 1)

El material no basta: hace falta una malla (BoxMesh) que le dé forma y una forma de colisión (BoxShape3D) que le dé cuerpo. Un MeshInstance3D sin mesh no dibuja nada, aunque tenga material.

{ V('Un MeshInstance3D sin mesh no se ve: el material solo se aplica a una superficie, y sin malla no hay superficie.') }

metallic y roughness solo se notan si hay luz y entorno. Un objeto metálico sin luz alrededor se ve negro: el material no crea la luz.

5. Cámara (CE g)

La cámara es el ojo del jugador. Su transform guarda posición y rotación juntas, así que se lee mal a simple vista. Lo importante:

Propiedad Valor Efecto
fov 75° campo de visión (más grados = más angular)
near 0,05 m lo más cerca que se ve
far 4000 m lo más lejos que se ve
current activada es la cámara que se usa
[node name="Camara" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.94, 0.34, 0, -0.34, 0.94, 0, 4.2, 8.5)

Los tres primeros números son el eje X, los tres siguientes el eje Y, luego el Z, y los tres últimos son la posición (0, 4.2, 8.5): 4,2 m de alto y 8,5 m atrás, mirando en diagonal hacia abajo.

En un Transform3D los tres últimos números son la posición. Los nueve primeros son los ejes de rotación: es una matriz de 3×3 más un vector.

6. Iluminación (CE g)

Sin luz, un MeshInstance3D se ve negro. La DirectionalLight3D simula el sol: ilumina todo el escenario en una dirección, sin importar dónde esté colocada.

Propiedad Valor Efecto
light_energy 1,2 intensidad (1 = normal)
shadow_enabled activado proyecta sombras
directional_shadow_max_distance 100 m hasta dónde se calculan sombras

La DirectionalLight3D solo cuenta su rotación, no su posición: moverla no cambia la luz, girarla sí.

Y una OmniLight3D (bombilla) sí depende de la posición y se atenúa con la distancia: es la que se usa para farolas y antorchas.

Direccional = sol (solo rotación). Omni = bombilla (posición y alcance). Spot = foco (cono con ángulo).

La caja azul cayendo sobre el suelo 3D con su sombra

La caja azul cayendo: 2,46 m de altura, con la sombra proyectada por la DirectionalLight3D. Captura real de fisica3d.tscn.

7. El código de la escena 3D

extends Node3D
# ============================================================
# UD6 · Escena 3D: físicas, cámara e iluminación
# RA5 · CE e (propiedades físicas), g (cámaras e iluminación)
# ============================================================

@onready var caja: RigidBody3D = $Caja
@onready var etiqueta: Label = $HUD/Altura


func _process(_delta: float) -> void:
    # muestra en pantalla la altura real de la caja cada fotograma
    etiqueta.text = "altura de la caja: %.2f m" % caja.global_position.y

La misma caja en reposo sobre el suelo: 0,69 m

Dos segundos y medio después: la caja ha caído y descansa sobre el suelo. La altura la mide el propio motor (0.69 m). Captura real.

Cuatro líneas de código y una escena: la física la pone el motor. Ese es el sentido de usar un motor de juegos.

La altura se lee de caja.global_position.y, la posición real simulada, no de una variable que llevemos a mano.

8. Añadir rebote e impulso

Para que la caja no se quede muerta en el suelo:

func _ready() -> void:
    # material físico: rebote del 60 % y rozamiento bajo
    var fisico := PhysicsMaterial.new()
    fisico.bounce = 0.6
    fisico.friction = 0.3
    caja.physics_material_override = fisico
    # impulso inicial hacia arriba: masa 1 kg → 4 m/s hacia arriba
    caja.apply_impulse(Vector3(0, 4, 0))

apply_impulse da un empujón instantáneo (newton·segundo); apply_force empuja fotograma a fotograma. Para un salto, impulso.

9. Pruebas de funcionamiento (CE i)

Prueba Cómo Resultado esperado
Caída arrancar la escena la caja baja de 3,2 m a ~0,5 m
Reposo esperar 2 s la caja queda quieta sobre el suelo
Cámara mirar la escena se ve la caja y el suelo, sin atravesarlos
Luz desactivar la luz todo negro (confirma que la luz ilumina)
Sombras mirar bajo la caja sombra proyectada en el suelo

Si al desactivar la luz no se ve negro, hay luz de entorno (Environment) y el DirectionalLight3D no está haciendo el trabajo.

10. Errores típicos

Error Síntoma Solución
Caja sin CollisionShape3D atraviesa el suelo añadir la forma
Mover un RigidBody3D con position la caja tiembla o se teletransporta usar impulsos
bounce en el material visual no rebota usar PhysicsMaterial
Cámara sin current se ve negro marcar la cámara como actual
Luz sin sombras los objetos flotan activar shadow_enabled

Caja que atraviesa el suelo = falta CollisionShape3D en la caja, en el suelo o en los dos.

Bibliografía oficial

Presentación