第二部分-光照与阴影——11. 阴影系统
11. 阴影系统1. 概述阴影是 Three.js 中增强场景真实感的重要元素。阴影映射Shadow Mapping是一种常见的技术通过从光源视角渲染场景来生成阴影贴图。┌─────────────────────────────────────────────────────────────┐ │ 阴影系统体系 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 阴影映射原理 │ │ ├── 从光源视角渲染场景 │ │ ├── 记录深度信息 │ │ ├── 比较深度确定阴影 │ │ └── 应用到最终渲染 │ │ │ │ 阴影类型 │ │ ├── BasicShadowMap基础阴影性能最好 │ │ ├── PCFShadowMapPCF 阴影边缘柔和 │ │ ├── PCFSoftShadowMap软阴影更柔和 │ │ └── VSMShadowMap方差阴影性能较好 │ │ │ │ 阴影配置 │ │ ├── 渲染器shadowMap.enabled │ │ ├── 光源castShadow │ │ ├── 物体castShadow / receiveShadow │ │ └── 阴影贴图mapSize, bias, normalBias │ │ │ └─────────────────────────────────────────────────────────────┘2. 阴影映射原理2.1 工作流程┌─────────────────────────────────────────────────────────────┐ │ 阴影映射工作流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 第一步从光源视角渲染 │ │ ├── 将相机放置在光源位置 │ │ ├── 渲染场景深度到阴影贴图 │ │ └── 存储每个像素的深度值 │ │ │ │ 第二步主相机渲染 │ │ ├── 将像素坐标转换到光源空间 │ │ ├── 比较深度值与阴影贴图 │ │ ├── 深度更远 → 阴影 │ │ └── 深度更近 → 光照 │ │ │ └─────────────────────────────────────────────────────────────┘3. 阴影类型3.1 阴影类型枚举// 阴影类型THREE.BasicShadowMap// 基础阴影性能最好边缘硬THREE.PCFShadowMap// PCF 阴影边缘柔和THREE.PCFSoftShadowMap// 软阴影边缘更柔和THREE.VSMShadowMap// 方差阴影性能较好适合动态场景// 设置阴影类型renderer.shadowMap.typeTHREE.PCFSoftShadowMap;3.2 阴影类型对比类型性能质量适用场景BasicShadowMap⭐⭐⭐⭐⭐⭐⭐性能优先PCFShadowMap⭐⭐⭐⭐⭐⭐⭐一般场景PCFSoftShadowMap⭐⭐⭐⭐⭐⭐⭐⭐追求画质VSMShadowMap⭐⭐⭐⭐⭐⭐⭐⭐动态物体4. 启用阴影4.1 渲染器配置constrenderernewTHREE.WebGLRenderer({antialias:true});// 启用阴影映射renderer.shadowMap.enabledtrue;// 设置阴影类型renderer.shadowMap.typeTHREE.PCFSoftShadowMap;// 自动更新阴影贴图renderer.shadowMap.autoUpdatetrue;// 手动更新阴影贴图renderer.shadowMap.needsUpdatetrue;4.2 光源配置// 平行光constdirectionalLightnewTHREE.DirectionalLight(0xffffff,1);directionalLight.castShadowtrue;// 点光源constpointLightnewTHREE.PointLight(0xff6600,1);pointLight.castShadowtrue;// 聚光灯constspotLightnewTHREE.SpotLight(0xffffff,1);spotLight.castShadowtrue;4.3 物体配置// 投射阴影mesh.castShadowtrue;// 接收阴影mesh.receiveShadowtrue;// 地面接收阴影但不投射plane.receiveShadowtrue;plane.castShadowfalse;// 光源本身不投射阴影light.castShadowfalse;5. 阴影贴图配置5.1 贴图大小// 提高阴影质量性能消耗增加directionalLight.shadow.mapSize.width2048;directionalLight.shadow.mapSize.height2048;// 常用尺寸配置// 低质量512x512// 中等1024x1024默认// 高质量2048x2048// 极高4096x4096// 动态调整functionsetShadowQuality(quality){constsizes{low:512,medium:1024,high:2048,ultra:4096};constsizesizes[quality];directionalLight.shadow.mapSize.widthsize;directionalLight.shadow.mapSize.heightsize;}5.2 阴影相机参数// 平行光阴影相机constshadowCameradirectionalLight.shadow.camera;// 近平面/远平面shadowCamera.near0.5;shadowCamera.far30;// 正交相机边界只影响阴影区域shadowCamera.left-10;shadowCamera.right10;shadowCamera.top10;shadowCamera.bottom-10;// 更新阴影相机光源移动后shadowCamera.updateProjectionMatrix();// 点光源阴影相机pointLight.shadow.camera.near0.5;pointLight.shadow.camera.far20;// 聚光灯阴影相机spotLight.shadow.camera.near0.5;spotLight.shadow.camera.far25;spotLight.shadow.camera.fovspotLight.angle*180/Math.PI;5.3 阴影偏移// 阴影偏移防止阴影痤疮directionalLight.shadow.bias0.0001;// 法线偏移更适合曲面directionalLight.shadow.normalBias0.05;// 点光源偏移pointLight.shadow.bias0.0001;// 不同场景的常用值// 地面阴影bias 0.0005// 墙面阴影bias 0.001// 曲面物体normalBias 0.056. 阴影辅助器6.1 阴影相机辅助器// 添加阴影相机辅助器调试用constshadowCameraHelpernewTHREE.CameraHelper(directionalLight.shadow.camera);scene.add(shadowCameraHelper);// 点光源阴影辅助器使用光源辅助器即可constpointLightHelpernewTHREE.PointLightHelper(pointLight,0.5);scene.add(pointLightHelper);// 聚光灯阴影辅助器constspotLightHelpernewTHREE.SpotLightHelper(spotLight);scene.add(spotLightHelper);// 更新辅助器shadowCameraHelper.update();spotLightHelper.update();7. 常见阴影问题7.1 阴影痤疮Shadow Acne// 问题表面出现条纹状阴影// 原因深度比较精度问题// 解决方案1增加偏移light.shadow.bias0.0005;// 解决方案2使用法线偏移light.shadow.normalBias0.05;7.2 彼得潘效应Peter Panning// 问题阴影与物体分离// 原因偏移过大// 解决方案减小偏移light.shadow.bias0.0001;7.3 阴影边缘锯齿// 问题阴影边缘锯齿状// 解决方案1增加阴影贴图分辨率light.shadow.mapSize.width2048;light.shadow.mapSize.height2048;// 解决方案2使用柔和阴影类型renderer.shadowMap.typeTHREE.PCFSoftShadowMap;7.4 阴影缺失// 问题阴影不显示// 检查清单// 1. 渲染器启用阴影renderer.shadowMap.enabledtrue;// 2. 光源投射阴影light.castShadowtrue;// 3. 物体投射阴影mesh.castShadowtrue;// 4. 地面接收阴影plane.receiveShadowtrue;// 5. 阴影相机范围覆盖物体shadowCamera.left-10;shadowCamera.right10;shadowCamera.top10;shadowCamera.bottom-10;shadowCamera.near0.5;shadowCamera.far30;8. 性能优化8.1 限制阴影范围// 限制阴影相机范围只覆盖必要区域constsceneBounds{minX:-8,maxX:8,minZ:-8,maxZ:8};shadowCamera.leftsceneBounds.minX;shadowCamera.rightsceneBounds.maxX;shadowCamera.topsceneBounds.maxZ;shadowCamera.bottomsceneBounds.minZ;8.2 选择性阴影// 只让重要物体投射阴影importantMesh.castShadowtrue;unimportantMesh.castShadowfalse;// 背景物体不接收阴影backgroundPlane.receiveShadowfalse;8.3 降低阴影质量// 远距离物体使用低质量阴影if(distance20){mesh.receiveShadowfalse;}// 根据性能动态调整if(isPerformanceMode){renderer.shadowMap.typeTHREE.BasicShadowMap;light.shadow.mapSize.width512;light.shadow.mapSize.height512;}9. 完整示例import*asTHREEfromthree;import{OrbitControls}fromthree/examples/jsm/controls/OrbitControls.js;constscenenewTHREE.Scene();scene.backgroundnewTHREE.Color(0x111122);constcameranewTHREE.PerspectiveCamera(45,window.innerWidth/window.innerHeight,0.1,1000);camera.position.set(8,6,12);camera.lookAt(0,0,0);constrenderernewTHREE.WebGLRenderer({antialias:true});renderer.setSize(window.innerWidth,window.innerHeight);renderer.shadowMap.enabledtrue;renderer.shadowMap.typeTHREE.PCFSoftShadowMap;document.body.appendChild(renderer.domElement);constcontrolsnewOrbitControls(camera,renderer.domElement);controls.enableDampingtrue;// 辅助对象constaxesHelpernewTHREE.AxesHelper(5);scene.add(axesHelper);constgridHelpernewTHREE.GridHelper(15,20);scene.add(gridHelper);// 地面constplaneGeometrynewTHREE.PlaneGeometry(12,12);constplaneMaterialnewTHREE.MeshStandardMaterial({color:0x336699,side:THREE.DoubleSide});constplanenewTHREE.Mesh(planeGeometry,planeMaterial);plane.rotation.x-Math.PI/2;plane.position.y-1.5;plane.receiveShadowtrue;scene.add(plane);// 立方体constboxGeometrynewTHREE.BoxGeometry(1,1,1);constboxMaterialnewTHREE.MeshStandardMaterial({color:0xff6633,metalness:0.2,roughness:0.4});constboxnewTHREE.Mesh(boxGeometry,boxMaterial);box.position.set(1.5,0,1.5);box.castShadowtrue;box.receiveShadowtrue;scene.add(box);// 球体constsphereGeometrynewTHREE.SphereGeometry(0.8,64,64);constsphereMaterialnewTHREE.MeshStandardMaterial({color:0x44aa88,metalness:0.5,roughness:0.3});constspherenewTHREE.Mesh(sphereGeometry,sphereMaterial);sphere.position.set(-1.5,0,1.5);sphere.castShadowtrue;sphere.receiveShadowtrue;scene.add(sphere);// 圆柱体constcylinderGeometrynewTHREE.CylinderGeometry(0.6,0.6,1.2,32);constcylinderMaterialnewTHREE.MeshStandardMaterial({color:0x4488ff,metalness:0.6,roughness:0.2});constcylindernewTHREE.Mesh(cylinderGeometry,cylinderMaterial);cylinder.position.set(0,0,2);cylinder.castShadowtrue;cylinder.receiveShadowtrue;scene.add(cylinder);// 环境光constambientLightnewTHREE.AmbientLight(0x404040,0.4);scene.add(ambientLight);// 平行光主光源constdirectionalLightnewTHREE.DirectionalLight(0xffffff,1);directionalLight.position.set(5,10,7);directionalLight.castShadowtrue;directionalLight.shadow.mapSize.width1024;directionalLight.shadow.mapSize.height1024;directionalLight.shadow.camera.near0.5;directionalLight.shadow.camera.far20;directionalLight.shadow.camera.left-5;directionalLight.shadow.camera.right5;directionalLight.shadow.camera.top5;directionalLight.shadow.camera.bottom-5;directionalLight.shadow.bias0.0001;scene.add(directionalLight);// 辅助光源补光constfillLightnewTHREE.PointLight(0x88aaff,0.3);fillLight.position.set(-3,2,4);scene.add(fillLight);// 辅助器constdirHelpernewTHREE.DirectionalLightHelper(directionalLight,0.5);scene.add(dirHelper);constshadowCameraHelpernewTHREE.CameraHelper(directionalLight.shadow.camera);scene.add(shadowCameraHelper);// GUI 控制importGUIfromlil-gui;constguinewGUI();// 阴影类型constshadowTypes{BasicShadowMap:THREE.BasicShadowMap,PCFShadowMap:THREE.PCFShadowMap,PCFSoftShadowMap:THREE.PCFSoftShadowMap,VSMShadowMap:THREE.VSMShadowMap};gui.add(renderer.shadowMap,type,shadowTypes).name(阴影类型).onChange((){console.log(Shadow type changed);});// 阴影贴图大小constshadowSize{512x512:512,1024x1024:1024,2048x2048:2048,4096x4096:4096};gui.add(directionalLight.shadow.mapSize,width,shadowSize).name(阴影贴图宽度);gui.add(directionalLight.shadow.mapSize,height,shadowSize).name(阴影贴图高度);// 阴影偏移gui.add(directionalLight.shadow,bias,-0.001,0.001,0.00001).name(阴影偏移);gui.add(directionalLight.shadow,normalBias,0,0.1,0.001).name(法线偏移);// 光源强度gui.add(directionalLight,intensity,0,2).name(光源强度);// 动画functionanimate(){requestAnimationFrame(animate);// 旋转物体box.rotation.y0.01;sphere.rotation.x0.01;cylinder.rotation.z0.01;controls.update();renderer.render(scene,camera);}animate();window.addEventListener(resize,onWindowResize,false);functiononWindowResize(){camera.aspectwindow.innerWidth/window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth,window.innerHeight);}10. 总结配置项说明优化方向shadowMap.enabled启用阴影不需要阴影时关闭shadowMap.type阴影类型质量与性能权衡castShadow投射阴影只对必要物体开启receiveShadow接收阴影只对必要物体开启mapSize贴图大小越大质量越高性能越低bias阴影偏移修复阴影痤疮normalBias法线偏移修复曲面阴影shadow.camera阴影相机限制范围提高性能阴影问题解决方案阴影痤疮增加 bias彼得潘效应减少 bias边缘锯齿增加 mapSize 或使用 PCFSoftShadowMap阴影缺失检查阴影配置性能问题降低 mapSize限制阴影范围