玩转WebGL(四)缓冲区

2,353 阅读5分钟

1.缓冲区

缓冲区是驻存于内存中的javascript对象,存储着即将推送到着色器中的attribute对象.最常用的attribute对象莫过于记录了空间中点位置信息的aVertexPosition了.缓冲区如同一个长长的队列,着色器每处理完一个顶点(或和顶点对应的其他attribute对象),缓冲区就提供下一个顶点给着色器处理.

上节中,只绘制了一个点,这节来实现一个较为复杂的例子,用webgl来画一个正方形.可以通过4次每次画一个点,画4个点,然后将这4个点连起来就成为了一个正方形,此外如果我们要一次性的画出4个点并连成正方形,就需要使用缓冲区.

创建缓冲区的步骤:

1.创建缓冲区对象(gl.createBuffer())
vertexPositionBuffer = gl.createBuffer(); 

2.绑定缓冲区对象(gl.bindBuffer(target, buffer))
# target: 指定存储缓存区的目标类型
# [
# gl.ARRAY_BUFFER : 指缓存区中包含了顶点的数据
# gl.ELEMENT_ARRAY_BUFFER : 指缓存区中包含了顶点数据的索引值
# ]
# buffer: 自己创建的缓存区对象,Javascript中可用Float32Array类型来创建支持GLSL的数据

3.将数据写入缓冲区对象(gl.bufferData())
gl.bufferData(target, size, usage);
# target: 同上
# size: 为多个顶点坐标的集合数组
# usage: 表示程序将如何使用缓存区中的数据
# [
# gl.STATIC_DRAW : 只会向缓存区对象中写入一次数据,但需要绘制很多次, 意味着VBO(Vertex buffer object)中的数据不会被改变
# gl.STREAM_DRAW : 只会向缓存区对象中写入一次数据,然后绘制若干次,意味着数据每帧都不同
# gl.DYNAMIC_DRAW : 会想缓存区对象中多次写入数据,并绘制很多次,意味着数据可以被频繁修改
# ]

4.将缓冲区对象分配给一个attribute变量(gl.vertexAttribPointer())
gl.vertexAttribPointer(index, size, type, normalized, stride, offset);
# index 指定待分配attribute变量存储位置,但是通常情况下我们可以通过getAttribLocation来获取这个值
# size 指定缓冲区中每个顶点的分量个数 [1,2,3,4(default)]
# type 类型 指定数据类型
# [
# gl.BYTE: 字节型, 取值范围[-128, 127]
# gl.SHORT: 短整型,取值范围[-32768, 32767]
# gl.UNSIGNED_BYTE: 无符号字节型,取值范围[0, 255]
# gl.UNSIGNED_SHORT: 无符号短整型, 取值范围[0, 65535]
# gl.FLOAT: 浮点型(default)
# ]
# normalized 表明是否将非浮点型的数据归一化到[0,1]或[-1,1]之间 [false|true]
# stride 指定相邻的两个顶点间的字节数,[0-255]
# offset 指定缓冲区对象的偏移量,即attribute变量从缓冲区中的何处开始储存(从0开始)

5.开启attribute变量(gl.enableVertexAttribArray())
gl.enableVertexAttribArray(index)
#index 同 vertexAttribPointer中的index

6.绘制的图形
gl.drawArrays(mode, first, count)
# mode: 需要绘制的图像形状
# [
# gl.POINTS: 绘制一个点。
# gl.LINE_STRIP: 绘制一条直线到下一个顶点
# gl.LINE_LOOP: 绘制一条首尾相连的线
# gl.LINES: 绘制一条线
# gl.TRIANGLES: 绘制一个三角形
# gl.TRIANGLE_STRIP: 绘制三角形带
# gl.TRIANGLE_FAN: 绘制一系列组成扇形的相邻三角形
# ]
# first: 绘制的开始点
# count: 需要绘制的图形个数

WebGL中只有将一个缓冲区绑定为“当前缓冲区”时,才可以对其进行操作.为缓冲区填充数据时,需要传入一个Float32Array对象,该对象是基于数组vertices建立的,该数组存储着所有顶点文本形式的坐标.Javascript中,数组是一个文本对象(text),而Float32Array对象是一个二进制对象(banary),显然二进制对象工作效率更高.

WebGL中需要浏览器和显卡进行通信,为了满足JavaScript与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式。像C语言那样,直接操作字节,然后将4个字节的32位整数,以二进制形式原封不动地送入显卡,脚本的性能就会大幅提升。

 let vertices = new Float32Array([0.0,0.0,0.0,0.1,0.0,-0.1])

2.绘制一个正方形

通过创建一个顶点缓冲区(用于读取图形的顶点坐标)以及片元缓冲区(用于读取每个片元的颜色信息)来进行绘制.

// 顶点着色器
attribute vec4 aVertexPosition;
attribute vec4 aVertexColor;

varying lowp vec4 vColor;

void main() {
    gl_Position = aVertexPosition; // Set the vertex coordinates of the point
    gl_PointSize = 1.0;                   // Set the point size
    vColor = aVertexColor;
}
// 片段着色器
varying lowp vec4 vColor;

void main() { 
    gl_FragColor = vColor;  // Set the point color
}

aVertexPosition,aVertexColor变量是attribute修饰符修饰的全局变量,其变量的值可以在js代码中被赋值. 通过声明两个同名的用varying修饰的变量vColor,就可以实现将值从顶点着色器传递到片元着色器中.

		// 获取在着色器中声明的变量
		let vertexPosition = gl.getAttribLocation(program, 'aVertexPosition');
		let vertexColor = gl.getAttribLocation(program, 'aVertexColor');

		// 正方形顶点
		let positionBuffer = gl.createBuffer();
		gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
		const positions = [
			1.0,  1.0,
		   -1.0,  1.0,
			1.0, -1.0,
		   -1.0, -1.0,
		];
		gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
		gl.vertexAttribPointer(vertexPosition, 2, gl.FLOAT, false, 0, 0);
		gl.enableVertexAttribArray(vertexPosition);

		// 正方形顶点颜色
		const colors = [
			1.0,  0.0,  0.0,  1.0,    // 红色
			0.0,  1.0,  0.0,  1.0,    // 绿色
			0.0,  0.0,  1.0,  1.0,    // 蓝色
			1.0,  1.0,  1.0,  1.0,    // 白色
		];
		let colorBuffer = gl.createBuffer();
		gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
		gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
		gl.vertexAttribPointer(vertexColor, 4, gl.FLOAT, false, 0, 0);
		gl.enableVertexAttribArray(vertexColor);

		gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

渲染出来的图形是一个彩色的正方形

之所以颜色会发生渐变,是因为片元着色器中在接收到4个顶点信息后,通过内插可以拿到每个片元的信息,最后渲染到浏览器中. 顶点着色器是唯一可以从缓存中读取值的地方,通过声明两个同名的用varying修饰的变量v_color,就可以实现将值从顶点着色器传递到片元着色器.

插值的过程示意图