光照与阴影
光照是 3D 场景中最重要的元素之一,它决定了物体的明暗变化、颜色表现和空间感。Three.js 提供了多种光源类型,可以模拟现实世界中不同的光照条件。本章将详细介绍各种光源的使用方法和阴影效果的实现。
光照基础
在现实世界中,光线从光源发出,照射到物体表面,然后反射到我们的眼睛。Three.js 模拟这个过程,通过计算光源和物体表面的关系,得出最终的渲染效果。
Three.js 的光照计算基于材质类型:
- MeshBasicMaterial:不受光照影响,始终显示纯色或纹理
- MeshLambertMaterial:计算漫反射,适合哑光表面
- MeshPhongMaterial:计算漫反射和高光,适合光滑表面
- MeshStandardMaterial:基于物理的渲染,更真实的光照效果
光源类型
Three.js 提供了多种光源类型,每种都有特定的用途和特性。
环境光(AmbientLight)
环境光均匀地照亮场景中的所有物体,没有方向性,不会产生阴影。
const ambientLight = new THREE.AmbientLight(
0xffffff, // 颜色
0.5 // 强度
);
scene.add(ambientLight);
环境光模拟的是光线在环境中多次反射后形成的均匀照明。单独使用环境光会让场景显得平淡,通常需要配合其他光源使用。
环境光强度不宜过高,否则会削弱其他光源的效果,让场景失去立体感。
半球光(HemisphereLight)
半球光模拟天空和地面的光照,提供从上方和下方两个方向的光线。
const hemisphereLight = new THREE.HemisphereLight(
0xffffbb, // 天空颜色
0x080820, // 地面颜色
1 // 强度
);
scene.add(hemisphereLight);
半球光常用于户外场景,天空提供主要照明,地面提供柔和的反射光。这种光照方式比单纯的环境光更有层次感。
方向光(DirectionalLight)
方向光模拟来自远处的平行光,如太阳光。所有光线都是平行的,方向一致。
const directionalLight = new THREE.DirectionalLight(
0xffffff, // 颜色
1 // 强度
);
// 设置光源方向(光线从这个位置射向原点)
directionalLight.position.set(5, 10, 5);
// 设置目标点
directionalLight.target.position.set(0, 0, 0);
scene.add(directionalLight);
scene.add(directionalLight.target);
方向光是最常用的光源之一,它可以产生阴影,适合模拟太阳光等远距离光源。
方向光的位置只影响光线方向,不影响光照强度。无论光源位置远近,照射到物体上的光强都是相同的。
点光源(PointLight)
点光源从一个点向所有方向发射光线,类似灯泡、蜡烛等。
const pointLight = new THREE.PointLight(
0xff0000, // 颜色
1, // 强度
10, // 衰减距离
2 // 衰减指数
);
pointLight.position.set(0, 5, 0);
scene.add(pointLight);
// 显示光源位置(可选)
const helper = new THREE.PointLightHelper(pointLight, 0.5);
scene.add(helper);
点光源的光强会随着距离衰减。衰减距离决定了光能到达的最远距离,衰减指数控制衰减的速度。
衰减公式:intensity = color * intensity / (distance + decay * distance^2)
聚光灯(SpotLight)
聚光灯从一个点沿锥形方向发射光线,类似手电筒、舞台灯光。
const spotLight = new THREE.SpotLight(
0xffffff, // 颜色
1, // 强度
20, // 衰减距离
Math.PI / 6, // 照射角度(弧度)
0.5 // 边缘柔和度
);
spotLight.position.set(0, 10, 0);
spotLight.target.position.set(0, 0, 0);
scene.add(spotLight);
scene.add(spotLight.target);
// 显示光源辅助线
const helper = new THREE.SpotLightHelper(spotLight);
scene.add(helper);
照射角度决定了光锥的张开程度,边缘柔和度控制光锥边缘的过渡效果。
矩形区域光(RectAreaLight)
矩形区域光从一个矩形区域均匀发射光线,模拟窗户、屏幕等面光源。
import { RectAreaLightUniformsLib } from 'three/addons/lights/RectAreaLightUniformsLib.js';
RectAreaLightUniformsLib.init();
const rectLight = new THREE.RectAreaLight(
0xffffff, // 颜色
5, // 强度
4, // 宽度
2 // 高度
);
rectLight.position.set(0, 5, 0);
rectLight.lookAt(0, 0, 0);
scene.add(rectLight);
// 显示光源区域
const helper = new THREE.RectAreaLightHelper(rectLight);
scene.add(helper);
矩形区域光只支持 MeshStandardMaterial 和 MeshPhysicalMaterial,不支持产生阴影。
阴影
阴影可以增强场景的真实感和立体感。Three.js 的阴影基于阴影映射技术,从光源视角渲染深度图,然后在渲染场景时比较深度值。
启用阴影
// 1. 启用渲染器的阴影功能
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// 2. 设置光源投射阴影
light.castShadow = true;
// 3. 设置物体投射阴影
mesh.castShadow = true;
// 4. 设置物体接收阴影
ground.receiveShadow = true;
阴影类型有三种:
THREE.BasicShadowMap:基础阴影,速度快但边缘锯齿明显THREE.PCFShadowMap:PCF 软阴影,边缘较柔和(默认)THREE.PCFSoftShadowMap:更柔和的阴影,需要更多采样
方向光阴影配置
方向光的阴影使用正交相机,需要配置阴影范围:
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 5);
directionalLight.castShadow = true;
// 阴影贴图分辨率
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
// 阴影相机范围
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;
// 阴影偏移(解决阴影痤疮问题)
directionalLight.shadow.bias = -0.0001;
scene.add(directionalLight);
阴影相机范围决定了阴影的覆盖区域。范围越小,阴影精度越高。需要根据场景大小合理设置。
阴影偏移用于解决"阴影痤疮"问题,即物体表面出现条纹状的伪影。正值会让阴影远离物体,负值会让阴影靠近物体。
点光源阴影配置
点光源的阴影使用透视相机,呈六面体分布:
const pointLight = new THREE.PointLight(0xffffff, 1, 20);
pointLight.position.set(0, 5, 0);
pointLight.castShadow = true;
pointLight.shadow.mapSize.width = 1024;
pointLight.shadow.mapSize.height = 1024;
pointLight.shadow.camera.near = 0.5;
pointLight.shadow.camera.far = 20;
scene.add(pointLight);
点光源阴影需要渲染六次(六个方向),性能开销较大。
聚光灯阴影配置
const spotLight = new THREE.SpotLight(0xffffff, 1);
spotLight.position.set(0, 10, 0);
spotLight.castShadow = true;
spotLight.shadow.mapSize.width = 1024;
spotLight.shadow.mapSize.height = 1024;
spotLight.shadow.camera.near = 1;
spotLight.shadow.camera.far = 20;
spotLight.shadow.camera.fov = 30;
scene.add(spotLight);
阴影优化
阴影渲染是性能开销较大的操作,以下是一些优化建议:
限制阴影范围:只对需要阴影的区域启用阴影,使用阴影相机的 near/far/left/right/top/bottom 限制范围。
降低阴影分辨率:根据场景需求选择合适的分辨率,1024x1024 通常足够,远处物体可以用更低分辨率。
使用级联阴影:对于大场景,可以使用 CSM(Cascaded Shadow Maps)将阴影分成多个级别,近处用高精度,远处用低精度。
减少阴影光源数量:每个阴影光源都需要额外的渲染 pass,尽量减少数量。
光照辅助工具
Three.js 提供了辅助工具来可视化光源:
// 方向光辅助线
const dirHelper = new THREE.DirectionalLightHelper(directionalLight, 1);
scene.add(dirHelper);
// 点光源辅助球
const pointHelper = new THREE.PointLightHelper(pointLight, 0.5);
scene.add(pointHelper);
// 聚光灯辅助线
const spotHelper = new THREE.SpotLightHelper(spotLight);
scene.add(spotHelper);
// 半球光辅助线
const hemiHelper = new THREE.HemisphereLightHelper(hemisphereLight, 1);
scene.add(hemiHelper);
这些辅助工具在开发调试时非常有用,可以帮助理解光源的位置和方向。
光照最佳实践
光照组合
通常需要组合多种光源来获得好的效果:
// 基础环境光(提供整体亮度)
const ambientLight = new THREE.AmbientLight(0xffffff, 0.3);
scene.add(ambientLight);
// 半球光(提供天空和地面的光照变化)
const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.5);
scene.add(hemisphereLight);
// 主光源(产生阴影)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 5);
directionalLight.castShadow = true;
scene.add(directionalLight);
// 补光(填充阴影区域)
const fillLight = new THREE.DirectionalLight(0xffffff, 0.3);
fillLight.position.set(-5, 5, -5);
scene.add(fillLight);
这种组合方式模拟了真实环境中的光照:天空提供环境光,太阳提供主光源,周围环境提供补光。
光照颜色
光照颜色会影响场景的氛围:
- 暖色调(黄、橙):营造温暖、舒适的感觉,适合室内、黄昏场景
- 冷色调(蓝、紫):营造冷静、神秘的感觉,适合夜晚、科技场景
- 白色:中性,不影响物体本身的颜色
// 黄昏场景
const sunLight = new THREE.DirectionalLight(0xffd599, 1);
const skyLight = new THREE.HemisphereLight(0xffd599, 0x080820, 0.5);
// 夜晚场景
const moonLight = new THREE.DirectionalLight(0x4466ff, 0.5);
const ambientLight = new THREE.AmbientLight(0x111122, 0.3);
性能考虑
光源数量直接影响渲染性能:
- 环境光、半球光:性能开销小,可以自由使用
- 方向光:性能开销中等,支持阴影时开销增加
- 点光源:性能开销较大,每个点光源都需要额外的光照计算
- 聚光灯:性能开销较大,类似点光源
- 矩形区域光:性能开销最大,只支持 PBR 材质
对于移动端或性能敏感的场景,建议限制光源数量,使用光照贴图等技术替代实时计算。
完整示例
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// 场景设置
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(8, 8, 8);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// 环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
scene.add(ambientLight);
// 半球光
const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.3);
scene.add(hemisphereLight);
// 主方向光
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.set(2048, 2048);
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
directionalLight.shadow.camera.left = -15;
directionalLight.shadow.camera.right = 15;
directionalLight.shadow.camera.top = 15;
directionalLight.shadow.camera.bottom = -15;
directionalLight.shadow.bias = -0.0001;
scene.add(directionalLight);
// 点光源
const pointLight = new THREE.PointLight(0xff6b6b, 1, 10);
pointLight.position.set(-3, 3, 0);
scene.add(pointLight);
// 点光源辅助球
const pointLightSphere = new THREE.Mesh(
new THREE.SphereGeometry(0.1, 16, 16),
new THREE.MeshBasicMaterial({ color: 0xff6b6b })
);
pointLightSphere.position.copy(pointLight.position);
scene.add(pointLightSphere);
// 创建物体
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
const sphereGeometry = new THREE.SphereGeometry(0.6, 32, 32);
const material = new THREE.MeshStandardMaterial({
color: 0x00ff88,
metalness: 0.3,
roughness: 0.4
});
const box = new THREE.Mesh(boxGeometry, material);
box.position.set(-2, 0.5, 0);
box.castShadow = true;
box.receiveShadow = true;
scene.add(box);
const sphere = new THREE.Mesh(sphereGeometry, material);
sphere.position.set(2, 0.6, 0);
sphere.castShadow = true;
sphere.receiveShadow = true;
scene.add(sphere);
// 地面
const groundGeometry = new THREE.PlaneGeometry(20, 20);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x333344,
roughness: 0.8
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// 动画
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const elapsed = clock.getElapsedTime();
// 旋转物体
box.rotation.y = elapsed;
box.rotation.x = elapsed * 0.5;
// 移动点光源
pointLight.position.x = Math.sin(elapsed) * 3;
pointLight.position.z = Math.cos(elapsed) * 3;
pointLightSphere.position.copy(pointLight.position);
controls.update();
renderer.render(scene, camera);
}
animate();
// 响应窗口变化
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
小结
光照和阴影是 3D 场景真实感的关键:
- 环境光提供基础照明,半球光模拟天空和地面光照
- 方向光模拟太阳光,适合产生阴影
- 点光源模拟灯泡等点状光源,光线向四周发散
- 聚光灯产生锥形光束,适合局部照明
- 阴影增强立体感,需要合理配置阴影范围和参数
下一章我们将学习动画和交互,让 3D 场景动起来。