Skip to content

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 (MeshStandardMaterial o MeshPhysicalMaterial).
  • 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: RectAreaLight no 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:

  1. Escena y Cámara:
  2. Se crea una escena y una cámara en perspectiva posicionada en (0, 5, 10), mirando hacia el centro de la escena.

  3. Renderizador:

  4. 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.

  5. Luces:

  6. AmbientLight: Proporciona una iluminación base suave en toda la escena.
  7. DirectionalLight: Simula una fuente de luz distante como el sol, proyectando sombras. Se configuran sus propiedades de sombra para mejorar la calidad.
  8. PointLight: Añade una fuente de luz localizada en una posición específica, con color rojo y capaz de proyectar sombras.

  9. Objetos:

  10. Cubo: Un objeto con MeshStandardMaterial que responde a la iluminación. Está configurado para proyectar y recibir sombras.
  11. Suelo: Un plano que actúa como suelo, recibiendo sombras proyectadas por el cubo y las luces.

  12. Animación:

  13. Se define una función animate que rota el cubo en cada frame y renderiza la escena.

  14. Redimensionamiento:

  15. 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:

  • DirectionalLight
  • SpotLight
  • PointLight (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. Ajustar near, far, left, right, top, bottom para 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 AmbientLight y 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:

  1. SpotLight:
  2. Se crea una SpotLight blanca con intensidad 1.
  3. Posicionada en (0, 10, 10) y apuntando hacia el centro de la escena.
  4. Configurada con un ángulo de cono de Math.PI / 6 (30 grados) y una penumbra de 0.1 para transiciones suaves.
  5. Habilitada para proyectar sombras y configurada para mejorar la calidad de las sombras.

  6. Objeto Iluminado:

  7. Se crea un TorusKnotGeometry con un material MeshStandardMaterial que responde a la iluminación.
  8. El objeto está configurado para proyectar y recibir sombras.

  9. Suelo con Textura:

  10. Se crea un plano grande que actúa como suelo.
  11. Se carga una textura (floor_texture.jpg) y se aplica al material del suelo.
  12. La textura se repite varias veces para cubrir toda la superficie del suelo.
  13. El suelo está configurado para recibir sombras.

  14. Animación:

  15. Se rota el TorusKnot en 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();