传送门:
前言
接上一节的内容,上一节我们讲述了使用WebGL如何绘制点和三角形,我们通过WebGLBuffer
往WebGL中传入顶点数据。我们留下两个问题:
gl.vertexAttribPointer
的参数中stride
和offset
这两个参数到底是干什么的?- 一段程序中有多个
WebGLBuffer
该如何处理呢?
今天我们就通过实战来化解掉这两个疑问。
实战——绘制颜色渐变三角形
之前我们绘制了纯色的三角形,现在我们来绘制一个渐变色的三角形,我们把三个顶点的颜色分别设置为红、绿、蓝。我们期望得到如下的效果:
准备数据
我们将之前的顶点数据改为:
const pointPos = [
-0.5, 0.0, 1.0, 0.0, 0.0, 1.0,
0.5, 0.0, 0.0, 1.0, 0.0, 1.0,
0.0, 0.5, 0.0, 0.0, 1.0, 1.0,
];
每一行的前两位表示顶点的位置,后面的四位数字组成了RGBA值,也就是该顶点的颜色。由于这里我们将顶点的位置信息和颜色信息混合在了一起,所以我们还需要告诉WebGL如何去读取我们的数据。之前我们使用的gl.vertexAttribPointer
这个API正是用来告诉WebGL如何读取数据的。
我们需要修改以下的代码
const a_position = gl.getAttribLocation(program, "a_position");
const a_color = gl.getAttribLocation(program, "a_color");
gl.vertexAttribPointer(
a_position,
2,
gl.FLOAT,
false,
Float32Array.BYTES_PER_ELEMENT * 6,
0
);
gl.vertexAttribPointer(
a_color,
4,
gl.FLOAT,
false,
Float32Array.BYTES_PER_ELEMENT * 6,
Float32Array.BYTES_PER_ELEMENT * 2
);
gl.enableVertexAttribArray(a_position);
gl.enableVertexAttribArray(a_color);
我们注意到我们修改了gl.vertexAttribPointer
的最后两个参数,回忆一下这个API的函数签名:
参数名 | 含义 |
---|---|
index | 指定要修改的顶点属性 |
size | 每个顶点属性的组成数量,换言之就是几个数据组成了一个顶点属性 |
type | 数据的类型 |
normalized | 是否进行归一化处理 |
stride | 顶点之间的偏移量 |
offset | 顶点属性数组中一部分字节的偏移量 |
-
stride:顶点之间的偏移量,通俗地讲,就是只每个顶点占据的大小。这里一个顶点使用了6个Float32类型的数据表示,所以每个顶点占据的空间大小则为:
Float32Array.BYTES_PER_ELEMENT * 6
。 -
offset:顶点属性数组中一部分字节的偏移量,这里我们以上面的代码为例,顶点的位置信息占据了两个数字,位置信息占据的大小为
Float32Array.BYTES_PER_ELEMENT * 2
,位置信息出现在每行的第一个,所以偏移量为0。而顶点的颜色信息占据了4个数字,在每一行的颜色信息之前,有占据Float32Array.BYTES_PER_ELEMENT * 2
大小的顶点位置信息,所以offset为Float32Array.BYTES_PER_ELEMENT * 2
修改Shader
至此,数据已经准备完成,我们还需要修改shader中的代码:
const vertexShader = `
attribute vec4 a_position;
// 新增一个a_color的 attribute 变量
attribute vec4 a_color;
// 新增一个v_color的 varying 变量
varying vec4 v_color;
void main () {
gl_Position = a_position;
v_color = a_color;
}
`;
const fragmentShader = `
precision mediump float;
varying vec4 v_color;
void main () {
gl_FragColor = v_color;
}
`;
这里需要解释的是a_color与v_color这两个变量。
a_color这个应该很好理解了,与a_position一样,它可以接受从外部传入的数据。那么,v_color又是干什么的呢?注意v_color前面的存储限定符varying
,它表示一个变化的量。
我们回忆一下渲染管线的流程: 从顶点着色器到片元着色器中间需要经过一步光栅化的处理,这一步其实就是根据已有的顶点信息,对拼装好的图元中的每一个片元(像素)进行插值,从而可以得到每一个片元的基本信息。
这里v_color = a_color
就是在告诉WebGL,我需要a_color
这个变量进行插值处理。在片元着色器中必须定义一个相同名称的变量,这样在片元着色器中就可以直接使用插值后的值了。
至此,我们的代码已经修改完毕。你已经能够得到正确的运行结果了。
如何分离颜色和位置信息
如果你觉得上面这种往顶点中传递数据的方式令你感到困惑,那么你也可以将顶点的位置信息和颜色信息分开来进行传递。
准备数据
这一次,我们将位置信息和颜色信息用两个数组分开表示。
const pointPos = [-0.5, 0.0, 0.5, 0.0, 0.0, 0.5];
const pointColor = [1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0];
我们还需要两个WebGLBuffer
分别存放两组数据
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(pointPos), gl.STATIC_DRAW);
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(pointColor), gl.STATIC_DRAW);
这里,在往WebGL传递数据中时,特别需要注意了。在调用vertexAttribPointer
API之前,一定要先bindBuffer
,这表示只有在当前的WebGLBuffer
中是以这种方式读取数据。
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(
a_position,
2,
gl.FLOAT,
false,
Float32Array.BYTES_PER_ELEMENT * 2,
0
);
// 这是将shader中的a_position与buffer绑定起来
gl.enableVertexAttribArray(a_position);
对于颜色数据,同理地:
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.vertexAttribPointer(
a_color,
4,
gl.FLOAT,
false,
Float32Array.BYTES_PER_ELEMENT * 4,
0
);
// 这是将shader中的a_color与colorBuffer绑定起来
gl.enableVertexAttribArray(a_color);
至此,我们完成了位置信息和颜色信息的分离了。相信你也能得到跟上面相同的结果。
总结
好了,今天我给三角形加上了丰富的色彩。我们讲述了如何传递除了顶点位置信息之外的其他信息。我们可以将其他信息和位置信息混在一起一起通过使用一个WebGLBuffer
进行传递,但是需要我们手动的计算每个顶点的大小和每组信息在每个顶点中的偏移量。但是这种效率是比较高的。如果你觉得繁琐,你也可以采用多个WebGLBuffer
进行信息的传递,缺点是不如上述的传递信息的效率高。
如果你觉得文章好的话,记得点一下赞👍哦~