怎么实现阴影?
- 实时渲染
- 用贴图实现
1. 实时渲染
demo在 ThreeJs 中,有三种光能引起阴影,分别是 DirectionalLight、SpotLight 和 PointLight。
设置步骤
- 对 renderer:renderer.shadowMap.enabled = true。
- 对光源:directionalLight.castShadow = true。
- 对要消费阴影的 mesh:plane.receiveShadow = true。
- 对要生产阴影的 mesh:sphere.castShadow = true。
调整阴影效果
设置 directionalLight.shadow.mapSize.width
和 directionalLight.shadow.mapSize.height
可以控制阴影贴图的分辨率。这两个属性决定了阴影贴图的宽度和高度,进而影响阴影的精细程度。
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
意义
更高的分辨率能带来更精细的阴影细节,而较低的分辨率会使阴影显得粗糙,可能出现明显锯齿边缘。
性能
较高的分辨率会增加计算阴影的成本,因为更多像素需要被渲染到阴影贴图中。相反,较低的分辨率可减少计算成本,提高渲染速度。
质量与性能的权衡
在实际应用中,需平衡阴影质量和性能。通常,1024x1024 是一个较为适中的分辨率,既能保证较好的阴影质量,又不会过分影响性能。
注意事项
- 内存消耗:较高的分辨率会增加内存消耗,因为需要存储更多像素数据。
- 渲染时间:较高的分辨率在有多个光源的情况下会增加渲染时间。
裁剪阴影范围
directionalLight.shadow.camera
代表 用于生成阴影的虚拟摄像机。在 Three.js 中,每个光源都有一个与其关联的摄像机,从光源角度观察场景并渲染深度图(即阴影贴图),以确定场景中哪些部分位于阴影中。
设置 directionalLight.shadow.camera.top
、directionalLight.shadow.camera.right
、directionalLight.shadow.camera.bottom
和 directionalLight.shadow.camera.left
这些属性,可优化阴影效果和性能。具体解释如下:
这些属性定义了阴影摄像机的视锥体边界,即阴影贴图的可见范围。通过适当设置这些值,可确保阴影贴图只包含场景中实际需要的部分,减少不必要的渲染工作。
提高阴影质量
若边界设置得太宽,可能导致阴影贴图中的物体边缘模糊或出现伪影。精确设置这些边界,能确保阴影贴图中的物体边界清晰准确。
性能提升
减少阴影贴图的渲染范围意味着减少需要处理的像素数量,从而提高渲染效率,这对性能敏感的应用(如移动设备或低端硬件上的应用)尤为重要。
示例代码:
directionalLight.shadow.camera.top = 2;
directionalLight.shadow.camera.right = 2;
directionalLight.shadow.camera.bottom = -2;
directionalLight.shadow.camera.left = -2;
这段代码将阴影摄像机的视锥体边界设置为一个正方形区域,中心位于原点,边长为 4 单位,意味着阴影贴图将仅覆盖这个区域内的物体。
如何优化
- 观察场景:
使用
THREE.CameraHelper
来可视化阴影摄像机的视锥体,了解哪些部分被包括在内,再根据场景中物体的实际分布调整边界。
const directionalLightCameraHelper = new THREE.CameraHelper(directionalLight.shadow.camera);
scene.add(directionalLight);
scene.add(directionalLightCameraHelper);
- 动态调整: 若场景中的物体位置会变化,可编写逻辑动态调整边界,以始终覆盖物体。例如,根据场景中最远和最近的物体位置计算合适的边界值。
性能考量
考虑性能因素时,可适当放宽边界以减少伪影发生,但也要确保不会过度渲染不必要的区域。
注意事项
- 过度裁剪:裁剪过于严格可能导致阴影贴图中缺失某些物体,因此裁剪时需留有一定余地。
- 动态场景:若场景中的物体位置不断变化,可能需要定期更新边界值,以确保阴影始终正确。
关于 shadow 的 radius
directionalLight.shadow.radius
属性可以用来调整阴影的模糊程度。当这个值增大时,阴影会变得更加模糊,给人一种柔和的效果;当这个值减小时,阴影会变得更加清晰锐利。
意义
通过调整 radius,可以实现不同风格的阴影效果,满足不同场景的需求。例如,在一些需要营造柔和氛围的场景中,可以增大 radius 使阴影变得模糊;而在需要强调物体轮廓的场景中,可以减小 radius 使阴影更加清晰。
性能
较大的 radius 值会增加计算阴影的复杂度,因为需要进行更多的模糊处理。这可能会导致性能下降,特别是在处理大量物体或复杂场景时。
注意事项
在调整 radius 时,需要注意与其他阴影属性的平衡。例如,如果同时设置了较高的阴影分辨率和较大的 radius,可能会导致性能问题。此外,不同的渲染器对 radius 的支持程度可能有所不同,需要根据实际情况进行测试和调整。
通过合理设置这些边界值和 radius,可以有效优化阴影效果,同时保持良好的性能表现。
2. 用贴图实现渲染
demo静止的 mesh
以一个球体和一个平面为例,如果球体处于静止状态,那么可以直接在平面上绘制阴影。
const textureLoader = new THREE.TextureLoader()
const bakedShadow = textureLoader.load('static/textures/bakedShadow.jpg')
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(5, 5),
new THREE.MeshBasicMaterial({ map: bakedShadow })
)
bakedShadow.jpg

运动的 mesh
如果球体是运动的,可以创建一个平面 mesh 来作为阴影,并与球体进行同步运动,可参考上述 demo。
const simpleShadowTexture = textureLoader.load('static/textures/simpleShadow.jpg')
const sphereShadow = new THREE.Mesh(
new THREE.PlaneGeometry(1.5, 1.5),
new THREE.MeshBasicMaterial({
color: 0x000000,
transparent: true,
alphaMap: simpleShadowTexture
})
)
z-fighting
注意作为阴影的平面 mesh,position.z 要比所处的平面 mesh 设置高一点
sphereShadow.position.z = plane.position.z + 0.01;
simpleShadow.jpg
对于 alphaMap 而言,白色区域为透明,黑色区域则是可见的。
