目录
使用灯光后,场景中就会产生阴影。物体的背面确实在黑暗中,这称为核心阴影(core shadow)。我们缺少的是落下的阴影(drop shadow),即对象在其他对象上创建阴影。本文主要探索是落下的阴影(drop shadow)的相关内容。
支持阴影的光源类型
前面的文章我们有介绍过,只有部分光源支持阴影:
- 平行光(
DirectionalLight
) - 点光源(
PointLight
) - 聚光灯(
SpotLight
)
它们对应的阴影在three.js中也有对应的实现类:
- 平行光阴影(
DirectionalLightShadow
) - 点光源阴影(
PointLightShadow
) - 聚光灯阴影(
SpotLightShadow
)
阴影的代码实现
在入门系列中有介绍阴影的基本代码实现,这里再简单叙述一遍。主要分为四个步骤:
- 告诉渲染器显示阴影贴图;
renderer.shadowMap.enabled = true;
- 对需要投影的物体设置
castShadow
为true
;sphere.castShadow = true;
- 对需要接收投影的物体设置
receiveShadow
为true
;plane.receiveShadow = true;
- 最后,对可以产生阴影的光源设置
castShadow
为true
;light.castShadow = true;
至此,我们应该就可以在接收投影的物体上看到阴影了!
阴影的实现原理
每次渲染时,three.js 将为每个支持阴影的光源都会做一次渲染。
- 光源具有一个相机,以这个相机为视角进行渲染;
- 光源的渲染结果被作为纹理存储起来,也就是 阴影贴图(shadow map);
- 阴影贴图将被用于所有需要接受阴影的材质(material),并投影到几何体(geometry)上。
有些材质不接受光照,所以材质也会影响阴影的显示。
阴影的优化和调整
阴影贴图大小
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
- 阴影贴图越大约清晰,但是越消耗资源;
- 512和1024大小对比图,就可以看出来对比效果是1024更清晰(运行效果更明显)。
相机设置
幅度
我们先来看一个阴影不完整的现象:
正交相机OrthographicCamera
平行光源用的是正交相机进行渲染:
export class DirectionalLight extends Light<DirectionalLightShadow> {
shadow: DirectionalLightShadow;
}
export class DirectionalLightShadow extends LightShadow<OrthographicCamera> {
camera: OrthographicCamera;
}
- 用相机辅助线看一下光线的阴影的相机视角:
const cameraHelper = new THREE.CameraHelper(directionalLight.shadow.camera);
scene.add(cameraHelper);
球体超过了阴影的相机视角的上边缘。
- 扩大相机的上边缘
directionalLight.shadow.camera.top = 8; // 这个值不是固定的,视场景不同
far
和相机的渲染一致,设置相机的far
,可以控制是否显示阴影。
directionalLight.shadow.camera.far = 100;
Blur 模糊
我们可以使用 radius 属性控制阴影模糊:
directionalLight.shadow.radius = 20
阴影的边缘会有模糊的效果。
阴影的类型
在Three.js
中,ShadowMapType
枚举定义了几种阴影映射的类型:
BasicShadowMap
:基本阴影映射类型,使用简单的阴影投影算法生成阴影。这是默认的阴影映射类型。
性能优秀但是质量不好(默认)
PCFShadowMap
:使用Percentage-Closer Filtering (PCF)
技术生成阴影,以获得更平滑的阴影边缘效果。
性能稍差但是拥有光滑的边缘
PCFSoftShadowMap
:使用软阴影技术生成阴影,通过多次采样和模糊处理来产生更柔和的阴影效果。
性能稍差但是拥有更 soft 的边缘
VSMShadowMap
:使用Variance Shadow Mapping (VSM)
技术生成阴影,以获得更高质量的阴影效果和更柔和的阴影边缘。
性能稍差,更多限制,有着意想不到的效果
代码实现:
renderer.shadowMap.type = THREE.BasicShadowMap;
我项目中使用,倒是看不出来有啥大的区别。