DirectionalLight: la fuente que está infinitamente lejos
Por qué una luz direccional no tiene posición sino dirección, qué implica eso para su atenuación y su sombra, cómo funciona el target, y cuándo la abstracción del sol deja de valer.
Rayos paralelos, o el límite de una fuente lejana
Una fuente puntual emite rayos que divergen desde un punto. A medida que la fuente se aleja, el cono de rayos que llega a un objeto de tamaño fijo se estrecha, hasta que en el límite todos los rayos son paralelos. Eso es una DirectionalLight: el límite de una fuente puntual infinitamente lejana e infinitamente potente. El sol, a 150 millones de kilómetros, es una aproximación excelente de ese límite para cualquier escena que quepa en un planeta.
De ahí salen las tres propiedades del modelo, y las tres son consecuencia de la misma idealización:
La dirección es constante en toda la escena. El shader recibe un único vec3 por luz direccional. No hay que calcularlo por fragmento, a diferencia de una luz puntual, donde cada fragmento resta su propia posición.
No hay atenuación. La ley del inverso del cuadrado dice que la irradiancia cae con la distancia al cuadrado, pero eso es porque el área que cubre un cono crece con el cuadrado. Rayos paralelos no divergen, así que el área no crece y la irradiancia no cae. Un objeto a diez unidades y otro a diez mil reciben la misma cantidad de luz. Por eso DirectionalLight no tiene decay ni distance.
La unidad es el lux. Como no hay caída, la magnitud natural no es la intensidad luminosa de la fuente sino la iluminancia que llega a una superficie perpendicular a los rayos. Un día soleado a mediodía ronda los 100 000 lux; un día muy nublado, 1 000. Three.js no trabaja con esos números absolutos porque el mapeo tonal se llevaría por delante todo el rango, pero la proporción sí es la buena: un cielo nublado es dos órdenes de magnitud menos que un sol directo.
const sol = new THREE.DirectionalLight( 0xfff4e5, 3.0 );sol.position.set( 4, 8, 3 ); // solo define la DIRECCIONscene.add( sol );// Estos dos casos producen exactamente el mismo sombreado:sol.position.set( 4, 8, 3 );sol.position.set( 400, 800, 300 );El target y la trampa clásica
La dirección efectiva es target.position - light.position, tomada en espacio de mundo. DirectionalLight crea su propio target en el constructor: un Object3D vacío en el origen. Y ahí está la trampa.
Un Object3D que no está en el grafo de escena no recibe actualizaciones de matrixWorld. Si mueves light.target.position sin haber añadido el target a la escena, su matrixWorld sigue siendo la identidad y la luz sigue apuntando al origen. El síntoma es exasperante porque el código parece correcto: cambias el valor, no pasa nada, y no hay ningún error en consola.
const sol = new THREE.DirectionalLight( 0xffffff, 3 );sol.position.set( 5, 10, 5 );// SIN esta linea, mover sol.target.position no hace absolutamente nada.scene.add( sol.target );scene.add( sol );sol.target.position.set( -8, 0, 4 ); // ahora siLa alternativa limpia, y la que se usa cuando la luz tiene que seguir a un personaje, es reemplazar el target por un objeto que ya esté en la escena:
// El sol apunta siempre al jugador, se mueva donde se mueva.sol.target = jugador;Aquí no hace falta scene.add( sol.target ) porque jugador ya está en el grafo y su matrixWorld se actualiza sola. Este patrón, combinado con mover sol.position para que mantenga un desplazamiento constante respecto al jugador, es la base de las sombras direccionales en mundos grandes: la cámara de sombra viaja con el jugador en vez de tener que cubrir el mundo entero.
Dónde deja de valer la abstracción
La idealización de rayos paralelos falla en dos escenarios concretos, y conviene reconocerlos antes de pelearse con los parámetros.
El primero es la sombra. Aunque el sombreado no dependa de la distancia, la sombra sí depende de la posición, porque el shadow map se renderiza desde una cámara ortográfica colocada en light.position y orientada hacia target. El frustum de esa cámara —shadow.camera.left, right, top, bottom, near, far— tiene por defecto un volumen de 10 por 10 por 499.5 unidades, y todo lo que quede fuera simplemente no proyecta sombra. Ahí es donde la posición vuelve a importar y donde hay que trabajar de verdad; es el tema del nivel de sombras.
El segundo es la penumbra. Una fuente direccional ideal produce sombras con el borde perfectamente afilado, porque no tiene tamaño angular. El sol real tiene medio grado de diámetro aparente, lo que produce una penumbra que crece con la distancia al oclusor: la sombra de un poste es nítida en su base y difusa a diez metros. Three.js no modela eso con DirectionalLight. Se aproxima con el filtrado del shadow map, que produce un desenfoque de anchura constante, no proporcional a la distancia. Si esa diferencia importa para tu escena, la respuesta no es un parámetro de la luz: es un filtro de sombras con radio variable o un lightmap horneado.