WebGL-工作原理


此文上接WebGL 基础概念。 在继续学习之前,我们需要探讨一下WebGL在GPU上究竟做了什么。 WebGL在GPU上的工作基本上分为两部分,第一部分是将顶点(或数据流)转换到裁剪空间坐标, 第二部分是基于第一部分的结果绘制像素点。

  当你调用:

  这里的9表示“处理9个顶点”,所以将会有9个顶点被转换。

顶点示意图

  左侧是你提供的数据。顶点着色器(Vertex Shader)是你写进GLSL 中的一个方法,每个顶点调用一次,在这个方法中做一些数学运算后设置了一个特殊的gl_Position变量, 这个变量就是该顶点转换到裁剪空间中的坐标值,GPU接收该值并将其保存起来。

  假设你正在画三角形,顶点着色器每完成三次顶点处理,WebGL就会用这三个顶点画一个三角形。 它计算出这三个顶点对应的像素后,就会光栅化这个三角形,“光栅化”其实就是“用像素画出来” 的花哨叫法。对于每一个像素,它会调用你的片断着色器询问你使用什么颜色。 你通过给片断着色器的一个特殊变量gl_FragColor设置一个颜色值,实现自定义像素颜色。

  使用它们可以做出非常有趣的东西,但如你所见,到目前为止的例子中, 处理每个像素时片断着色器可用信息很少,幸运的是我们可以给它传递更多信息。 想要从顶点着色器传值到片断着色器,我们可以定义“可变量(varyings)”。

  一个简单的例子,将顶点着色器计算出的裁剪空间坐标从顶点着色器传递到片断着色器。

  我们来画一个简单的三角形,从之前的例子继续,让我们把矩形改成三角形。

我们只需要画三个顶点:

然后在我们的顶点着色器中定义一个varying(可变量)用来给片断着色器传值。

在片断着色器中定义同名varying变量:

WebGL会将同名的可变量从顶点着色器输入到片断着色器中。

运行下面的代码:

当你移动,缩放,旋转三角形时,发现颜色随位置变化,不跟着三角形移动。

回想一下,我们只计算了三个顶点,调用了三次顶点着色器,所以也只计算出了三个颜色值, 但是我们的三角形却有很多颜色,这就是称之为可变量的varying的原因啦!

WebGL先获得顶点着色器中计算的三个颜色值,在光栅化三角形时将会根据这三个值进行插值。 每一个像素在调用片断着色器时,可变量的值是与之对应的插值。

让我们从上例的三个顶点开始分析

我们的给顶点着色器施加了一个包含平移,旋转和缩放的的矩阵,并将结果转换到裁剪空间。 默认平移,旋转和缩放值为:平移 = 200, 150,旋转 = 0,缩放 = 1,所以这里只进行了平移。 画布大小(背景缓冲)为 400×300,所以三个顶点在裁剪空间中为以下坐标值。

同时将这些值转换到颜色空间中赋给我们定义的可变量v_color。

利用这三个值进行插值后传进每个像素运行的片断着色器中。

想要给片断着色器传值,我们可以先把值传递给顶点着色器然后再传给片断着色器。 让我们来画一个由两个不同颜色三角形组成的矩形。我们需要给顶点着色器添加一个属性值, 把值通过属性传递给它后它再直接传递给片断着色器。

现在要给WebGL提供要用的颜色。

在渲染的时候设置颜色属性:

调整顶点的数量为6用来画两个三角形:

运行下面的代码:

你可能注意到这两个三角形是纯色的。我们传递给每个三角形的顶点的颜色值是相同的, 所以我们传递的varying会被插值成相同的颜色,如果我们传递不同的颜色,就会看到插值的颜色。

运行下面的代码:

可能不值一提的是上例还演示了使用多个属性并且通过顶点着色器向片断着色器传值。 如果你看了处理图片的例子, 那里面还用了另外一个属性传递纹理坐标。

关于buffer和attribute的代码

缓冲操作是在GPU上获取顶点和其他顶点数据的一种方式。 gl.createBuffer创建一个缓冲;gl.bindBuffer是设置缓冲为当前使用缓冲; gl.bufferData将数据拷贝到缓冲,这个操作一般在初始化完成。

一旦数据存到缓冲中,还需要告诉WebGL怎么从缓冲中提取数据传给顶点着色器的属性。

要做这些,首先需要获取WebGL给属性分配的地址,如下方代码所示:

这一步一般也是在初始化部分完成。

一旦知道了属性的地址,在绘制前还需要发出三个命令。

这个命令是告诉WebGL我们想从缓冲中提供数据。

这个命令是将缓冲绑定到 ARRAY_BUFFER 绑定点,它是WebGL内部的一个全局变量。

这个命令告诉WebGL从 ARRAY_BUFFER 绑定点当前绑定的缓冲获取数据。 每个顶点有几个单位的数据(1 – 4),单位数据类型是什么(BYTE, FLOAT, INT, UNSIGNED_SHORT, 等等…), stride 是从一个数据到下一个数据要跳过多少位,最后是数据在缓冲的什么位置。

单位个数永远是 1 到 4 之间。

如果每个类型的数据都用一个缓冲存储,stride 和 offset 都是 0 。 对 stride 来说 0 表示 “用符合单位类型和单位个数的大小”。 对 offset 来说 0 表示从缓冲起始位置开始读取。 它们使用 0 以外的值时会复杂得多,虽然这样会取得一些性能能上的优势, 但是一般情况下并不值得,除非你想充分压榨WebGL的性能。

希望这些关于缓冲和属性的内容对你来说讲的足够清楚。


vertexAttribPointer 中的 normalizeFlag 参数是什么意思?

标准化标记(normalizeFlag)适用于所有非浮点型数据。如果传递false就解读原数据类型。 BYTE 类型的范围是从 -128 到 127,UNSIGNED_BYTE 类型的范围是从 0 到 255, SHORT 类型的范围是从 -32768 到 32767,等等…

如果标准化标记设为true,BYTE 数据的值(-128 to 127)将会转换到 -1.0 到 +1.0 之间, UNSIGNED_BYTE (0 to 255) 变为 0.0 到 +1.0 之间,SHORT 也是转换到 -1.0 到 +1.0 之间, 但比 BYTE 精确度高。

最常用的是标准化颜色数据。大多数情况颜色值范围为 0.0 到 +1.0。 使用4个浮点型数据存储红,绿,蓝和阿尔法通道数据时,每个顶点的颜色将会占用16字节空间, 如果你有复杂的几何体将会占用很多内存。代替的做法是将颜色数据转换为四个 UNSIGNED_BYTE , 其中 0 表示 0.0,255 表示 1.0。现在每个顶点只需要四个字节存储颜色值,省了 75% 空间。

我们来修改之前代码实现。当我们告诉WebGL如何获取颜色数据时将这样

如下向缓冲添加数据:

运行结果: