灯光与阴影
🎯 引言
上一篇我们发现 Standard 材质离开灯光就是一团黑。灯光是 3D 场景的“氛围组”,决定画面的明暗和层次。学完这篇文章,你能认识三种常用灯光,学会“环境光打底 + 平行光主光源”的组合套路,并给场景加上真实的阴影。
🧱 三种常用灯光
Three.js 内置了好几种灯光,日常高频使用的是这三种:
| 灯光 | 类名 | 特点 | 生活类比 |
|---|---|---|---|
| 环境光 | AmbientLight | 均匀照亮所有物体,没有方向 | 阴天的自然光 |
| 平行光 | DirectionalLight | 从一个方向平行照射 | 太阳 |
| 点光源 | PointLight | 从一点向四周发散 | 灯泡 |
三种光创建时都接收两个参数:颜色和强度。
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // 环境光
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5); // 平行光
const pointLight = new THREE.PointLight(0xffffff, 30); // 点光源
环境光没有方向,放进场景就生效;平行光和点光源需要设置位置:
directionalLight.position.set(3, 5, 4); // 从右上方斜照下来
pointLight.position.set(2, 3, 2); // 挂在这个坐标的“灯泡”
30 这类偏大的值才够亮。平行光和环境光的强度一般在 0.5 ~ 2 之间调整。🛠 组合布光:环境光打底 + 平行光主光源
只用平行光,背光面会黑得看不清;只用环境光,画面平得像纸片。实际开发中的常用套路是两种光配合:
- 环境光:低强度,负责把背光面“兜底”提亮。
- 平行光:高强度,负责制造明暗层次,当主光源。
// 环境光打底:强度低一点
scene.add(new THREE.AmbientLight(0xffffff, 0.4));
// 平行光当主光源:从右上方照下来
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5);
directionalLight.position.set(3, 5, 4);
scene.add(directionalLight);
下面是对比效果:左边只有平行光,背光面接近全黑;右边加上环境光后,背光面也能看清轮廓:
只有平行光
环境光 + 平行光
🧱 给场景加上阴影
有了光,自然就有影子。Three.js 的阴影默认是关闭的,需要三步手动开启:
// 1. 渲染器开启阴影贴图
renderer.shadowMap.enabled = true;
// 2. 灯光声明“产生阴影”
directionalLight.castShadow = true;
// 3. 物体声明“投出阴影”或“接收阴影”
cube.castShadow = true; // 立方体投出阴影
plane.receiveShadow = true; // 地面接收阴影
完整示例:一个悬空的立方体,影子投在地面上:
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, 320 / 240, 0.1, 1000);
camera.position.set(2, 2, 4);
camera.lookAt(0, 0, 0);
scene.add(new THREE.AmbientLight(0xffffff, 0.4));
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5);
directionalLight.position.set(3, 5, 4);
directionalLight.castShadow = true;
scene.add(directionalLight);
// 悬空的立方体
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ color: 0x44aa88 })
);
cube.position.y = 0.8;
cube.castShadow = true;
scene.add(cube);
// 地面:用一个大平面充当
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(6, 6),
new THREE.MeshStandardMaterial({ color: 0xdddddd })
);
plane.rotation.x = -Math.PI / 2; // 平面默认竖着,转成水平
plane.position.y = -0.5;
plane.receiveShadow = true;
scene.add(plane);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(320, 240);
renderer.shadowMap.enabled = true; // 别忘了渲染器这一步
containerRef.value.appendChild(renderer.domElement);
renderer.render(scene, camera);
代码里两个新面孔解释一下:
camera.lookAt(0, 0, 0):让相机转头盯住原点,不管相机摆在哪都朝向场景中心。plane.rotation.x = -Math.PI / 2:新建的平面是竖立的(正对着相机),绕 x 轴转 90° 才平躺在地上。
下面是这段代码的真实渲染效果,注意立方体投在地面上的影子:
castShadow,小物件不投影,兼顾效果和性能。🧾 小节总结
- 三种常用灯光:环境光(均匀兜底)、平行光(像太阳,当主光源)、点光源(像灯泡)。
- 组合套路:低强度环境光打底 + 高强度平行光制造层次。
- 点光源按物理距离衰减,强度要给得比平行光大很多。
- 阴影三步开启:渲染器
shadowMap.enabled、灯光castShadow、物体castShadow/receiveShadow。 camera.lookAt()让相机盯住指定点;平面默认竖立,要转 90° 才能当地面。
❓ 知识问答
Q1:灯光越多画面越亮吗?
总体趋势是变亮,但过亮会一片惨白。亮度主要靠一主一辅两盏灯控制,不够再微调强度,不建议堆数量。
Q2:为什么我的阴影边缘粗糙、有锯齿?
阴影贴图默认分辨率较低。可以提高灯光的 shadow.mapSize,比如 light.shadow.mapSize.set(1024, 1024),代价是多一点显存开销。
Q3:Basic 材质会受到灯光影响吗?
不会。MeshBasicMaterial 天生不响应灯光,加再多灯颜色也不变。想要明暗效果请用 MeshStandardMaterial。
Q4:环境光会产生阴影吗?
不会。环境光没有方向,到处都一样亮,无从产生影子。能产生阴影的是平行光、点光源这类有方向的光。
🧪 小练习
搭一个“黄昏小场景”:
- 创建一个球体放在地面上,球体和地面都用 Standard 材质。
- 添加低强度环境光,再加一盏暖色调(如
0xffaa55)的平行光。 - 开启阴影,让球体的影子投在地面上。
<template>
<div ref="containerRef" class="container"></div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import * as THREE from 'three';
const containerRef = ref(null);
onMounted(() => {
// 请在这里编写代码
});
</script>
<style scoped>
.container {
width: 320px;
height: 240px;
}
</style>
🎉 恭喜你已经学会布光和阴影啦!下一篇我们回头细看相机的四个参数,搞懂“看得清、看不见”背后的原理。
