Three.js 灯光:给你的 3D 场景打上第一束光
原文出处:Three.js Manual – Lights
本文基于官方手册 Lights 章节整理,用通俗方式带你看懂 three.js 里各种"灯"该怎么用、什么时候该用哪种。
写在前面
前面几篇我们陆续搞定了场景、几何体、材质,模型摆好了、颜色也有了。但如果你刚建好一个场景、加上材质,结果画面是一片惨白、或者黑乎乎的,完全没有立体感。
原因很简单:没有光。
之前我们用MeshBasicMaterial(不受光照影响的材质)时,不用管灯光也能看见东西。但一旦换成MeshPhongMaterial、MeshStandardMaterial这类"会受光影响"的材质,灯光就成了刚需。这篇就带你看一遍 three.js 里的六种灯,环境光、半球光、平行光、点光、聚光灯和区域光。
为了方便演示,我们先准备一个标准场景:一块带棋盘格纹理的地面、一个立方体、一个球,相机放在斜上方,再挂上OrbitControls确保能拖动观察。
constfov=45;constaspect=2;// 画布默认宽高比constnear=0.1;constfar=100;constcamera=newTHREE.PerspectiveCamera(fov,aspect,near,far);camera.position.set(0,10,20);import{OrbitControls}from'three/addons/controls/OrbitControls.js';constcontrols=newOrbitControls(camera,canvas);controls.target.set(0,5,0);controls.update();地面、立方体、球都用MeshPhongMaterial,这样灯光一上去就能立刻看出区别。
一、AmbientLight:环境光,均匀但"没立体感"
环境光是最简单的一种光——它没有方向,只是把整个场景的颜色乘上去。
constcolor=0xFFFFFF;constintensity=1;constlight=newTHREE.AmbientLight(color,intensity);scene.add(light);你可以把它理解成"大晴天把所有东西都均匀照亮"。它的工作原理其实就是一道公式:
最终颜色 = 材质颜色 × 灯光颜色 × 强度划重点:环境光其实"不太像光"。因为它 100% 均匀,所以物体没有任何明暗交界、没有立体感,看起来是平的。
二、HemisphereLight:半球光,模拟"天光 + 地面反光"
半球光可以理解为环境光的升级版:它有两种颜色,一种从上方(天空)照下来,一种从下方(地面)反射上来。
constskyColor=0xB1E1FF;// 天蓝constgroundColor=0xB97A20;// 土黄橙constintensity=1;constlight=newTHREE.HemisphereLight(skyColor,groundColor,intensity);scene.add(light);它依然不会有强烈的明暗对比,但物体朝上的一面会偏冷色、朝下的一面会偏暖色,比纯环境光自然得多。
三、DirectionalLight:平行光
平行光模拟的是"无限远处的光源",比如太阳。它的特点是:所有光线方向一致、互相平行,所以没有具体的发光点,只关心光从哪个方向来。
constcolor=0xFFFFFF;constintensity=1;constlight=newTHREE.DirectionalLight(color,intensity);light.position.set(0,10,0);light.target.position.set(-5,0,0);scene.add(light);scene.add(light.target);常见坑:平行光是靠position和target两个点来决定方向的——光从position射向target。所以target也要加进场景(scene.add(light.target)),否则它可能不生效。
调试时可以挂一个DirectionalLightHelper,用一个线框小飞机帮你直观看到光的方向:
consthelper=newTHREE.DirectionalLightHelper(light);scene.add(helper);注意:每次改了position或target,都要调一下light.target.updateMatrixWorld()和helper.update(),辅助箭头才会跟着动。
四、PointLight:点光
点光就是一个"会发光的点",光线从它向四面八方散射,类似现实里的灯泡、蜡烛。
constcolor=0xFFFFFF;constintensity=150;// 注意:点光强度通常要调大很多constlight=newTHREE.PointLight(color,intensity);light.position.set(0,10,0);scene.add(light);两个关键点:
- 点光的
intensity默认偏小,手册里给了 150,比方向光的 1 大得多,别奇怪。 - 它有个
distance属性:设为 0 表示照到无穷远;设成大于 0 的数,光会在distance范围内从满强度逐渐衰减到 0——你拖动distance就能看到光照范围收缩。
五、SpotLight:聚光灯
聚光灯 = 点光 + 一个锥形光束,只照亮锥体内部,像手电筒、舞台灯。
constcolor=0xFFFFFF;constintensity=150;constlight=newTHREE.SpotLight(color,intensity);scene.add(light);scene.add(light.target);// 同样需要 target 决定锥体朝向它比点光多了两个核心参数:
angle(弧度):锥体张角大小。注意是弧度不是角度,所以手册里用DegRadHelper把角度和弧度互相转换。penumbra(0~1):内外锥之间的"柔化过渡带"。0表示边缘锐利,1表示光从锥体中心就开始渐隐到边缘。想做柔和的舞台光,就把penumbra往 1 调。
坑:聚光灯的锥体默认"开口朝下",如果它埋在地面以下你可能根本看不到光锥。把distance调小一点(比如 5),就能看到张开的锥口了。
六、RectAreaLight:区域光
区域光就是一块"矩形发光面",像天花板上的长条灯、磨砂天窗。它最特别:
- 只支持
MeshStandardMaterial和MeshPhysicalMaterial,所以得先把所有材质换成标准材质。 - 使用前必须初始化
RectAreaLightUniformsLib,否则灯能亮但效果会很怪。 - 它不用 target,而是用自身的
rotation决定朝向。 - 它的 Helper 必须是光的子节点(
light.add(helper)),不能像别的 helper 那样直接加进场景。
import{RectAreaLightUniformsLib}from'three/addons/lights/RectAreaLightUniformsLib.js';import{RectAreaLightHelper}from'three/addons/helpers/RectAreaLightHelper.js';RectAreaLightUniformsLib.init();// 别忘了这步constcolor=0xFFFFFF;constintensity=5;constwidth=12;constheight=4;constlight=newTHREE.RectAreaLight(color,intensity,width,height);light.position.set(0,10,0);light.rotation.x=THREE.MathUtils.degToRad(-90);scene.add(light);consthelper=newRectAreaLightHelper(light);light.add(helper);七、性能提醒
最后说一个很现实的点:每往场景里多加一盏灯,three.js 的渲染就会变慢一点。所以实战里要养成习惯——用尽可能少的灯达到你要的效果。
总结
| 灯光类型 | 像什么 | 有方向吗 | 典型用途 |
|---|---|---|---|
| AmbientLight | 均匀打底 | 无 | 防止暗部太黑(垫底) |
| HemisphereLight | 天光+地反 | 上/下两向 | 自然户外补光 |
| DirectionalLight | 太阳 | 有(平行) | 主光源、阴影 |
| PointLight | 灯泡 | 四面八方 | 局部点光源 |
| SpotLight | 射灯 | 锥形 | 舞台、手电 |
| RectAreaLight | 灯管/天窗 | 矩形面 | 室内写实照明 |
延伸阅读
- Light – three.js docs
- DirectionalLight
- PointLight
- SpotLight
- RectAreaLight
- 下一篇我们聊 Cameras(相机)