¿Qué es el 3D en la web?
WebGL permite que el navegador dibuje 3D acelerado por hardware utilizando la GPU. Es potente, pero su API nativa es verbosa: debes gestionar buffers, escribir shaders y encargarte de las matemáticas por tu cuenta. Three.js envuelve WebGL en un grafo de escena amigable para que puedas describir objetos, luces y cámaras, dejando que la librería se encargue del resto.
El resultado es que el 3D en la web es ahora práctico para configuradores de productos, visualización de datos, storytelling interactivo, juegos y experiencias inmersivas de WebXR. No es la opción adecuada para todos los sitios, pero cuando el movimiento y el espacio cuentan la historia mejor que una página plana, Three.js es la herramienta estándar.
Escena, cámara y renderizador
Cada aplicación de Three.js tiene tres objetos fundamentales.
// setup.js
import * as THREE from "three";
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 100);
camera.position.set(0, 0, 3);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
La scene es el contenedor. La camera define la vista; PerspectiveCamera aporta una profundidad realista, mientras que OrthographicCamera es útil para vistas planas o isométricas. El renderer dibuja la escena en un canvas. Limitar el pixel ratio evita que las pantallas de alta densidad rendericen más píxeles de los necesarios.
Meshes, geometría y materiales
Un objeto visible es un mesh: una geometría para su forma y un material para su superficie.
// mesh.js
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ color: 0x22c55e, roughness: 0.4 }),
);
cube.position.set(0, 0, 0);
scene.add(cube);
Three.js incluye muchas geometrías integradas (box, sphere, plane, torus y más) y materiales para lograr diferentes apariencias. MeshStandardMaterial responde a las luces y es una buena opción por defecto; MeshBasicMaterial ignora la iluminación y es más ligero. Los meshes pueden agruparse y anidarse, formando el grafo de la escena a través del cual se heredan las transformaciones.
Luces y sombras
Sin luz, la mayoría de los materiales se renderizan en negro.
// lights.js
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
const key = new THREE.DirectionalLight(0xffffff, 2.5);
key.position.set(2, 3, 2);
scene.add(ambient, key);
Las luces son objetos en la escena, por lo que su posición y tipo son fundamentales. Las sombras requieren activar renderer.shadowMap y marcar la luz, el objeto y la superficie; además, son relativamente costosas, así que úsalas con moderación. Cada luz añade carga al rendimiento, por lo que es recomendable mantener el número de luces al mínimo.
Modelos y texturas
En los proyectos reales, lo habitual es cargar assets en lugar de construir la geometría a mano. glTF es el formato estándar de 3D para la web.
// load.js
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
const loader = new GLTFLoader();
loader.load("/models/robot.glb", (gltf) => {
scene.add(gltf.scene);
});
Las texturas se cargan de forma similar con TextureLoader, y RGBELoader o PMREMGenerator pueden proporcionar mapas de entorno para lograr reflejos realistas. Mantén el tamaño de los modelos y las texturas en niveles razonables, ya que son los elementos que más afectan al tiempo de carga y al uso de memoria.
El bucle de animación
La animación actualiza la escena y la vuelve a renderizar en cada frame.
// loop.js
const clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
const delta = clock.getDelta();
cube.rotation.y += delta;
renderer.render(scene, camera);
});
El uso de delta mantiene la velocidad del movimiento constante, independientemente de la tasa de refresco (frame rate). Cuando la escena es estática, renderiza bajo demanda en lugar de hacerlo continuamente: renderiza después de un cambio, una interacción con los controles o una animación, y detente cuando no esté ocurriendo nada. Esto ahorra batería y recursos de la GPU en dispositivos móviles.
Interacción
Para hacer que los objetos sean clickeables o reaccionen al pasar el cursor, utiliza un Raycaster.
// pick.js
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
addEventListener("pointerdown", (event) => {
pointer.x = (event.clientX / innerWidth) * 2 - 1;
pointer.y = -(event.clientY / innerHeight) * 2 + 1;
raycaster.setFromCamera(pointer, camera);
const hit = raycaster.intersectObjects(scene.children)[0];
if (hit) hit.object.material.color.set(0xf97316);
});
El raycasting desde la cámara a través del puntero devuelve los objetos que se encuentran debajo, lo cual es suficiente para la selección, el resaltado e interacciones sencillas.
Rendimiento y limpieza
El 3D es lo más costoso que la mayoría de los sitios llegarán a renderizar, por lo que la disciplina es fundamental.
- Libera (Dispose) geometrías, materiales y texturas cuando elimines objetos; estos residen en la memoria de la GPU.
- Reutiliza geometrías y materiales entre instancias en lugar de crear nuevos.
- Fusiona (Merge) geometrías estáticas y utiliza instancing para objetos repetidos.
- Limita las luces y los objetos que proyectan sombras.
- Reduce el pixel ratio en pantallas de alta densidad.
- Renderiza bajo demanda para escenas estáticas.
- Pausa el renderizado cuando el canvas esté fuera de la pantalla.
// dispose.js
mesh.geometry.dispose();
mesh.material.dispose();
scene.remove(mesh);
Olvidar liberar la memoria es la causa más común de que una aplicación de Three.js consuma lentamente toda la memoria disponible.
React Three Fiber
Con React, React Three Fiber renderiza Three.js de forma declarativa, por lo que la escena se describe mediante componentes y se integra con el estado y el ciclo de vida de React.
// Scene.jsx
import { Canvas } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei";
export function Scene() {
return (
<Canvas>
<ambientLight intensity={0.4} />
<directionalLight position={[2, 3, 2]} intensity={2.5} />
<mesh>
<boxGeometry />
<meshStandardMaterial color="#22c55e" />
</mesh>
<OrbitControls />
</Canvas>
);
}
Los helpers de drei proporcionan controles, entornos y abstracciones comunes. Es la forma recomendada de utilizar Three.js en React.
Mejores prácticas
- Limita el pixel ratio y ajusta el tamaño del renderer según la ventana.
- Reutiliza geometrías y materiales; utiliza instanciado para objetos repetidos.
- Libera los recursos de la GPU al eliminar objetos.
- Mantén al mínimo el número de luces y emisores de sombras.
- Renderiza bajo demanda en escenas estáticas.
- Realiza pruebas en dispositivos móviles reales de gama media.
- Proporciona una alternativa (fallback) cuando WebGL no esté disponible.
Errores comunes
- Olvidar hacer el dispose y provocar fugas de memoria de la GPU.
- Renderizar continuamente cuando nada ha cambiado.
- Crear nuevas geometrías o materiales en cada frame.
- Añadir demasiadas luces y sombras sin realizar mediciones.
- Cargar modelos y texturas enormes y sin optimizar.
- Probar únicamente en un ordenador rápido y desplegar en dispositivos móviles.
Próximos pasos
Three.js abre una dimensión completamente nueva para la web. Añade movimiento coreografiado con la guía de Animación Avanzada, construye con React Three Fiber si utilizas React, y mantén el rendimiento optimizado con la guía de Rendimiento Web. Después, crea la escena más sencilla posible —un cubo iluminado alrededor del cual puedas orbitar— y ve expandiéndola concepto a concepto.