第十九节:阴影进阶 - 软阴影与性能平衡技术
第十九节:阴影进阶 - 软阴影与性能平衡技术
PCF滤波与级联阴影映射实战
1. 核心概念解析
1.1 阴影映射原理
技术 | 原理 | 优点 | 缺点 |
---|---|---|---|
阴影贴图 | 从光源视角渲染深度图 | 实现简单,通用性强 | 锯齿明显,分辨率依赖 |
PCF软阴影 | 多重采样深度图边缘平滑 | 边缘柔和,视觉效果自然 | 性能开销较大 |
VSM | 使用方差计算阴影概率 | 软阴影质量高,性能较好 | 容易出现光渗现象 |
CSM | 多个阴影贴图覆盖不同距离区域 | 远近阴影质量均衡 | 实现复杂,内存占用高 |
1.2 阴影质量影响因素
2. PCF软阴影实现
2.1 基础PCF实现
// 创建方向光与阴影
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 7);
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.top = 20; // 正交相机范围
directionalLight.shadow.camera.right = 20;
directionalLight.shadow.camera.bottom = -20;
directionalLight.shadow.camera.left = -20;// 启用PCF软阴影
directionalLight.shadow.radius = 3; // PCF采样半径// 添加到场景
scene.add(directionalLight);
2.2 自定义PCF着色器
// 自定义PCF软阴影材质
const softShadowMaterial = new THREE.ShaderMaterial({uniforms: {shadowMap: { value: directionalLight.shadow.map },lightProjectionMatrix: { value: directionalLight.shadow.camera.projectionMatrix },lightViewMatrix: { value: directionalLight.shadow.camera.matrixWorldInverse },shadowBias: { value: 0.003 },shadowRadius: { value: 3.0 }},vertexShader: `varying vec4 vShadowCoord;void main() {gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);// 计算阴影坐标vShadowCoord = lightProjectionMatrix * lightViewMatrix * modelMatrix * vec4(position, 1.0);}`,fragmentShader: `uniform sampler2D shadowMap;uniform float shadowBias;uniform float shadowRadius;varying vec4 vShadowCoord;// PCF采样函数float pcfShadow(sampler2D shadowMap, vec2 uv, float compare, float radius) {float result = 0.0;int samples = 0;for(int x = -2; x <= 2; x++) {for(int y = -2; y <= 2; y++) {vec2 offset = vec2(x, y) * radius / textureSize(shadowMap, 0);float depth = texture2D(shadowMap, uv + offset).r;result += compare > depth + shadowBias ? 0.0 : 1.0;samples++;}}return result / float(samples);}void main() {// 透视除法vec3 shadowCoord = vShadowCoord.xyz / vShadowCoord.w;shadowCoord = shadowCoord * 0.5 + 0.5; // 转换到[0,1]范围// 检查是否在阴影贴图范围内if(shadowCoord.z > 1.0 || shadowCoord.x < 0.0 || shadowCoord.x > 1.0 || shadowCoord.y < 0.0 || shadowCoord.y > 1.0) {gl_FragColor = vec4(1.0); // 不在范围内,完全照亮return;}// 计算阴影float shadow = pcfShadow(shadowMap, shadowCoord.xy, shadowCoord.z, shadowRadius);gl_FragColor = vec4(vec3(shadow), 1.0);}`
});
3. 级联阴影映射(CSM)
3.1 CSM原理与配置
// 创建CSM实例
import { CSM } from 'three/addons/csm/CSM.js';const csm = new CSM({maxFar: 100, // 最远视距cascades: 4, // 级联数量mode: 'practical', // 模式: practical, logarithmic, uniformparent: scene, // 父场景shadowMapSize: 1024, // 每个级联的阴影贴图大小lightDirection: new THREE.Vector3(1, -1, 1).normalize(),camera: camera // 主相机
});// 更新CSM(每帧调用)
function updateCSM() {csm.update(camera.matrix);
}// 材质适配(需要支持接收阴影)
csm.setupMaterial(mesh.material);
3.2 自定义CSM着色器
// CSM兼容的自定义着色器
const csmMaterial = new THREE.ShaderMaterial({defines: {USE_CSM: 1,CSM_CASCADES: 4, // 与CSM实例中的级联数量一致CSM_FADE: 1 // 启用级联间渐变},uniforms: {...THREE.UniformsLib['csm'], // 包含CSM所需uniforms// 其他自定义uniforms},vertexShader: `#include <common>#include <csm_pars_vertex>void main() {#include <csm_vertex>// 其他顶点着色器代码}`,fragmentShader: `#include <common>#include <csm_pars_fragment>void main() {#include <csm_fragment>// 其他片元着色器代码}`
});
4. 性能优化策略
4.1 阴影贴图分辨率优化
// 动态分辨率调整
function adjustShadowResolution(quality) {let resolution;switch(quality) {case 'low':resolution = 512;directionalLight.shadow.radius = 1; // 减少PCF采样break;case 'medium':resolution = 1024;directionalLight.shadow.radius = 2;break;case 'high':resolution = 2048;directionalLight.shadow.radius = 3;break;case 'ultra':resolution = 4096;directionalLight.shadow.radius = 4;break;}directionalLight.shadow.mapSize.width = resolution;directionalLight.shadow.mapSize.height = resolution;directionalLight.shadow.map.dispose(); // 释放旧贴图directionalLight.shadow.map = null; // 强制重新创建
}// 基于性能自动调整
let frameTime = 0;
let lastTime = performance.now();function checkPerformance() {const currentTime = performance.now();const delta = currentTime - lastTime;lastTime = currentTime;// 计算平均帧时间frameTime = frameTime * 0.9 + delta * 0.1;// 根据帧率调整阴影质量if (frameTime > 18) { // 低于55FPSadjustShadowResolution('medium');} else if (frameTime > 25) { // 低于40FPSadjustShadowResolution('low');} else if (frameTime < 12) { // 高于83FPSadjustShadowResolution('high');}
}
4.2 基于距离的阴影优化
// 距离相关的阴影质量调整
function updateShadowQualityByDistance() {// 计算相机到场景中心的距离const sceneCenter = new THREE.Vector3();sceneCenter.setFromMatrixPosition(scene.matrixWorld);const distance = camera.position.distanceTo(sceneCenter);// 根据距离调整阴影参数if (distance > 50) {// 远距离:降低质量directionalLight.shadow.mapSize.width = 1024;directionalLight.shadow.mapSize.height = 1024;directionalLight.shadow.radius = 1;} else if (distance > 20) {// 中距离:中等质量directionalLight.shadow.mapSize.width = 2048;directionalLight.shadow.mapSize.height = 2048;directionalLight.shadow.radius = 2;} else {// 近距离:高质量directionalLight.shadow.mapSize.width = 4096;directionalLight.shadow.mapSize.height = 4096;directionalLight.shadow.radius = 3;}
}
4.3 可见性检测与裁剪
// 基于视锥体的阴影优化
const shadowCamera = directionalLight.shadow.camera;
const frustum = new THREE.Frustum();
const cameraViewProjectionMatrix = new THREE.Matrix4();function updateShadowCulling() {// 更新相机视锥体cameraViewProjectionMatrix.multiplyMatrices(camera.projectionMatrix,camera.matrixWorldInverse);frustum.setFromProjectionMatrix(cameraViewProjectionMatrix);// 检测场景中的对象scene.traverse((object) => {if (object.isMesh && object.castShadow) {const sphere = new THREE.Sphere();object.geometry.boundingSphere.clone().applyMatrix4(object.matrixWorld);// 如果对象不在视锥体内,不投射阴影object.visible = frustum.intersectsSphere(sphere);}});// 调整阴影相机范围以匹配视锥体const cameraFrustum = getCameraFrustumInLightSpace();shadowCamera.left = cameraFrustum.min.x;shadowCamera.right = cameraFrustum.max.x;shadowCamera.bottom = cameraFrustum.min.y;shadowCamera.top = cameraFrustum.max.y;shadowCamera.updateProjectionMatrix();
}
5. 完整案例:高质量动态阴影系统
class DynamicShadowSystem {constructor(scene, camera) {this.scene = scene;this.camera = camera;this.lights = [];this.csm = null;this.init();}init() {// 创建主方向光this.mainLight = new THREE.DirectionalLight(0xffffff, 1);this.mainLight.position.set(10, 15, 10);this.mainLight.castShadow = true;this.configureShadow(this.mainLight);this.scene.add(this.mainLight);// 创建辅助填充光this.fillLight = new THREE.DirectionalLight(0x4455aa, 0.3);this.fillLight.position.set(-5, 5, -5);this.scene.add(this.fillLight);// 初始化CSMif (this.supportsCSM()) {this.initCSM();}// 性能监控this.stats = {frameTime: 0,shadowUpdates: 0};}configureShadow(light) {light.shadow.mapSize.width = 2048;light.shadow.mapSize.height = 2048;light.shadow.camera.near = 0.5;light.shadow.camera.far = 50;light.shadow.camera.top = 20;light.shadow.camera.right = 20;light.shadow.camera.bottom = -20;light.shadow.camera.left = -20;light.shadow.radius = 2;light.shadow.bias = -0.001;}supportsCSM() {// 检测设备是否支持CSMreturn !navigator.userAgent.match(/(iPhone|iPod|iPad|Android)/) &&renderer.capabilities.maxTextures >= 8;}initCSM() {this.csm = new CSM({maxFar: 100,cascades: 4,mode: 'practical',parent: this.scene,shadowMapSize: 1024,lightDirection: this.mainLight.position.clone().normalize(),camera: this.camera});}update() {const startTime = performance.now();// 更新光源方向const time = Date.now() * 0.001;this.mainLight.position.x = Math.cos(time) * 15;this.mainLight.position.z = Math.sin(time) * 15;// 更新CSMif (this.csm) {this.csm.update(this.camera.matrix);this.csm.lightDirection = this.mainLight.position.clone().normalize();} else {// 更新传统阴影相机this.mainLight.shadow.camera.updateProjectionMatrix();this.mainLight.target.position.set(0, 0, 0);this.mainLight.target.updateMatrixWorld();}// 性能自适应this.adaptiveQuality();this.stats.shadowUpdates++;this.stats.frameTime = performance.now() - startTime;}adaptiveQuality() {// 基于性能调整阴影质量const targetFPS = 60;const targetFrameTime = 1000 / targetFPS;if (this.stats.frameTime > targetFrameTime * 1.2) {// 性能不足,降低质量this.reduceShadowQuality();} else if (this.stats.frameTime < targetFrameTime * 0.8) {// 性能充足,提高质量this.increaseShadowQuality();}}reduceShadowQuality() {if (this.csm) {this.csm.shadowMapSize = Math.max(512, this.csm.shadowMapSize / 2);} else {this.mainLight.shadow.mapSize.width = Math.max(1024, this.mainLight.shadow.mapSize.width / 2);this.mainLight.shadow.mapSize.height = Math.max(1024, this.mainLight.shadow.mapSize.height / 2);this.mainLight.shadow.radius = Math.max(1, this.mainLight.shadow.radius - 1);}}increaseShadowQuality() {if (this.csm) {this.csm.shadowMapSize = Math.min(2048, this.csm.shadowMapSize * 2);} else {this.mainLight.shadow.mapSize.width = Math.min(4096, this.mainLight.shadow.mapSize.width * 2);this.mainLight.shadow.mapSize.height = Math.min(4096, this.mainLight.shadow.mapSize.height * 2);this.mainLight.shadow.radius = Math.min(4, this.mainLight.shadow.radius + 1);}}// 为材质启用阴影支持setupMaterial(material) {if (this.csm) {this.csm.setupMaterial(material);}// 确保材质接收和投射阴影material.needsUpdate = true;}
}// 使用示例
const shadowSystem = new DynamicShadowSystem(scene, camera);function animate() {requestAnimationFrame(animate);shadowSystem.update();renderer.render(scene, camera);
}// 为场景中的材质启用阴影
scene.traverse((object) => {if (object.isMesh) {object.castShadow = true;object.receiveShadow = true;shadowSystem.setupMaterial(object.material);}
});
6. 注意事项与重点核心
6.1 阴影失真问题解决
彼得潘现象(阴影分离):
- 原因:阴影偏移过大
- 解决:减小
shadow.bias
值或使用自动偏移
directionalLight.shadow.bias = -0.001; // 轻微负值可减少彼得潘现象
directionalLight.shadow.autoUpdate = true; // 启用自动更新
阴影痤疮(表面自阴影):
- 原因:阴影偏移不足
- 解决:增加
shadow.bias
值或使用法线偏移
directionalLight.shadow.bias = 0.003;
directionalLight.shadow.normalBias = 0.04; // 沿法线方向偏移
6.2 性能监控与调试
// 阴影性能监控面板
const shadowDebug = {enabled: true,showShadowCamera: false,visualizeCascades: false
};// 阴影相机辅助器
const shadowHelper = new THREE.CameraHelper(directionalLight.shadow.camera);
shadowHelper.visible = shadowDebug.showShadowCamera;
scene.add(shadowHelper);// 级联可视化
if (csm) {csm.showCascades = shadowDebug.visualizeCascades;
}// 性能统计
const stats = new Stats();
stats.showPanel(2); // 显示FPS面板
document.body.appendChild(stats.dom);function animate() {stats.begin();// 渲染逻辑stats.end();
}
6.3 移动端优化策略
- 降低分辨率:使用512x512或1024x1024阴影贴图
- 减少PCF采样:设置
shadow.radius = 1
或使用硬阴影 - 限制级联数量:CSM最多使用2级级联
- 禁用远处阴影:根据距离动态禁用阴影投射
- 使用烘焙阴影:静态场景使用光照贴图替代实时阴影
下一节预告:3D文本渲染
第二十节:字体几何体生成与特效
核心内容:
- FontLoader字体加载与解析
- TextGeometry参数详解与自定义
- 文字描边、挤出与材质特效
- 性能优化与大文本渲染技术
重点掌握:创建各种风格的3D文字效果,从基础文字到高级特效渲染。