WebGL第三十九课:3D前置知识点之 z-value

1,149 阅读6分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

本文标题:WebGL第三十九课:3D前置知识点之 z-value

友情提示

这篇文章是WebGL课程专栏的第39篇,强烈建议从前面开始看起。因为花了大量的工夫来讲解向量的概念和矩阵运算。这些基础知识会影响你的思维。

本课代码直接跳转获取:三十九课代码(WebGL课程代码专用) - 掘金 (juejin.cn)

引子

我们知道, WebGL是用来画三角形的, 而一个三角形是由三个点组成的,我们提供的点的坐标的xy分量必须要在 [1,1][-1, 1]之内,才可以画出来,被看见。

这里思考一个关键的问题,如果画两个三角形,他们的某些区域有重合的话,那么WebGL到底显示哪一个呢?

比如说下面这个场景:

image.png

我们如何指定到底是红色三角形挡住绿色三角形,还是反过来?

默认情况下的覆盖策略

这很简单,就跟我们画画一样,后来居上,如果先画的绿色三角形,而后画的红色三角形,那么效果就如上图所示。 后画的东西会覆盖掉前面的颜色,将自己的颜色hu上去。

什么叫默认情况,这里有一个关键的配置项:

是否进行深度测试(Depth Test)

深度测试(Depth Test)

打开深度测试的代码是这样的:

gl.enable(gl.DEPTH_TEST);

打开这个测试之后,覆盖策略就再也不是后来居上了。

我们来详细解释一下这个过程:

  • 画面上的每一个像素点,其实除了颜色之外,还有一个隐藏的属性,这个属性叫 z-value或者depth-value

  • 注意,这个z-value的范围是[0,1][0, 1], 这一块留个心眼,因为可能会让你十分困惑,但是不怕,这篇文章会扫清你的困惑。总之,现在记住,z-value的范围是[0,1][0, 1]

  • 我们是可以在程序里控制每个像素点的z-value, 就如同颜色一样, 具体的下面讲代码的时候再说。

  • 也就是说,我们画绿色三角形的时候,可以指定三个点的z-value。注意,三角形中间区域的每个像素点的z-value由插值得来,这个概念在前面的文章可以找到。

  • 画完绿色三角形后,绿色三角形覆盖的像素区域,除了看见的绿色之外,z-value也被设置好了。

  • 此时,开始画红色三角形,不重叠的部分没什么可说的,但是注意,只要画,那么z-value就会被设置

  • 我们来说一说重叠部分,当画红色三角形的过程中,遇见了一个像素点,发现这个像素点的z-value已经被设置过了,这说明什么?这说明,前面已经有什么操作在这个像素点,画了东西了。

  • 此时,绘制程序就会做一个检查,将红色三角形在这个像素的z-value 与 当前这个像素已经有的z-value做一个对比。

  • 如果红色三角形在这个像素的z-value小,那么不好意思,红色会覆盖前面的绿色,并且,z-value也会覆盖原有的。

  • 反之,绘制程序认为,不应该覆盖这个像素的任何值,直接 continue,接着走下一个像素。

  • 此种检测过程,称之为深度测试 (Depth Test)

指定顶点(vertex)的 z-value

不好意思,这是个伪概念。

我们无法直接去设置一个顶点,或者像素的 z-value。

但是我们能通过一些过程去控制他的值。

这就是一个困惑的地方了。

回顾一下,vertex shader的基本写法:

    precision mediump int;
    precision mediump float;
    
    attribute vec2 a_PointVertex; // 顶点坐标
    
    void main() {
      gl_Position = vec4(a_PointVertex.x, a_PointVertex.y, 0.0, 1.0);
    }

上面的gl_Position变量的前两个分量xy, 就是最后顶点的xy坐标。也就是这个东西要在[1,1][-1, 1]之内才行。

我们想要控制这个顶点最终的z-value,其实和第三个分量z有关。(第四个分量暂时不提)

当我们输出一个gl_Position的时候,一定要注意一个基本原则(不考虑第四个分量,也就是暂时写 1.0 ):

gl_Position = vec4(x, y, z, 1.0);

这个原则就是,如果你想要这个点在屏幕区域内能展示出来,那么这三个分量一定是 [1,1][-1, 1]

前两个分量的实际含义已经说过很多遍,就是屏幕相对坐标的xy

那么第三个分量z是干啥用的,结合本文提的z-value

你可能会认为,`gl_Position.z` 就是来设置`z-value`的。(❌)
                 --上面这句话是错的,请牢记。

实际情况就是,

z_value=(gl_Position.z+1)0.5z\_value = (gl\_Position.z+1) * 0.5 。 (在不考虑第四分量w的情况下)

举个例子:

如果你设置

  • gl_Position.z = -1.0, 那么最终的z-value = 0.0
  • gl_Position.z = 0.0, 那么最终的z-value = 0.5
  • gl_Position.z = 1.0, 那么最终的z-value = 1.0

数学好的小伙伴看出来了,就是把[1,1][-1, 1]的值线性变换到[0,1][0, 1]

利用 gl_FragCoord 展示z-value

我们知道 WebGL shader 程序很难像一般的程序一样,打印个什么日志啥的,可以方便的看出各个属性。

所以,一般情况下,我们想要展示shader里的某个值的出后,只能通过颜色来进行展示。

为了证明,gl_Position.z = 0.0, 那么最终的z-value = 0.5这句话是对的。

我们绘制一个矩形,这个矩形的颜色就是 (z-value, z-value, z-value, 1.0)

那么此时,按道理应该绘制一个灰色的矩形。

问题来了,绘制颜色用到的fragment shader如何拿到z-value?

简单,有一个内置变量:

gl_FragCoord

这是一个vec4类型的变量,其中第三个分量z就是我们所要的z-value

好了,fragment shader如下:

    precision mediump int;
    precision mediump float;

    void main() {
        gl_FragColor = vec4(gl_FragCoord.z,gl_FragCoord.z,gl_FragCoord.z, 1.0);
    }

就很简单,一句话输出颜色即可。

整体代码请翻到本文最前,有一个链接获取。

这里给出运行结果:

image.png

确实一个灰色的矩形。

那还是不能证明,这个z-value就是0.5啊。

祭出网页颜色调试利器之取色笔:

image.png

用取色笔取出的结果是 #808080,

这啥意思,对应到 WebGL 的取值范围,不就是三个数(0.5, 0.5, 0.5)!

总结

我们提供给 gl_Position 的 xyz 一定要 [1,1][-1, 1]

前两个分量xy决定了像素点最后落在屏幕的方位。

第三个分量z决定了像素点最后的z-value

如果打开了深度测试,那么一个顶点的gl_Position.z越靠近 1-1,则越不容易被别的点覆盖。

诸位可以试试画两个矩形或者三角形,试试看。

我们后面利用这种特性,来模拟,离眼睛近的东西,会遮住离眼睛远的东西,这个物理概念。

代码注意点

本文的代码是基于系列课程已经封装好的一些代码来进行的。 大家可以自行修改使用。

function generateGrid(gl) {
    //////////////////////////
    let cube0 = new GridObject(0.3, 0.3, 0, 0, 0, 1, 0, 0);
    gridList.push(cube0);
    let cube1 = new GridObject(0.3, 0.3, 0.2, 0.2, -0.5, 0, 1, 0);
    gridList.push(cube1);

    gridList.forEach(element => {
        element.genData(gl);
    });
}

这块代码可以用来修改,方便调试。 以上,我是画了两个矩形,分别设置了

  • x = 0, y = 0, z = 0
  • x = 0.2, y = 0.2 , z = -0.5 最后的效果如下:

image.png

z 小的确实覆盖的 z 大的。




  正文结束,下面是答疑

小瓜瓜说:那么 gl_Position.w 也就是第四分量做啥的?

  • 玄之又玄,暂且不提。