4. Luces en Three.js
4.1 Introducción a las Luces
La iluminación es un componente crucial en cualquier entorno 3D, ya que determina cómo se ven los objetos, sus colores, sombras y texturas. En Three.js, las luces simulan diferentes fuentes de luz presentes en el mundo real, permitiendo crear ambientes realistas o estilizados según tus necesidades.
4.2 Tipos de Luces en Three.js
4.2.1 THREE.AmbientLight
- Descripción: Proporciona una iluminación uniforme en toda la escena sin dirección específica. No crea sombras y afecta a todos los objetos de manera igualitaria.
- Uso común: Simular luz ambiental general, como la luz difusa que no proviene de una fuente específica.
Sintaxis:
const ambientLight = new THREE.AmbientLight(color, intensidad);
Ejemplo:
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // Luz blanca con 50% de intensidad
scene.add(ambientLight);
4.2.2 THREE.DirectionalLight
- Descripción: Simula una fuente de luz distante que emite luz en una dirección específica, similar al sol. Puede proyectar sombras.
- Uso común: Iluminación principal de la escena, proporcionando sombras y profundidad.
Sintaxis:
const directionalLight = new THREE.DirectionalLight(color, intensidad);
Ejemplo:
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 7.5); // Posición de la luz
scene.add(directionalLight);
4.2.3 THREE.PointLight
- Descripción: Emite luz en todas las direcciones desde un punto específico, similar a una bombilla. Puede proyectar sombras.
- Uso común: Simular fuentes de luz localizadas, como lámparas o luces ambientales.
Sintaxis:
const pointLight = new THREE.PointLight(color, intensidad, distancia, atenuación);
Ejemplo:
const pointLight = new THREE.PointLight(0xffffff, 1, 100);
pointLight.position.set(0, 10, 0);
scene.add(pointLight);
4.2.4 THREE.SpotLight
- Descripción: Emite luz en un cono desde una posición específica, con ángulo y penumbra ajustables. Puede proyectar sombras.
- Uso común: Simular focos de luz, como lámparas de escenario o linternas.
Sintaxis:
const spotLight = new THREE.SpotLight(color, intensidad, distancia, ángulo, penumbra, decaimiento);
Ejemplo:
const spotLight = new THREE.SpotLight(0xffffff, 1);
spotLight.position.set(10, 20, 10);
spotLight.angle = Math.PI / 6; // Ángulo del cono de luz
spotLight.penumbra = 0.1; // Transición suave en los bordes
scene.add(spotLight);
4.2.5 THREE.HemisphereLight
- Descripción: Simula la luz ambiental proveniente del cielo y del suelo, proporcionando una iluminación suave y difusa sin dirección específica.
- Uso común: Crear efectos de iluminación natural, como la luz del día.
Sintaxis:
const hemisphereLight = new THREE.HemisphereLight(skyColor, groundColor, intensidad);
Ejemplo:
const hemisphereLight = new THREE.HemisphereLight(0xaaaaaa, 0x444444, 1);
hemisphereLight.position.set(0, 20, 0);
scene.add(hemisphereLight);
4.2.6 THREE.RectAreaLight
- Descripción: Emite luz desde una superficie rectangular, proporcionando una iluminación suave y realista. Requiere el uso de materiales específicos (
MeshStandardMaterialoMeshPhysicalMaterial). - Uso común: Simular luces de ventana o paneles de luz.
Sintaxis:
const rectAreaLight = new THREE.RectAreaLight(color, intensidad, ancho, alto);
Ejemplo:
const rectAreaLight = new THREE.RectAreaLight(0xffffff, 1, 4, 2);
rectAreaLight.position.set(0, 10, 0);
rectAreaLight.lookAt(0, 0, 0); // Orientar la luz hacia el centro de la escena
scene.add(rectAreaLight);
Nota:
RectAreaLightno soporta sombras en Three.js en la versión actual. Para utilizarlo correctamente, asegúrate de que los materiales de los objetos sean compatibles.
4.3 Añadir Luces a la Escena
Agregar luces a una escena en Three.js es sencillo. A continuación, se muestra cómo integrar diferentes tipos de luces en una escena básica.
Ejemplo Completo: Integración de Varias Luces
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Luces en Three.js</title>
<style>
body { margin: 0; }
canvas { display: block; }
</style>
</head>
<body>
<!-- Enlace a Three.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r152/three.min.js"></script>
<script>
// 1. Crear la escena
const scene = new THREE.Scene();
// 2. Crear la cámara
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 5, 10);
camera.lookAt(0, 0, 0);
// 3. Crear el renderizador
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true; // Habilitar sombras
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // Tipo de sombras
document.body.appendChild(renderer.domElement);
// 4. Añadir AmbientLight
const ambientLight = new THREE.AmbientLight(0x404040, 1); // Luz ambiental suave
scene.add(ambientLight);
// 5. Añadir DirectionalLight
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 7.5);
directionalLight.castShadow = true; // La luz proyecta sombras
// Configuración de sombras
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
scene.add(directionalLight);
// 6. Añadir PointLight
const pointLight = new THREE.PointLight(0xff0000, 1, 100);
pointLight.position.set(-5, 5, -5);
pointLight.castShadow = true;
scene.add(pointLight);
// 7. Crear un objeto para iluminar
const geometry = new THREE.BoxGeometry(2, 2, 2);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
cube.castShadow = true; // El cubo proyecta sombras
cube.receiveShadow = true; // El cubo recibe sombras
scene.add(cube);
// 8. Crear el suelo
const sueloGeometry = new THREE.PlaneGeometry(20, 20);
const sueloMaterial = new THREE.MeshStandardMaterial({ color: 0x808080 });
const suelo = new THREE.Mesh(sueloGeometry, sueloMaterial);
suelo.rotation.x = -Math.PI / 2; // Rotar para que quede horizontal
suelo.position.y = -1;
suelo.receiveShadow = true; // El suelo recibe sombras
scene.add(suelo);
// 9. Función de animación
function animate() {
requestAnimationFrame(animate);
// Rotar el cubo
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
// Renderizar la escena
renderer.render(scene, camera);
}
animate();
// 10. Manejar el redimensionamiento de la ventana
window.addEventListener('resize', () => {
const width = window.innerWidth;
const height = window.innerHeight;
renderer.setSize(width, height);
camera.aspect = width / height;
camera.updateProjectionMatrix();
});
</script>
</body>
</html>
Explicación del Código:
- Escena y Cámara:
-
Se crea una escena y una cámara en perspectiva posicionada en
(0, 5, 10), mirando hacia el centro de la escena. -
Renderizador:
-
Se configura el renderizador para ocupar toda la ventana, se habilitan las sombras y se establece el tipo de sombra (
PCFSoftShadowMap) para obtener sombras más suaves. -
Luces:
- AmbientLight: Proporciona una iluminación base suave en toda la escena.
- DirectionalLight: Simula una fuente de luz distante como el sol, proyectando sombras. Se configuran sus propiedades de sombra para mejorar la calidad.
-
PointLight: Añade una fuente de luz localizada en una posición específica, con color rojo y capaz de proyectar sombras.
-
Objetos:
- Cubo: Un objeto con
MeshStandardMaterialque responde a la iluminación. Está configurado para proyectar y recibir sombras. -
Suelo: Un plano que actúa como suelo, recibiendo sombras proyectadas por el cubo y las luces.
-
Animación:
-
Se define una función
animateque rota el cubo en cada frame y renderiza la escena. -
Redimensionamiento:
- Se añade un evento que actualiza el tamaño del renderizador y la cámara cuando la ventana cambia de tamaño.
4.4 Configuración de Sombras
Las sombras añaden realismo a las escenas 3D al simular la obstrucción de la luz por objetos. Para gestionar las sombras en Three.js, se deben seguir varios pasos:
4.4.1 Habilitar Sombras en el Renderizador
Antes de poder utilizar sombras, es necesario habilitarlas en el renderizador.
Ejemplo:
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.shadowMap.enabled = true; // Habilitar sombras
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // Tipo de sombra (opcional)
4.4.2 Configurar Luces para Proyectar Sombras
No todas las luces pueden proyectar sombras. Las luces que pueden hacerlo son:
DirectionalLightSpotLightPointLight(en versiones más recientes de Three.js)
Para habilitar sombras en una luz, se debe establecer la propiedad castShadow en true y ajustar las propiedades de la sombra para mejorar la calidad.
Ejemplo:
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 7.5);
directionalLight.castShadow = true; // La luz proyecta sombras
// Configurar la sombra
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
scene.add(directionalLight);
4.4.3 Configurar Objetos para Proyectar y Recibir Sombras
Para que los objetos interactúen con las sombras, deben configurarse adecuadamente.
- Proyectar sombras (
castShadow): Determina si el objeto puede proyectar sombras sobre otros objetos. - Recibir sombras (
receiveShadow): Determina si el objeto puede recibir sombras de otros objetos.
Ejemplo:
// Objeto que proyecta y recibe sombras
const cube = new THREE.Mesh(geometry, material);
cube.castShadow = true; // El cubo proyecta sombras
cube.receiveShadow = true; // El cubo puede recibir sombras
scene.add(cube);
// Objeto que solo recibe sombras
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.receiveShadow = true; // El suelo puede recibir sombras
scene.add(floor);
4.4.4 Ajustar la Calidad de las Sombras
Para mejorar la calidad de las sombras, puedes ajustar varias propiedades de la luz y de la sombra.
Propiedades Clave:
shadow.mapSize: Define la resolución del mapa de sombras. Valores más altos mejoran la calidad pero consumen más recursos.shadow.camera: Define el área de visión de la cámara de sombras. Ajustarnear,far,left,right,top,bottompara optimizar.shadow.bias: Ayuda a prevenir artefactos como el "shadow acne" ajustando un sesgo en las sombras.
Ejemplo:
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.left = -10;
directionalLight.shadow.camera.right = 10;
directionalLight.shadow.camera.top = 10;
directionalLight.shadow.camera.bottom = -10;
directionalLight.shadow.bias = -0.001;
4.5 Efectos de las Luces en Materiales
Los materiales en Three.js interactúan de manera diferente con las luces según su tipo y propiedades. A continuación, se explica cómo las luces afectan a algunos materiales comunes:
4.5.1 MeshBasicMaterial
- Descripción: Material básico que no responde a la iluminación. Siempre muestra el color o textura tal como está definido.
- Interacción con luces: Ninguna. Las luces no afectan este material.
Uso:
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
4.5.2 MeshLambertMaterial
- Descripción: Material que responde a la iluminación difusa, simulando superficies mate.
- Interacción con luces: Reacciona a
AmbientLighty otras luces que emiten luz difusa.
Uso:
const material = new THREE.MeshLambertMaterial({ color: 0x00ff00 });
4.5.3 MeshPhongMaterial
- Descripción: Material que responde a la iluminación difusa y especular, simulando superficies brillantes.
- Interacción con luces: Reacciona a todas las fuentes de luz y permite la configuración de reflejos especulares.
Uso:
const material = new THREE.MeshPhongMaterial({ color: 0x0000ff, shininess: 100 });
4.5.4 MeshStandardMaterial
- Descripción: Material basado en PBR (Physically Based Rendering) que proporciona un realismo superior al simular propiedades físicas de los materiales.
- Interacción con luces: Reacciona a todas las fuentes de luz, permitiendo configuraciones detalladas como metalicidad y rugosidad.
Uso:
const material = new THREE.MeshStandardMaterial({
color: 0xffffff,
metalness: 0.5,
roughness: 0.5
});
4.6 Ejemplos Prácticos
A continuación, se presentan dos ejemplos que ilustran el uso de diferentes tipos de luces y cómo afectan a los objetos en la escena.
4.6.1 Ejemplo 1: Escena con AmbientLight y DirectionalLight
Este ejemplo muestra cómo combinar una luz ambiental con una luz direccional para iluminar una escena básica con un cubo y un suelo.
Estructura de Carpetas:
mi-escena-luces-threejs/
├── index.html
└── textures/
└── brick_diffuse.jpg
Contenido de index.html:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Luces Básicas en Three.js</title>
<style>
body { margin: 0; }
canvas { display: block; }
</style>
</head>
<body>
<!-- Enlace a Three.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r152/three.min.js"></script>
<script>
// 1. Crear la escena
const scene = new THREE.Scene();
// 2. Crear la cámara
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 5, 10);
camera.lookAt(0, 0, 0);
// 3. Crear el renderizador
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true; // Habilitar sombras
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
// 4. Añadir AmbientLight
const ambientLight = new THREE.AmbientLight(0x404040, 1); // Luz ambiental suave
scene.add(ambientLight);
// 5. Añadir DirectionalLight
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 7.5);
directionalLight.castShadow = true; // La luz proyecta sombras
// Configuración de la sombra
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
directionalLight.shadow.camera.left = -10;
directionalLight.shadow.camera.right = 10;
directionalLight.shadow.camera.top = 10;
directionalLight.shadow.camera.bottom = -10;
scene.add(directionalLight);
// 6. Crear el cubo
const geometry = new THREE.BoxGeometry(2, 2, 2);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
cube.castShadow = true; // El cubo proyecta sombras
cube.receiveShadow = true; // El cubo puede recibir sombras
scene.add(cube);
// 7. Crear el suelo
const floorGeometry = new THREE.PlaneGeometry(20, 20);
const floorMaterial = new THREE.MeshStandardMaterial({ color: 0x808080 });
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2; // Rotar para que quede horizontal
floor.position.y = -1;
floor.receiveShadow = true; // El suelo recibe sombras
scene.add(floor);
// 8. Función de animación
function animate() {
requestAnimationFrame(animate);
// Rotar el cubo
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
// Renderizar la escena
renderer.render(scene, camera);
}
animate();
// 9. Manejar el redimensionamiento de la ventana
window.addEventListener('resize', () => {
const width = window.innerWidth;
const height = window.innerHeight;
renderer.setSize(width, height);
camera.aspect = width / height;
camera.updateProjectionMatrix();
});
</script>
</body>
</html>
Resultado Esperado:
Una escena con un cubo verde giratorio iluminado por una luz ambiental suave y una luz direccional que proyecta sombras sobre un suelo gris. La combinación de luces crea una apariencia equilibrada con profundidad gracias a las sombras.
4.6.2 Ejemplo 2: Uso de SpotLight con Sombras
Este ejemplo demuestra cómo utilizar una SpotLight para iluminar un objeto específico y crear sombras dinámicas.
Estructura de Carpetas:
mi-escena-spotlight-threejs/
├── index.html
└── textures/
└── floor_texture.jpg
Contenido de index.html:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>SpotLight en Three.js</title>
<style>
body { margin: 0; }
canvas { display: block; }
</style>
</head>
<body>
<!-- Enlace a Three.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r152/three.min.js"></script>
<script>
// 1. Crear la escena
const scene = new THREE.Scene();
// 2. Crear la cámara
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 5, 15);
camera.lookAt(0, 0, 0);
// 3. Crear el renderizador
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true; // Habilitar sombras
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
// 4. Añadir SpotLight
const spotLight = new THREE.SpotLight(0xffffff, 1);
spotLight.position.set(0, 10, 10);
spotLight.angle = Math.PI / 6;
spotLight.penumbra = 0.1;
spotLight.decay = 2;
spotLight.distance = 50;
spotLight.castShadow = true; // La luz proyecta sombras
// Configuración de la sombra
spotLight.shadow.mapSize.width = 1024;
spotLight.shadow.mapSize.height = 1024;
spotLight.shadow.camera.near = 0.5;
spotLight.shadow.camera.far = 50;
scene.add(spotLight);
// 5. Crear el objeto a iluminar
const geometry = new THREE.TorusKnotGeometry(1, 0.4, 100, 16);
const material = new THREE.MeshStandardMaterial({ color: 0x156289, emissive: 0x072534, metalness: 0.5, roughness: 0.1 });
const torusKnot = new THREE.Mesh(geometry, material);
torusKnot.castShadow = true; // El objeto proyecta sombras
torusKnot.receiveShadow = true; // El objeto puede recibir sombras
scene.add(torusKnot);
// 6. Crear el suelo con textura
const floorGeometry = new THREE.PlaneGeometry(50, 50);
const textureLoader = new THREE.TextureLoader();
const floorTexture = textureLoader.load('textures/floor_texture.jpg');
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
floorTexture.repeat.set(10, 10);
const floorMaterial = new THREE.MeshStandardMaterial({ map: floorTexture });
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2; // Rotar para que quede horizontal
floor.position.y = -3;
floor.receiveShadow = true; // El suelo recibe sombras
scene.add(floor);
// 7. Función de animación
function animate() {
requestAnimationFrame(animate);
// Rotar el objeto
torusKnot.rotation.x += 0.01;
torusKnot.rotation.y += 0.01;
// Renderizar la escena
renderer.render(scene, camera);
}
animate();
// 8. Manejar el redimensionamiento de la ventana
window.addEventListener('resize', () => {
const width = window.innerWidth;
const height = window.innerHeight;
renderer.setSize(width, height);
camera.aspect = width / height;
camera.updateProjectionMatrix();
});
</script>
</body>
</html>
Explicación del Código:
- SpotLight:
- Se crea una
SpotLightblanca con intensidad 1. - Posicionada en
(0, 10, 10)y apuntando hacia el centro de la escena. - Configurada con un ángulo de cono de
Math.PI / 6(30 grados) y una penumbra de 0.1 para transiciones suaves. -
Habilitada para proyectar sombras y configurada para mejorar la calidad de las sombras.
-
Objeto Iluminado:
- Se crea un
TorusKnotGeometrycon un materialMeshStandardMaterialque responde a la iluminación. -
El objeto está configurado para proyectar y recibir sombras.
-
Suelo con Textura:
- Se crea un plano grande que actúa como suelo.
- Se carga una textura (
floor_texture.jpg) y se aplica al material del suelo. - La textura se repite varias veces para cubrir toda la superficie del suelo.
-
El suelo está configurado para recibir sombras.
-
Animación:
- Se rota el
TorusKnoten cada frame para mostrar la interacción de las sombras dinámicas con la luz.
Resultado Esperado:
Una escena con un TorusKnot giratorio iluminado por una SpotLight, proyectando sombras dinámicas sobre un suelo texturizado. La luz focalizada del SpotLight crea áreas iluminadas y sombras realistas, mejorando el realismo de la escena.
4.7 Mejores Prácticas
Uso de Helpers para Depuración:
- Utiliza THREE.DirectionalLightHelper, THREE.SpotLightHelper y otros helpers para visualizar y ajustar las posiciones y direcciones de las luces durante el desarrollo.
Ejemplo de Uso de Helpers:
// Añadir un helper para la DirectionalLight
const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
scene.add(directionalLightHelper);
// Añadir un helper para la SpotLight
const spotLightHelper = new THREE.SpotLightHelper(spotLight);
scene.add(spotLightHelper);
// Actualizar los helpers en la animación si la luz se mueve
function animate() {
requestAnimationFrame(animate);
// Rotar el objeto
torusKnot.rotation.x += 0.01;
torusKnot.rotation.y += 0.01;
// Actualizar helpers
directionalLightHelper.update();
spotLightHelper.update();
// Renderizar la escena
renderer.render(scene, camera);
}
animate();