深入浅出 WebGL 阴影渲染
2024-02-01 23:11:14
在图形渲染中,阴影是提升场景真实感的重要因素之一。没有阴影,物体就像漂浮在空中,缺乏真实世界的立体感和深度感。WebGL 提供了强大的阴影渲染能力,其中阴影贴图(Shadow Mapping)是一种常用的技术,它能够高效地生成逼真的阴影效果。
阴影贴图的核心思想是从光源的角度去看待场景,并将场景深度信息存储到一张纹理中,我们称之为深度贴图(Depth Map)或者阴影贴图。这张纹理就像光源的眼睛,记录了每个像素点到光源的距离。当我们从摄像机的角度渲染场景时,可以通过查询这张深度贴图来判断一个像素点是否被光照射到,从而决定它是否处于阴影中。
创建深度贴图
要生成深度贴图,我们需要先创建一个帧缓冲区对象(Framebuffer Object,简称 FBO)。FBO 可以看作是一个屏幕外的渲染目标,我们可以将场景渲染到 FBO 中,而不是直接渲染到屏幕上。
FBO 通常包含一个颜色附件和一个深度附件。颜色附件存储颜色信息,深度附件存储深度信息。对于阴影贴图来说,我们只需要深度信息,所以可以将颜色附件设置为 null。
创建 FBO 后,我们需要将一个纹理绑定到它的深度附件上。这个纹理就是我们的深度贴图。深度贴图的尺寸通常是 2 的幂次方,例如 1024x1024 或者 2048x2048。
从光源视角渲染场景
创建好深度贴图后,我们需要从光源的角度渲染场景。这意味着我们需要将摄像机的位置和方向设置为光源的位置和方向,并将投影矩阵设置为光源的投影矩阵。
光源的投影矩阵通常是一个正交投影矩阵或者透视投影矩阵。正交投影矩阵适用于平行光,透视投影矩阵适用于点光源和聚光灯。
渲染场景时,我们只需要渲染物体的深度信息,不需要渲染颜色信息。我们可以通过设置 WebGL 的渲染状态来禁用颜色渲染,只启用深度渲染。
在场景中使用深度贴图
从光源视角渲染完场景后,我们就得到了深度贴图。接下来,我们需要在渲染场景时使用这张深度贴图来判断像素点是否处于阴影中。
具体来说,我们需要将深度贴图绑定到一个纹理单元上,并在着色器中访问它。对于每个像素点,我们需要计算它到光源的距离,并将这个距离与深度贴图中对应像素点的深度值进行比较。
如果像素点到光源的距离大于深度贴图中对应像素点的深度值,说明这个像素点被其他物体遮挡了,它应该处于阴影中。否则,这个像素点应该被光照射到。
代码示例
以下是一个简单的阴影贴图实现示例:
// 创建 FBO
const fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
// 创建深度贴图
const depthTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, depthTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT16, 1024, 1024, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// 将深度贴图绑定到 FBO
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture, 0);
// 从光源视角渲染场景
gl.viewport(0, 0, 1024, 1024);
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.clear(gl.DEPTH_BUFFER_BIT);
// ...渲染场景...
// 在场景中使用深度贴图
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// ...绑定深度贴图到纹理单元...
// ...在着色器中访问深度贴图...
// ...渲染场景...
常见问题及解答
- 为什么深度贴图会出现锯齿?
深度贴图的锯齿是由深度值的精度不足导致的。深度值通常使用 16 位或者 24 位浮点数表示,这会导致深度值的精度有限。为了减少锯齿,我们可以使用一些抗锯齿技术,例如 PCF(Percentage Closer Filtering)或者 ESM(Exponential Shadow Mapping)。
- 为什么阴影会出现自阴影问题?
自阴影问题是指物体自身的阴影投射到自身身上,导致物体表面出现一些不正确的阴影。自阴影问题通常是由深度值的精度不足导致的。为了解决自阴影问题,我们可以使用一些偏移技术,例如深度偏移或者法线偏移。
- 为什么阴影会出现漏光问题?
漏光问题是指阴影边缘出现一些不应该被光照射到的区域。漏光问题通常是由深度贴图的精度不足或者光源的投影矩阵设置不正确导致的。为了解决漏光问题,我们可以使用一些过滤技术,例如 PCF 或者 ESM。
- 为什么阴影会出现摩尔纹?
摩尔纹是指阴影中出现一些不规则的条纹。摩尔纹通常是由深度贴图的采样频率与场景的几何形状不匹配导致的。为了解决摩尔纹,我们可以使用一些抖动技术,例如随机抖动或者泊松盘采样。
- 为什么阴影的性能比较低?
阴影渲染需要额外的渲染步骤,这会导致性能下降。为了提高阴影的性能,我们可以使用一些优化技术,例如级联阴影贴图(CSM)或者延迟渲染。