绘制多个点
在上一部分的内容中,我们讲了使用WebGL的基本方法,探究了顶点着色器和片元着色器的功能和特征,以及如何使用着色器进行绘图。在此基础上,做了几个示例程序,如何使用WebGL绘制一个有颜色和坐标的点。这部分,我们将如何学习绘制多个点。
我们先看完整的代码,之后再一一分析。
// MultiPoint.js
// 顶点着色器
var VSHADER_SOURCE=
'attribute vec4 a_Position;\n'+
'void main(){\n'+
'gl_Position = a_Position;\n'+
'gl_PointSize = 10.0;\n'+
'}\n';
// 片元着色器
var FSHADER_SOURCE=
'void main(){\n'+
'gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);\n'+ //设置颜色
'}\n';
function main(){
var canvas = document.getElementById('example');
var gl = getWebGLContext(canvas);
if (!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)) {
console.log('fail to init shader');
};
// 设置顶点数据
var n = initVertexBuffers(gl);
if(n < 0) {
console.log('fail to set Position of the vertices')
}
// 设置 canvas背景色
gl.clearColor(0.0,0.0,0.0,1.0);
// 清空canvas
gl.clear(gl.COLOR_BUFFER_BIT);
// 函数中 只调用一次就完成了绘制,因为调用时,第三个参数传递的是n,而不是1。
// 必须显式地告诉WebGL系统要绘制几个点,因为WebGL并不知道 缓冲区中有多少个顶点的数据,
// 即使知道也不能确定是否全部要画出。
gl.drawArrays(gl.POINT,0,n);
}
// 创建顶点缓冲区对象,并将多个顶点的数据保存在缓冲区,然后将缓冲区传给顶点着色器。
function initVertexBuffers(gl){
var vertices = new Float32Array({
0.0,0.5,-0.5,-0.5,0.5,-0.5
});
var n = 3;
// 创建缓冲区对象
var vertexBuffer = gl.createBuffer();
if(!vertexBuffer) {
console.log('fail to create buffer');
return -1;
};
// 将缓冲区对象绑定到目标
gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer);
// 向缓冲区对象中写入数据
gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW);
// 获取 a_Position 存储位置
var a_Position = gl.getAttribLocation(gl.program,'a_Position');
// 将缓冲区对象分配给a_Position
gl.vertexAttribPointer(a_Position,2,gl_FLOAT,false,0,0);
// 连接 a_Position变量和分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Position);
return n;
}
缓冲区对象
对那些由多个顶点组成的图形,比如三角形,矩形和立方体来说,需要一次性将图形的所有顶点全部传入顶点着色器,然后才能把图形画出来。为了实现这一点,WebGL提供了一种很方便的机制,即缓冲区对象。
概念
缓冲区对象是WebGL系统中的一块内存区域,我们可以一次性地向缓冲区对象中填入大量的顶点数据,然后把这些数据保存在其中,供顶点着色器使用。
使用缓冲区对象
使用缓冲区对象向顶点着色器传入多个顶点的数据,需要遵循以下五个步骤:处理其他对象也类似。
- 创建缓冲区对象 gl.createBuffer
- 绑定缓冲区对象 gl.bindBuffer
- 将数据写入缓冲区对象 gl.bufferData
- 将缓冲区对象分配给一个attrbute变量 gl.vertexAttribPointer
- 开启attribute变量 gl.enableVertexAttribArray
我们来看 示例程序中的 initVertexBuffers方法。
function initVertexBuffers(gl){
var vertices = new Float32Array({
0.0,0.5,-0.5,-0.5,0.5,-0.5
});
var n = 3;
// 创建缓冲区对象
var vertexBuffer = gl.createBuffer();
if(!vertexBuffer) {
console.log('fail to create buffer');
return -1;
};
// 将缓冲区对象绑定到目标
gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer);
// 向缓冲区对象中写入数据
gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW);
// 获取 a_Position 存储位置
var a_Position = gl.getAttribLocation(gl.program,'a_Position');
// 将缓冲区对象分配给a_Position
gl.vertexAttribPointer(a_Position,2,gl_FLOAT,false,0,0);
// 连接 a_Position变量和分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Position);
return n;
}
创建缓冲区对象
var vertexBuffer = gl.createBuffer();
使用WebGL时,你需要调用gl.createBuffer方法来创建缓冲区对象。相应的,gl.deleteBuffer(buffer)创建出来的缓冲区对象。
绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer);
创建缓冲区对象之后的第二个步骤就是将缓冲区对象绑定到WebGL系统中已经存在的"目标"上。这个"目标"表示缓冲区对象的用途,在这里,就是向顶点着色器提供给attribute变量的数据,这样WebGL才能正确处理其中的内容。
gl.bingdBuffer(target,buffer) 函数规范如下:
参数:
- target参数可以是以下中的一个:
- gl.ARRAY_BUFFER 表示缓冲区对象中包含了顶点的数据
- gl.ELEMENT_ARRAY_BUFFER 表示缓冲区对象中包含了顶点的索引值。
- buffer 指定之前由gl.createBuffer() 返回的待绑定的缓冲区对象。
返回值: 无
错误: INVALID_ENUM target 不是上述值之一,这时保持原有的绑定情况不变。
向缓冲区对象中写入数据
gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW)
第三步,开辟空间并向缓冲区中写入数据。
该方法的效果是 将第2个参数vertices中的数据写入了绑定到第一个参数gl.ARRAY_BUFFER 上的缓冲区对象。我们不能直接向缓冲区对象写入数据,只能向"目标"写入数据。
gl.bufferData(target,data,usage) 函数规范如下:
参数:
- target gl.ARRAY_BUFFER 或者 gl.ELEMENT_ARRAY
- data 写入缓冲区对象的数据,类型化数组
- usage 表示程序将如何使用存储在缓冲区对象中的数据。该参数将帮助WebGL优化操作,就算传入了错误的值,也不会影响程序的执行,只会影响程序的执行效率
- gl.STATIC_DRAW 只会讲缓冲区对象中写入一次数据,但需要绘制很多次
- gl.STREAM_DRAW 只会向缓冲区对象中写入一次数据,然后绘若干次
- gl.DYNATIC_DRAW 会像缓冲区对象中写入多次数据,并绘制很多次
返回值: 无
错误: INVALID_ENUM target不是上述值之一
类型化数组
向缓存区对象写入的数据是一种特殊的JavaScript数组,浏览器事先知道数组中的数据类型,处理起来也会更有效率。
类型:
-
Int8Array 1 8位整型数数
-
UInt8Array 1 8位无符号整型数数
-
Int16Array 2 16位整型数数
-
UInt16Array 2 16位无符号整型数数
-
Int32Array 4 32位整型数
-
Uint32Array 4 32位无符号整型数数
-
Float32Array 8 单精度32位浮点数
-
Float64Array 8 双精度64位浮点数
常用方法:
get(index) 获取第index个元素的值
set(index,value) 设置第index个元素的值为value
set(array,offset) 从offset个元素开始,将数组array中的值填充进去
length 数组的长度
BYTES_PER_ELEMENT 数组中每个元素所占的字节数
注意:
- 与普通的数组不同,类型化数组不支持push和pop方法
- 创建类型化数组的方法,只能是使用new操作符
- 可以通过指定数组元素的个数的方法来创建一个空的类型化数组
将缓冲区对象分配给attribute变量
在之前的例子,我们使用gl.vertexAttrib[1-4]f系列函数来为attribute变量分配值,但是这些方法一次只能向attribute变量分配一个值。而现在需要将整个数组中的所有值,在这里是顶点数据,一次性地分配给一个attribute变量。
gl.vertexAttribPointer()方法解决了这个问题,可以将整个缓冲区对象分配给attribute变量。
var a_Position = gl.getAttribLocation(gl.program,'a_Position');
gl.vertexAttribPointer(a_Position,2,gl.FLOAT,flase,0,0);
gl.vertexAttribPointer(location,size,type,normalized,stride,offset)函数的规范如下:
- 参数
- location 指定分配给attribbute变量的存储位置
- size 指定缓冲区对象中每个顶点的分量个数(1-4)。若size比attribute变量需要的分量数小,缺失分量将按照 gl.vertexAttribu[1-4]f系列函数中的规则补全。
- type 用以下类型之一来指定数据格式
- gl.UNSIGNED_BYTE 无符号字节 Uint8Array
- gl.SHORT 短整型 Int16Array
- gl.UNSIGNED_SHORT 无符号短整型 Uint16Array
- gl.INT 整型 Uint32Array
- gl.UNSIGNED_INT 无符号整型 Uint32Array
- gl.FLOAT 浮点型 Float32Array
- normalize true/false 表示是否将非浮点的数据 归一到 [0,1] 或者[-1,1]之间
- stride 指定相邻两个顶点间的字节数(即每个顶点占用多个字节数)
- offset 指定缓冲区对象中的偏移量。 以字节为单位。即attribute变量从缓冲区的何处开始存储。如果是从起始位置,offset设为0
- 错误
- INVALID_OPERATION 不存在当前程序对象
- INVALID_VALUE location大于等于attribute变量的最大数,或者stride或者offset是负值
开启attribute变量
最后一步,就是开启或者说激活attribute变量(自己的理解是 让attribute变量有权限能够访问到缓冲区对象)。
我们使用 gl.enableVertexAttribArray(a_Position)来完成。
当然了,也可以使用gl.disableVertexAttribArray(location); 关闭分配。
绘制三角形
在之前的部分,我们已经学习了如何将多个顶点的坐标数据传递给顶点着色器,下面我们尝试使用这些顶点来绘制一个简单的二维图形。
相比之前绘制多个点的代码,我们需要改动的代码并不多,只有两处。
- 在顶点着色器的代码中,指定点的尺寸的一行gl.PointSize =10.0 被去掉了。该语句只在绘制单个点的时候才起作用。
- gl.drawArrays()的第一个参数mode 改成了 gl.TRIANGLES。改成 gl.TRIANGLES 就相当于告诉WebGL,从缓冲区中的第一个顶点开始,使顶点着色器执行3次,用这3个顶点绘制处一个三角形。
完整代码如下:
// 顶点着色器
var VSHADER_SOURCE =
' attribute vec4 a_Position;\n' +
'void main() { \n ' +
'gl_Position = a_Position;\n' +
'}\n';
var FSHADER_SOURCE=
'void main(){\n'+
'gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);\n'+ //设置颜色
'}\n';
function main() {
var canvas = document.getElementById('example');
var gl = getWebGLContext(canvas);
if (!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)) {
}
// 设置顶点数据
var n = initVertexBuffers(gl);
if(n < 0) {
console.log('fail to set Position of the vertices')
}
// 设置 canvas背景色
gl.clearColor(0.0,0.0,0.0,1.0);
// 清空canvas
gl.clear(gl.COLOR_BUFFER_BIT);
// 示例程序中只用了一个gl.drawArrays 就完成了绘图操作。传入的第三个参数 是n。
// 应该要显式地告诉WebGL应该要绘制多少个顶点。
gl.drawArrays(gl.TRIANGLES,0,n);
}
function initVertexBuffers(gl) {
var vertices = new Float32Array([
0.0,0.5,-0.5,-0.5,0.5,-0.5
]);
var n = 3;
var vertexBuffer = gl.createBuffer();
if(!vertexBuffer) {
console.log('fail to create the buffer object');
return -1;
}
// 2. 将缓冲区绑定到目标
gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer);
// 向缓冲区对象中写入数据
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
var a_Position = gl.getAttribLocation(gl.program,'a_Position');
// 将缓冲区对象分配给a_Position 变量
gl.vertexAttribPointer(a_Position,2,gl.FLOAT,false,0,0);
// 连接 a_Position 变量和分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Position);
return n
}
gl.drawArrays()中的mode参数
WebGL可以绘制多种基本图形,通过设置gl.drawArrays的第一个参数mode。
-
点 gl.POINTS 一系列点,绘制在 v0,v1,v2,v3...
-
线段 gl.LINES 一系列 独立的线段,绘制在(v0,v1),(v2,v3),(v4,v5)处,如果点的个数是奇数,最后一个会被忽略
-
线条 gl.LINE_STRIP 一系列 连接的线段,绘制在(v0,v1),(v1,v2),(v2,v3)上
-
回路 gl.LINE_LOOP 与gl.LINE_STRIP相比,增加了最后一个点到来连接到第一个点的线段
-
三角形 gl.TRIANGLES 一系列独立的三角形,绘制在(v0,v1,v2),(v3,v4,v5)上, 如果点的个数不是3的倍数,
那么剩下的最后一个点或者两个点都会被忽略
- 三角带 gl.TRIANGLE_STRIP 一系列条带状的三角形。前三个点构成了第一个三角形,从第二个点开始的第三个点构成了第二个三角形。
(v0,v1,v2),(v2,v1,v3),(v3,v2,v4)依次类推。第二个点不是(v1,v2,v3),是为了保证第二个三角形的绘制也是按照逆时针的顺序绘制
- 三角扇 gl.TRIANGLE_FAN 一系列三角形 组成的类似扇形的图形。前三个点组成了一个三角型。(v0,v1,v2),(v0,v2,v3),(v0,v3,v4)