如何在WebGL中实现阴影贴图?

时间:2015-10-15 22:31:25

标签: javascript webgl

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

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

阴影贴图必须包含信息,灯光在撞击物体之前可以从其源头传播的距离,并且将此信息传递给用于绘制物体的着色器,可以指定,哪些部件由来自光源的光照亮,哪些部件仅由环境光照亮。

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

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

spheres lit by point light

这两个球体的中心位于[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;
  }

片段着色器

  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);
  }

所以,我现在要做的第一件事就是写另一对着色器,因为它们不习惯实际绘制某些东西,我可以省略法线的属性以及正常矩阵,也不需要投影矩阵,对吧?

第二个顶点着色器可能看起来像那样:

  attribute vec4 aPosition;

  uniform mat4 uModelViewMatrix;

  varying vec4 vPosition;

  void main ( ) {
    vPosition = uModelViewMatrix * aPosition;
    gl_Position = vPosition;
  }

但片段着色器怎么样?而且,无论如何,我如何找到来自点光源的光线撞击物体的位置?我的意思是,通常需要立即从所有相关对象传递顶点位置数据,不是吗? (虽然在这种特殊情况下,只需要传递较小球体的顶点位置......)

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

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

2 个答案:

答案 0 :(得分:4)

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

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

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

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

至于如何更改渲染输出目标,您需要使用FrameBuffer对象。

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

// 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

编辑:为片段着色器添加了一个样本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;
}

答案 1 :(得分:0)

以下代码是一种奇怪的类型,但非常有帮助。

<html>
<body>
    <canvas id="myCanvas" width="600" height="400">
    </canvas>
    <script src="https://www.drawing3d.de/bridge.js"></script>
    <script src="https://www.drawing3d.de/WebDrawing3d.js"></script>
    <script src="https://www.drawing3d.de/Classes.js"></script>
    <script>
        var canvas = document.getElementById("myCanvas");
        var WebDevice = new Device(canvas);
        WebDevice.Shadow = true;
        WebDevice.Paint = draw;
        WebDevice.Refresh();
        function draw() {
        WebDevice.drawBox(new xyz(-10,-10,-1),new xyz(20,20,1));
        WebDevice.drawSphere(new xyz(0, 0, 5), 5);
        }
    </script>
</body>
</html>