Parte: 12 — Juegos web y HTML5 · Fuente: Documentación oficial de Three.js (threejs.org) ⏱️ Duración estimada: 75 min · Nivel: Intermedio
Three.js es la biblioteca más usada para 3D en el navegador: envuelve WebGL con una API cómoda de escenas, cámaras, mallas y luces. En esta clase montarás las tres piezas imprescindibles de cualquier aplicación 3D web: una Scene (el mundo), una PerspectiveCamera (el punto de vista) y un WebGLRenderer (que dibuja en el canvas). Sobre ellas colocarás una Mesh —geometría más material— iluminada por una luz, y la animarás en un bucle renderer.render(scene, camera).
Al terminar tendrás un cubo iluminado que rota, con una cámara colocada en el espacio, y sabrás cómo se extiende esto para cargar modelos glTF y añadir controles de órbita. Este es el cimiento de cualquier juego o experiencia 3D en la web.
Al finalizar, el alumno podrá:
Scene, PerspectiveCamera y WebGLRenderer y conectarlos.Mesh a partir de geometría y material, y añadirla a la escena.requestAnimationFrame y renderer.render.| # | Tema | Por qué importa |
|---|---|---|
| 1 | Scene | Contenedor de todo lo visible en 3D. |
| 2 | PerspectiveCamera | Define el punto de vista y la perspectiva. |
| 3 | WebGLRenderer | Dibuja la escena en el canvas vía GPU. |
| 4 | Geometry + Material = Mesh | La unidad visible del mundo 3D. |
| 5 | Luces | Sin luz, los materiales estándar salen negros. |
| 6 | Bucle de render | Redibuja cada cuadro para animar. |
| 7 | Modelos glTF | Formato estándar para importar assets 3D. |
| 8 | OrbitControls | Permite girar la cámara con el ratón. |
domElement es el lienzo que insertas en la página.MeshStandardMaterial necesita luz para verse.GLTFLoader, ideal para assets externos.Usaremos Three.js vía CDN con un import map para poder importar el módulo por nombre (https://unpkg.com/three). Sirve por HTTP con python -m http.server, ya que los módulos ES no funcionan bajo file://. La documentación y el manual "Creating a scene" están en https://threejs.org/docs/.
Crea three-cubo/ con un único index.html que contenga el HTML y el módulo. No hace falta empaquetador: aprovecharemos los módulos nativos del navegador.
Montarás un cubo iluminado que rota con una cámara colocada frente a él.
index.html con un import map y un <script type="module">:<!DOCTYPE html>
<html lang="es">
<head><meta charset="UTF-8"><title>Cubo 3D</title>
<style>body{margin:0;overflow:hidden}</style>
<script type="importmap">
{ "imports": { "three": "https://unpkg.com/three@0.160.0/build/three.module.js" } }
</script>
</head>
<body>
<script type="module" src="juego.js"></script>
</body>
</html>
juego.js crea la escena, la cámara y el renderer, e inserta el canvas:import * as THREE from 'three';
const escena = new THREE.Scene();
escena.background = new THREE.Color(0x101828);
// Cámara: FOV 60°, aspecto de la ventana, planos de recorte near/far.
const camara = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 100);
camara.position.set(0, 1.5, 4); // Un poco arriba y hacia atrás.
camara.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
const geometria = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x4ade80 });
const cubo = new THREE.Mesh(geometria, material);
escena.add(cubo);
escena.add(new THREE.AmbientLight(0xffffff, 0.4));
const sol = new THREE.DirectionalLight(0xffffff, 1.2);
sol.position.set(3, 5, 2);
escena.add(sol);
requestAnimationFrame, rotando el cubo cada cuadro:function animar() {
requestAnimationFrame(animar);
cubo.rotation.x += 0.01;
cubo.rotation.y += 0.015;
renderer.render(escena, camara); // Redibuja la escena desde la cámara.
}
animar();
addEventListener('resize', () => {
camara.aspect = innerWidth / innerHeight;
camara.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
python -m http.server 8000) y abre http://localhost:8000/. Verás un cubo verde iluminado girando sobre fondo oscuro, que se adapta al tamaño de la ventana.Ya tienes la base 3D. Para cargar un modelo real, importarías GLTFLoader desde three/addons/loaders/GLTFLoader.js y usarías loader.load('modelo.gltf', (gltf) => escena.add(gltf.scene)).
SphereGeometry) y observa cómo la luz la modela.MeshBasicMaterial y explica por qué deja de necesitar luz.OrbitControls (import desde three/addons/controls/OrbitControls.js) para girar la cámara con el ratón.far si el objeto desaparece.Crea una escena con al menos tres mallas distintas (por ejemplo cubo, esfera y cilindro) de colores diferentes, iluminadas por una luz ambiental y una direccional, todas rotando a velocidades distintas, con la cámara controlable por el ratón mediante OrbitControls.
Criterio de aceptación: al abrir la página se ven las tres mallas iluminadas rotando, el ratón orbita la cámara alrededor de la escena, y la ventana se redimensiona sin deformar la imagen; sin errores en consola.
| Síntoma / mensaje | Causa y cómo arreglar |
|---|---|
| Pantalla negra total | Falta luz o la cámara mira a otro lado. Añade luces y usa camara.lookAt(0,0,0). |
| "Failed to resolve module specifier 'three'" | Falta el import map o sirves por file://. Añade el import map y sirve por HTTP. |
| El cubo no rota | No llamas a renderer.render dentro del bucle. Verifica que animar se reinvoque con rAF. |
| La imagen se ve estirada | No actualizas aspect al redimensionar. Recalcula y llama a updateProjectionMatrix. |
| El objeto sale recortado o invisible | Está fuera del rango near/far o detrás de la cámara. Ajusta posición y planos de recorte. |
❓ ¿Three.js es un motor de juego? No exactamente: es una biblioteca de render 3D. Aporta el dibujo y la escena; la lógica de juego, física y audio los añades tú o con librerías complementarias.
❓ ¿Por qué mi material sale negro? Los materiales tipo Standard o Phong necesitan luz. Añade al menos una luz o usa MeshBasicMaterial, que no la requiere.
❓ ¿Qué formato de modelo uso? glTF/GLB es el recomendado por su eficiencia y soporte oficial mediante GLTFLoader.
❓ ¿Necesito empaquetador (bundler)? No para aprender: con un import map y módulos ES nativos basta. Para producción, un bundler ayuda a optimizar y versionar dependencias.
Clase 218 - PixiJS y renderizado 2D acelerado