一个场景的光:三种灯与只算一次的阴影
半球光给环境色,主光给方向与阴影,轮廓光把岛从背景里拉出来;其余六个灯笼只是点光。阴影贴图只算一次,因为它照的东西一动不动。
岛上有十个光源,但真正决定画面的是前三个。
三盏灯各管一件事
const hemisphere = new THREE.HemisphereLight('#b3d6e0', '#283f41', 2.5);
const sun = new THREE.DirectionalLight('#ffe3bd', 3.5);
const rim = new THREE.DirectionalLight('#7de3ed', 3);
半球光的两个颜色是「天空」与「地面」,负责整体环境色;主光是唯一的投影光源,暖白、从左上打下来;轮廓光是青色、从背面打过来,让岛的外缘有一道冷边。没有轮廓光,岛就会糊进雾里 —— 这是最容易被忽略、又最影响观感的一盏。
阴影只算一次
主岛的投影体是静态的(树、亭子、桥都不动),所以阴影贴图不需要每帧重算:
renderer.shadowMap.autoUpdate = false;
renderer.shadowMap.needsUpdate = true;
阴影相机的范围是手调的,只覆盖主岛:
Object.assign(sun.shadow.camera, { left: -9, right: 9, top: 10, bottom: -8, near: 0.5, far: 40 });
范围越小,2048 的贴图分辨率越集中。再给 normalBias 一点值,修掉自阴影产生的摩尔纹。
夜印不是换灯,是换颜色
切到月夜时,灯的位置、数量一个都没变,变的是颜色、强度、曝光与泛光:
sun.color.copy(sunDay).lerp(sunNight, themeMix);
hemisphere.color.copy(ambientDay).lerp(ambientNight, themeMix);
sun.intensity = 3.5 - themeMix * 1.8;
renderer.toneMappingExposure = 1.13 - themeMix * 0.08;
bloom.strength = 0.32 + themeMix * 0.17;
themeMix 用与帧率无关的指数逼近走向 0 或 1,所以两个氛围之间是「过渡」而不是「切换」。
灯笼只是点光
六个灯笼加亭子里的那一盏都是点光,强度从 0.5 涨到 1.6。它们不投影 —— 点光投影要立方体贴图,成本是方向光的六倍,而这里的效果只靠「亮起来」就够。
数量上也克制:点光越多,每个材质编译出的着色器变体越复杂。七个已经是这个场景的上限。
收尾
场景打光的原则是每盏灯只负责一件事:环境、方向、轮廓、点缀。混着用就会出现「哪里都亮、哪里都没重点」,而那种画面改起来最痛苦 —— 你根本不知道该关哪一盏。

评论
…