1、实现阴影的原理:
原理:根据光源与物体之间的距离(也就是物体在光源坐标系下的深度z值),来决定物体是否可见。如下图,同一条光线上有两个点P1、P2,由于P2的z值大于P1,所以P2在阴影中。
2、如何实现❓
需要使用两对着色器:(1)一对着色器用来计算光源到物体的距离;(2)另一对根据(1)中计算出的距离绘制场景。
(2)如何使用(1)中的距离❓
使用一张纹理图像把(1)的结果传入(2)中,这张纹理图像称为 <阴影贴图>。
通过阴影贴图实现阴影的方法称为 <阴影映射>。
阴影映射的过程:
1⃣️ 将视点移到光源的位置处,并运行(1)中的着色器。这时,那些‘将要被绘出’的片元都是被光照射到的,即落在这个像素上的各个片元中最前面的。并不实际绘制出片元的颜色,而是将片元的z值写入到阴影贴图中。
假设P1点z值为1,P2点z值为2,阴影贴图中数据大概如下:
2⃣️ 将视点移回原来的位置,运行(2)中的着色器绘制场景。此时,计算出每个片元在光源坐标系下的坐标,并与阴影贴图中记录的z值比较,如果前者大于后者,就说明当前片元处在阴影之中,用较深暗的颜色绘制。
3、具体实现思路:
(1)、投影矩阵:
Matrix4.setPerspective(fov, aspect, near, far)
// fov - 垂直视角,即可视空间顶面和底面间的夹角,必须大于0.
// aspect - 指定近裁剪面的宽高比,应当与canvas 保持一致
// near,far - 近、远裁剪面的位置。
⚠️ 光源的投影矩阵:aspect 需要使用生成的阴影贴图的分辨率,eg:256*256、1024*1024
⚠️ 正常绘制时的投影矩阵:aspect 使用canvas的分辨率
(2)、生成阴影贴图:
1⃣️ 使用 <光源处> 的视图投影矩阵,绘制模型(模型可能存在偏移、旋转等);
2⃣️ 保存此时的 <光源下的模型视图投影矩阵> ;
3⃣️ 将光源视点下的每个顶点的深度值存入到阴影贴图,保存在帧缓冲区中。
gl_FragCoord 的内置变量是vec4类型的,用来表示片元的坐标。
gl_FragCoord.x 和 gl_FragCoord.y 是片元在屏幕上的坐标,gl_FragCoord.z 是深度值。
它们是通过 (gl_Position.xyz / gl_Position.w)/2.0+0.5 计算的,并被归一化到[0.0, 1.0]区间。
(3)、正常绘制模型:
1⃣️ 使用 <视点处> 的视图投影矩阵,绘制模型(模型可能存在偏移、旋转等);
2⃣️ 根据 <光源下的模型视图投影矩阵> 计算模型在光源坐标系下的顶点位置,顶点的位置需要进行归一化;
WebGL 中的x和y坐标都是在[-1.0, 1.0]区间中的,纹理坐标 s和t是在[0.0, 1.0]中的,所以需要将 模型在光源坐标系下的顶点的x和y坐标 转化为 s和t坐标:
vec3 shadowCoord = (v_PositionFromLight.xyz / v_PositionFromLight.w)/2.0+0.5;
shadowCoord.x 和 shadowCoord.y ,为当前片元在阴影贴图中对应纹素的纹理坐标,shadowCoord.z 为当前片元在光源坐标系中的归一化的z值。
3⃣️ 根据shadowCoord.x 和 shadowCoord.y,去获取到阴影贴图对应点纹素的深度值;
4⃣️ shadowCoord.z > 阴影贴图对应点纹素的深度值,visibility 为0.5,即此位置的片元为 <阴影部分>;
shadowCoord.z < 阴影贴图对应点纹素的深度值,visibility 为1.0,即此位置的片元为 <非阴影部分>;
代码实现可参考:WebGL实现阴影效果