WebGL学习(二)-- 使用WebGL绘制多个点以及基本图形

1,918 阅读9分钟

绘制多个点

在上一部分的内容中,我们讲了使用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系统中的一块内存区域,我们可以一次性地向缓冲区对象中填入大量的顶点数据,然后把这些数据保存在其中,供顶点着色器使用。

使用缓冲区对象

使用缓冲区对象向顶点着色器传入多个顶点的数据,需要遵循以下五个步骤:处理其他对象也类似。

  1. 创建缓冲区对象 gl.createBuffer
  2. 绑定缓冲区对象 gl.bindBuffer
  3. 将数据写入缓冲区对象 gl.bufferData
  4. 将缓冲区对象分配给一个attrbute变量 gl.vertexAttribPointer
  5. 开启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) 函数规范如下:

参数:

  1. target参数可以是以下中的一个:
    • gl.ARRAY_BUFFER 表示缓冲区对象中包含了顶点的数据
    • gl.ELEMENT_ARRAY_BUFFER 表示缓冲区对象中包含了顶点的索引值。
  2. 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) 函数规范如下:

参数:

  1. target gl.ARRAY_BUFFER 或者 gl.ELEMENT_ARRAY
  2. data 写入缓冲区对象的数据,类型化数组
  3. usage 表示程序将如何使用存储在缓冲区对象中的数据。该参数将帮助WebGL优化操作,就算传入了错误的值,也不会影响程序的执行,只会影响程序的执行效率
    • gl.STATIC_DRAW 只会讲缓冲区对象中写入一次数据,但需要绘制很多次
    • gl.STREAM_DRAW 只会向缓冲区对象中写入一次数据,然后绘若干次
    • gl.DYNATIC_DRAW 会像缓冲区对象中写入多次数据,并绘制很多次

返回值: 无

错误: INVALID_ENUM target不是上述值之一

类型化数组

向缓存区对象写入的数据是一种特殊的JavaScript数组,浏览器事先知道数组中的数据类型,处理起来也会更有效率。

类型:

  1. Int8Array 1 8位整型数数

  2. UInt8Array 1 8位无符号整型数数

  3. Int16Array 2 16位整型数数

  4. UInt16Array 2 16位无符号整型数数

  5. Int32Array 4 32位整型数

  6. Uint32Array 4 32位无符号整型数数

  7. Float32Array 8 单精度32位浮点数

  8. Float64Array 8 双精度64位浮点数

常用方法:

get(index) 获取第index个元素的值

set(index,value) 设置第index个元素的值为value

set(array,offset) 从offset个元素开始,将数组array中的值填充进去

length 数组的长度

BYTES_PER_ELEMENT 数组中每个元素所占的字节数

注意:

  1. 与普通的数组不同,类型化数组不支持push和pop方法
  2. 创建类型化数组的方法,只能是使用new操作符
  3. 可以通过指定数组元素的个数的方法来创建一个空的类型化数组

将缓冲区对象分配给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)函数的规范如下:

  1. 参数
    • 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
  2. 错误
    • INVALID_OPERATION 不存在当前程序对象
    • INVALID_VALUE location大于等于attribute变量的最大数,或者stride或者offset是负值

开启attribute变量

最后一步,就是开启或者说激活attribute变量(自己的理解是 让attribute变量有权限能够访问到缓冲区对象)。

我们使用 gl.enableVertexAttribArray(a_Position)来完成。

当然了,也可以使用gl.disableVertexAttribArray(location); 关闭分配。

绘制三角形

在之前的部分,我们已经学习了如何将多个顶点的坐标数据传递给顶点着色器,下面我们尝试使用这些顶点来绘制一个简单的二维图形。

相比之前绘制多个点的代码,我们需要改动的代码并不多,只有两处。

  1. 在顶点着色器的代码中,指定点的尺寸的一行gl.PointSize =10.0 被去掉了。该语句只在绘制单个点的时候才起作用。
  2. 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。

  1. 点 gl.POINTS 一系列点,绘制在 v0,v1,v2,v3...

  2. 线段 gl.LINES 一系列 独立的线段,绘制在(v0,v1),(v2,v3),(v4,v5)处,如果点的个数是奇数,最后一个会被忽略

  3. 线条 gl.LINE_STRIP 一系列 连接的线段,绘制在(v0,v1),(v1,v2),(v2,v3)上

  4. 回路 gl.LINE_LOOP 与gl.LINE_STRIP相比,增加了最后一个点到来连接到第一个点的线段

  5. 三角形 gl.TRIANGLES 一系列独立的三角形,绘制在(v0,v1,v2),(v3,v4,v5)上, 如果点的个数不是3的倍数,

那么剩下的最后一个点或者两个点都会被忽略

  1. 三角带 gl.TRIANGLE_STRIP 一系列条带状的三角形。前三个点构成了第一个三角形,从第二个点开始的第三个点构成了第二个三角形。

(v0,v1,v2),(v2,v1,v3),(v3,v2,v4)依次类推。第二个点不是(v1,v2,v3),是为了保证第二个三角形的绘制也是按照逆时针的顺序绘制

  1. 三角扇 gl.TRIANGLE_FAN 一系列三角形 组成的类似扇形的图形。前三个点组成了一个三角型。(v0,v1,v2),(v0,v2,v3),(v0,v3,v4)