2.3阴影 (Shadows) Three.js 中的阴影 (Shadows) 阴影是增强 3D 场景真实感的重要组成部分。在 Three.js 中,我们可以通过配置光源和物体来创建阴影。本文将深入探讨 Three.js 中阴影的实现机制、配置选项以及优化技巧。 2.3 阴影 (Shadows) 2.3.1 阴影原理 Three.js 使用阴影贴图 (Shadow Map) 技术来生成阴影。其基本原理是: 光源视角渲染: 从光源的位置,渲染场景的深度信息,生成一张深度图 (Depth Map),这就是阴影贴图。 物体渲染: 从摄像机视角渲染场景,对于每个像素,计算其到光源的距离。 阴影判断: 将像素到光源的距离与阴影贴图中对应位置的深度值进行比较。
阴影是增强 3D 场景真实感的重要组成部分。在 Three.js 中,我们可以通过配置光源和物体来创建阴影。本文将深入探讨 Three.js 中阴影的实现机制、配置选项以及优化技巧。
Three.js 使用阴影贴图 (Shadow Map) 技术来生成阴影。其基本原理是:
光源视角渲染: 从光源的位置,渲染场景的深度信息,生成一张深度图 (Depth Map),这就是阴影贴图。
物体渲染: 从摄像机视角渲染场景,对于每个像素,计算其到光源的距离。
阴影判断: 将像素到光源的距离与阴影贴图中对应位置的深度值进行比较。如果像素的距离大于阴影贴图中的深度值,则该像素位于阴影中。
要启用场景中的阴影,需要进行以下步骤:
renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 可选:设置阴影贴图类型
renderer.shadowMap.enabled = true 开启了渲染器的阴影计算功能。renderer.shadowMap.type 用于设置阴影贴图的类型,常见的有:
THREE.BasicShadowMap: 基本阴影贴图,性能最高,但阴影边缘锯齿感明显。
THREE.PCFShadowMap: 默认值,使用 Percentage Closer Filtering (PCF) 算法,对阴影边缘进行平滑处理。
THREE.PCFSoftShadowMap: 比 PCFShadowMap 更柔和的阴影,但性能消耗更高。
THREE.VSMShadowMap: 使用 Variance Shadow Maps (VSM) 算法,可以产生更柔和的阴影,但对透明物体支持不好。
并非所有光源都能产生阴影。只有 DirectionalLight、SpotLight 和 PointLight 支持阴影。
const directionalLight = new THREE.DirectionalLight(0xffffff, 1); directionalLight.position.set(5, 10, 5); directionalLight.castShadow = true; // 开启光源的阴影投射 scene.add(directionalLight);
directionalLight.castShadow = true 开启了光源的阴影投射能力。
并非所有物体都能投射或接收阴影。
const sphereGeometry = new THREE.SphereGeometry(1, 32, 32); const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 }); const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial); sphere.castShadow = true; // 允许物体投射阴影 scene.add(sphere); const planeGeometry = new THREE.PlaneGeometry(10, 10); const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x808080 }); const plane = new THREE.Mesh(planeGeometry, planeMaterial); plane.rotation.x = -Math.PI / 2; plane.receiveShadow = true; // 允许物体接收阴影 scene.add(plane);
mesh.castShadow = true 允许物体投射阴影。
mesh.receiveShadow = true 允许物体接收阴影。
光源的 shadow 属性允许我们配置阴影贴图的各种参数,以控制阴影的质量和性能。
directionalLight.shadow.mapSize.width = 1024; directionalLight.shadow.mapSize.height = 1024; directionalLight.shadow.camera.near = 0.5; directionalLight.shadow.camera.far = 50; directionalLight.shadow.camera.left = -5; directionalLight.shadow.camera.right = 5; directionalLight.shadow.camera.top = 5; directionalLight.shadow.camera.bottom = -5;
shadow.mapSize.width 和 shadow.mapSize.height: 阴影贴图的尺寸,值越大,阴影的细节越高,但性能消耗也越大。 常用值有 512, 1024, 2048 等。
shadow.camera.near 和 shadow.camera.far: 阴影相机 (Shadow Camera) 的近平面和远平面,决定了阴影的渲染范围。 需要根据场景的大小和光源的位置进行调整。
shadow.camera.left, shadow.camera.right, shadow.camera.top, shadow.camera.bottom: 正交投影阴影相机的视锥体边界。 对于 DirectionalLight 和 SpotLight,阴影相机通常使用正交投影。 调整这些值可以控制阴影的覆盖范围和精度。
对于 SpotLight,还可以设置 shadow.angle 和 shadow.penumbra 来控制阴影锥的形状和边缘模糊度。
spotLight.shadow.angle = Math.PI / 4; // 阴影锥的角度 spotLight.shadow.penumbra = 0.05; // 阴影边缘模糊度
阴影的计算是一个性能消耗较大的过程。以下是一些优化阴影性能的技巧:
降低阴影贴图尺寸: 在满足视觉效果的前提下,尽量使用较小的阴影贴图尺寸。
调整阴影相机参数: 合理设置 near、far、left、right、top、bottom 等参数,使阴影相机只渲染必要的区域。
使用 shadow.autoUpdate = false: 如果场景中的物体或光源是静态的,可以关闭阴影的自动更新,手动控制更新时机。
directionalLight.shadow.autoUpdate = false; function render() { // ... if (needUpdateShadow) { directionalLight.shadow.map.dispose(); // 释放之前的阴影贴图 directionalLight.shadow.map = null; renderer.render(scene, camera); // 重新渲染阴影 directionalLight.shadow.needsUpdate = true; needUpdateShadow = false; } renderer.render(scene, camera); }
减少投射和接收阴影的物体数量: 只让必要的物体投射和接收阴影。
使用 LOD (Level of Detail): 对于远处物体,使用低模,并关闭阴影投射和接收。
Shadow Caching: 对于静态场景,可以将阴影贴图缓存起来,避免重复计算。
以下是一个完整的 Three.js 阴影示例:
<!DOCTYPE html> <html> <head> <title>Three.js Shadows</title> <style> body { margin: 0; } canvas { display: block; } </style> </head> <body> <script src="https://cdn.jsdelivr.net/npm/three@0.155.0/build/three.min.js"></script> <script> const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; // 启用阴影 renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 设置阴影类型 document.body.appendChild(renderer.domElement); // 创建平面 const planeGeometry = new THREE.PlaneGeometry(20, 20); const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x808080 }); const plane = new THREE.Mesh(planeGeometry, planeMaterial); plane.rotation.x = -Math.PI / 2; plane.receiveShadow = true; // 允许接收阴影 scene.add(plane); // 创建球体 const sphereGeometry = new THREE.SphereGeometry(1, 32, 32); const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 }); const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial); sphere.castShadow = true; // 允许投射阴影 sphere.position.y = 1; scene.add(sphere); // 创建光源 const directionalLight = new THREE.DirectionalLight(0xffffff, 1); directionalLight.position.set(5, 10, 5); directionalLight.castShadow = true; // 允许投射阴影 scene.add(directionalLight); // 配置阴影贴图 directionalLight.shadow.mapSize.width = 1024; directionalLight.shadow.mapSize.height = 1024; directionalLight.shadow.camera.near = 0.5; directionalLight.shadow.camera.far = 50; directionalLight.shadow.camera.left = -5; directionalLight.shadow.camera.right = 5; directionalLight.shadow.camera.top = 5; directionalLight.shadow.camera.bottom = -5; // 环境光 const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); camera.position.set(0, 5, 10); camera.lookAt(0, 0, 0); function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); } animate(); </script> </body> </html>
这个示例创建了一个带有阴影的简单场景,包含一个平面、一个球体和一个方向光。
阴影不显示: 检查渲染器的阴影支持是否启用,光源和物体是否都设置了 castShadow 和 receiveShadow。
阴影锯齿感严重: 尝试使用 PCFShadowMap 或 PCFSoftShadowMap,并增加阴影贴图的尺寸。
阴影裁剪: 调整阴影相机的 near 和 far 值,确保阴影覆盖整个场景。
性能问题: 参考阴影优化部分,降低阴影贴图尺寸,减少投射和接收阴影的物体数量。
自阴影 (Shadow Acne): 由于深度缓冲区的精度限制,物体表面可能会错误地投射阴影到自身。 可以尝试调整 shadow.bias 属性来减轻这个问题。 directionalLight.shadow.bias = -0.0001;
阴影是 Three.js 中一个重要的渲染特性,可以显著提高场景的真实感。 通过合理配置光源和物体的阴影属性,并进行适当的优化,可以创建出高质量且性能良好的阴影效果。 理解阴影的原理和配置选项是掌握 Three.js 渲染的关键一步。