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

Posted

技术标签:

【中文标题】如何在 WebGL 中实现阴影映射?【英文标题】:How to implement shadow mapping in WebGL? 【发布时间】:2016-01-14 13:34:45 【问题描述】:

我第一次尝试在 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;
  

片段着色器

  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;
  

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

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

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

【问题讨论】:

【参考方案1】:

见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;

【讨论】:

您好瓦茨瓦夫,非常感谢您的回答!好吧,如果我没听错的话,我要采取的第一步 是创建一个lightMatrix,它应该是一个mat4。所以,在第一次通过的顶点着色器中,它看起来像vPosition = lightMatrix * modelViewMatrix * aPosition,或者我是否也必须考虑到 projectionMatrix,比如vPosition = uProjectionMatrix * lightMatrix * modelViewMatrix * aPosition?要检索第二遍的 vec3,我必须使用 glMatrix vec4.transformMat4 之类的东西,将 1 添加为第四个值,对吧? 我也很难找到一些关于深度纹理的东西:MSDN 根本没有描述这个扩展,MDN 只有一个死链接。基本语法应该是depthTexture = gl.getExtension('WEBGL_depth_texture')吧?您是否有任何链接可以让我看到哪些浏览器支持此功能? caniuse.com 对于 WebGL 并没有那么有用,上面提到的网络也不是。我记得我读过一些关于将值存储为普通纹理的 RGBA 值的内容,这样可以作为后备吗? learningwebgl.com 好像下线了... ...这非常很伤心,因为如果我没记错的话,有一节课是关于如何使用 frameBuffer 对象来制作可以在第二关。我从未使用过这种技术,因此我对它的工作原理只有一些模糊的想法,现在我只是找不到解释这一点的文章或教程。但是,这对我来说还是个大问题:如果你想学习纯 WebGL,那么很难找到任何文章、文档和教程!这就像尝试学习 javascript,而您在网络上找到的所有内容都只是关于 jQuery 或 Java。 :-( 没有时间更好地回答 atm,请转到 webglfundamentals.org 获取有关 webGL 的教程。 webglstats.com webgl 可用性。 无论如何你都想帮助我真是太棒了!所以,不要着急! ;-) 我已经知道 webglfundamentals.org 并且不认为使用帧缓冲区有什么好处,但你是对的! - 在我第一次检查时忽略了关于图像处理的文章中确实有一个解释。 webglstats.com 似乎也很有用!非常感谢!【参考方案2】:

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

<html>
<body>
    <canvas id="myCanvas"  >
    </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>

【讨论】:

以上是关于如何在 WebGL 中实现阴影映射?的主要内容,如果未能解决你的问题,请参考以下文章

WebGL入门(四十二)-使用(FBO)实现阴影效果

WebGL入门(四十二)-使用(FBO)实现阴影效果

如何在 Spark UDAF 中实现 fastutils 映射?

如何在使用 Hibernate 映射的类中实现 toString()?

如何在c ++中实现哈希映射?

如何在 iOS 中实现这种风格?