我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情
本文标题:WebGL第三十九课:3D前置知识点之 z-value
友情提示
这篇文章是WebGL课程专栏的第39篇,强烈建议从前面开始看起。因为花了大量的工夫来讲解向量的概念和矩阵运算。这些基础知识会影响你的思维。
本课代码直接跳转获取:三十九课代码(WebGL课程代码专用) – ()
引子
我们知道, WebGL
是用来画三角形的, 而一个三角形是由三个点组成的,我们提供的点的坐标的xy
分量必须要在
[−1,1][-1, 1]之内,才可以画出来,被看见。
这里思考一个关键的问题,如果画两个三角形,他们的某些区域有重合的话,那么WebGL
到底显示哪一个呢?
比如说下面这个场景:
我们如何指定到底是红色三角形
挡住绿色三角形
,还是反过来?
默认情况下的覆盖策略
这很简单,就跟我们画画一样,后来居上,如果先画的绿色三角形
,而后画的红色三角形
,那么效果就如上图所示。
后画的东西会覆盖掉前面的颜色,将自己的颜色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);
}
就很简单,一句话输出颜色即可。
整体代码请翻到本文最前,有一个链接获取。
这里给出运行结果:
确实一个灰色的矩形。
那还是不能证明,这个z-value
就是0.5啊。
祭出网页颜色调试利器之取色笔:
用取色笔取出的结果是 #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 最后的效果如下:
z 小的确实覆盖的 z 大的。
正文结束,下面是答疑
小瓜瓜说:那么 gl_Position.w 也就是第四分量做啥的?
- 玄之又玄,暂且不提。