如何在 WebGL 中实现阴影映射?

Sic*_*Boy 2 javascript webgl

我第一次尝试在 WebGL 场景中实现阴影,据我所知,最直接的方法是使用阴影贴图,但我找不到解释这个概念的教程,那是既不是关于 OpenGL,也不是包含一些像 Three.js 这样的库。

但是,我读过,我必须编写两对着色器。第一对着色器用于构建阴影贴图,它必须以某种方式存储在帧缓冲区对象中。然后我必须从帧缓冲区中获取存储的阴影贴图并将其传递给第二对着色器,用于绘制我的场景对象。

阴影贴图必须包含信息,光线在撞击对象之前可以从其源头传播的距离,并将此信息传递给用于绘制对象的着色器,可以指定哪些部分是由光源发出的光和哪些部分仅由环境光照亮。

理论就这么多,但我不知道如何让这个工作......

为了进行实验,我设置了一个非常简单的场景,渲染由单个点光源照亮的两个球体,如下所示:

点光源照亮的球体

两个球体都是以它们的中心[0.0, 0.0, 0.0]和半径创建的1.0

较大球体的矩阵由 平移[-2.0, 0.0, -2.0]和缩放[2.0, 2.0, 2.0]

较小球体的矩阵由 平移[2.0, 0.0, 2.0]和缩放[0.5, 0.5, 0.5]

点光源的位置是[4.0, 0.0, 4.0]

因此,较小的球体现在正好位于较大的球体和光源之间,因此在较大的球体的表面上应该有一个不被直接照亮的区域。

我用于此场景的两个着色器如下所示:

顶点着色器

  attribute vec4 aPosition;
  attribute vec3 aNormal;

  uniform mat4 uProjectionMatrix;
  uniform mat4 uModelViewMatrix;
  uniform mat3 uNormalMatrix;

  varying vec4 vPosition;
  varying vec3 vTransformedNormal;

  void main ( ) {
    vTransformedNormal = uNormalMatrix * aNormal;
    vPosition = uModelViewMatrix * aPosition;
    gl_Position = uProjectionMatrix * vPosition;
  }
Run Code Online (Sandbox Code Playgroud)

片段着色器

  precision highp float;

  uniform vec3 uLightPosition;

  varying vec4 vPosition;
  varying vec3 vTransformedNormal;

  void main ( ) {
    vec3 lightDirection = normalize(uLightPosition - vPosition.xyz);

    float diffuseLightWeighting = max(
        dot(normalize(vTransformedNormal), lightDirection), 0.0);

    vec3 lightWeighting = vec3(0.1, 0.1, 0.1) +
        vec3(0.8, 0.8, 0.8) * diffuseLightWeighting;

    gl_FragColor = vec4(vec3(1.0, 1.0, 1.0) * lightWeighting, 1.0);
  }
Run Code Online (Sandbox Code Playgroud)

所以,我现在必须做的第一件事是编写另一对着色器,因为它们不用于实际绘制某些东西,我可以省略法线的属性以及法线矩阵的统一,而且也不需要投影矩阵,对吧?

第二个顶点着色器看起来像这样:

  attribute vec4 aPosition;

  uniform mat4 uModelViewMatrix;

  varying vec4 vPosition;

  void main ( ) {
    vPosition = uModelViewMatrix * aPosition;
    gl_Position = vPosition;
  }
Run Code Online (Sandbox Code Playgroud)

但是片段着色器呢?而且,无论如何,我如何找出来自点光源的光击中物体的位置?我的意思是,通常需要在顶点位置数据传递来自所有相关对象一次,不是吗?(尽管在这种特殊情况下,只需要传入较小球体的顶点位置......)

现在,我的问题是,我该如何继续?什么必须写入片段着色器,如何保存阴影贴图以及如何使用它来计算较小球体在较大球体上的阴影?

我担心我可能要求太多,但如果有人至少能指出我正确的方向,那就太好了。

Wac*_*per 5

http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping/

基本上,正如您所发现的,阴影贴图是通过 2 遍完成的。第一遍从光源角度写入/渲染深度纹理,然后在第二遍中使用它来确定像素是否处于阴影中。这个想法基本上是,如果像素比阴影贴图深度/距离更远离光,那么这意味着场景包含另一个更接近光/阻挡光的对象。AKA,当前像素在阴影中。

第一遍着色器基本上是一个标准的顶点着色器,带有一个渲染到深度纹理的空片段着色器。深度纹理目前是一个非常受支持的扩展。

在第二遍中,您需要设置某种方式来进行上述深度比较。然后基本上你会根据深度比较的结果计算一个阴影值,通常是 1 或 0,并在光照计算中使用它。例如,阴影值 1 会阻止漫射光和镜面反射光的贡献。

至于如何更改渲染输出目的地,则需要使用 FrameBuffer Objects。

完整的设置可能如下所示:

// HANDLE VBOS

fbo_shadow.setAsDrawTarget();
gl.clear(gl.DEPTH_BUFFER_BIT);

shadow_depth_shader.use();
shadow_depth.setUnif("u_viewProjection", lightMatrix);

// DRAW EVERYTHING

fbo_lightPass.setAsDrawTarget();
gl.clear(gl.COLOR_BUFFER_BIT );

lighting_pass_shader.use();
lighting_pass_shader.setUnif("u_shadowMap", fbo_shadow.depthTexture);
lighting_pass_shader.setUnif("u_viewProjection", camera.getVPMatrix());
// other unifs ...

// DRAW EVERYTHING AGAIN
Run Code Online (Sandbox Code Playgroud)

编辑:为片段着色器添加了一个示例 shadowCalculation 函数:

float shadowCalculation(vec4 fragPosLightSpace, sampler2D u_shadowMap, float bias){
   // perform perspective divide and map to [0,1] range
   vec3 projCoords = fragPosLightSpace.xyz/fragPosLightSpace.w;
   projCoords = projCoords * 0.5 + 0.5;
   float shadowDepth = texture2D(u_shadowMap, projCoords.xy).r;
   float depth = projCoords.z;
   float shadow = step(depth-bias,shadowDepth);
   return shadow;
}
Run Code Online (Sandbox Code Playgroud)