1、绘制多个点
构建三维模型的基本单位是三角形。不管三维模型的形状多么复杂,其基本组成部分都是三角形,只不过复杂的模型由更多的三角形构成而已。
gl.vertexAttrib3f()一次只能向顶点着色器传入一个顶点,而绘制三角形、矩形和立方体等,你需要一次性将图形的顶点全部传入顶点着色器。WebGL提供了一种很方便的机制,即缓冲区对象。
缓冲区对象:可以一次性地向着色器传入多个顶点的数据。缓冲区对象是WebGL系统中的一块内存区域,我们可以一次性地向缓冲区对象中填充大量的顶点数据,然后将这些数据保存在其中,供顶点着色器使用。
示例:
//顶点着色器程序
var VSHADER_SOURCE = `
attribute vec4 a_Position;
void main(){gl_Position = a_Position;//将attribute变量赋值给gl_Position变量gl_PointSize = 10.0;//设置尺寸
}
`;
//片元着色器程序
var FSHADER_SOURCE = `
precision mediump float;
uniform vec4 u_FragColor;//uniform变量
void main(){gl_FragColor = u_FragColor;//设置颜色
}`function main() {//获取<canvas>元素var canvas = document.getElementById('myCanvas');if (!canvas) {console.log("failed to retrieve the canvas element");return;}//获取WebGL的绘图上下文var gl = canvas.getContext('webgl');if (!gl) {console.log("failed to get webgl context");return;}//初始化着色器if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {console.log("failed to initialize shaders");return;}//*************设置顶点位置start***************/var n = initVertexBuffers(gl);if (n < 0) {console.log("failed to set the positions of the vertices");return;}//*************设置顶点位置end***************/var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');gl.uniform4f(u_FragColor, 1.0, 0.0, 0.0, 1.0);//设置canvas的背景色gl.clearColor(0.0, 0.0, 0.0, 1.0);//黑色//清除canvasgl.clear(gl.COLOR_BUFFER_BIT);//绘制三个点gl.drawArrays(gl.POINTS, 0, n);
}function initVertexBuffers(gl) {var vertices = new Float32Array([0.0, 0.5, -0.5, -0.5, 0.5, -0.5]);var n = 3;//1.1 创建缓冲区对象var vertexBuffer = gl.createBuffer();if (!vertexBuffer) {console.log("failed to create the buffer object");return -1;}//1.2 将缓冲区对象绑定到目标gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);//1.3 向缓冲区对象中写入数据gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);//2.1 获取顶点着色器变量位置var a_Position = gl.getAttribLocation(gl.program, 'a_Position');//2.2 将缓冲区对象分配给a_Positiongl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);//2.3 连接a_Position变量与分配给它的缓冲区对象gl.enableVertexAttribArray(a_Position);return n;
}
initVertexBuffers()函数的功能是:
- 创建顶点缓冲区对象
- 将多个顶点的数据保存在缓冲区中
- 然后将缓冲区传给顶点着色器
1.1 使用缓冲区对象
使用缓冲区对象向顶点着色器传入多个顶点的数据,需要遵循以下五个步骤:
- 创建缓冲区对象(gl.createBuffer())
- 绑定缓冲区对象(gl.bindBuffer())
- 将数据写入缓冲区对象(gl.bufferData())
- 将缓冲区对象分配给一个attribute变量(gl.vertexAttribPointer())
- 开启attribute变量(gl.enableVertexAttribArray())
1.1.1 创建缓冲区对象
执行该方法的结果是:WebGL系统中多了一个新创建出来的缓冲区对象。
gl.createBuffer()函数规范如下:
gl.createBuffer();//创建缓冲区对象 | ||
返回值 | 非null | 新创建的缓冲区对象 |
null | 创建缓冲区对象失败 | |
错误 | 无 |
相应地,gl.deleteBuffer(buffer)函数可用来删除被gl.createBuffer()创建出来的缓冲区对象。
gl.deleteBuffer(buffer);//删除参数buffer表示的缓冲区对象 | ||
参数 | buffer | 待删除的缓冲区对象 |
返回值 | 无 | |
错误 | 无 |
1.1.2 绑定缓冲区
将缓冲区对象绑定到WebGL系统中已经存在的“目标”上。这个“目标”表示缓冲区对象的用途,这样WebGL才能够正确处理其中的内容。gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer)函数规范如下:
gl.bindBuffer(target,buffer);//允许使用buffer表示的缓冲区对象并将其绑定到target表示的目标上 | ||
target | gl.ARRAY_BUFFER | 表示缓冲区对象中包含了顶点的数据 |
gl.ELEMENT_ARRAY_BUFFER | 表示缓冲区对象中包含了顶点的索引值 | |
buffer | 指定之前由gl.createBuffer()返回的待绑定的缓冲区对象。如果为null,则禁用对target的绑定 | |
返回值 | 无 | |
错误 | INVALID_ENUM | target不是上述值之一,这时将保持原有的绑定情况不变 |
1.1.3 向缓冲区对象中写入数据gl.bufferData()
gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW);将第2个参数vertices中的数据写入了绑定到第1个参数gl.ARRAY_BUFFER上的缓冲区对象。gl.bufferData()的规范如下:
gl.bufferData(target,data,usage);//开辟存储空间,向绑定在target上的缓冲区对象中写入数据data | ||
target | gl.ARRAY_BUFFER或gl.ELEMENT_ARRAY_BUFFER | |
data | 写入缓冲区对象的数据(类型化数组) | |
usage 表示程序将如何使用存储在缓冲区对象中的数据 | gl.STATIC_DRAW | 只会向缓冲区对象中写入一次数据,但需要绘制很多次 |
gl.STREAM_DRAW | 只会向缓冲区对象中写入一次数据,然后绘制若干次 | |
gl.DYNAMIC_DRAW | 会向缓冲区对象中多次写入数据,并绘制很多次 | |
返回值 | 无 | |
错误 | INVALID_ENUM | target不是上述值之一,这时将保持原有的绑定情况不变 |
类型化数组
JavaScript中常见的Array对象是一种通用的类型,既可以在里面存储数字也可以存储字符串。而类型化数组,所有元素都是同一种类型(可提高性能)。
类型化数组的方法、属性和常量:
方法、属性和常量 | 描述 |
get(index) | 获取第index个元素值 |
set(index,value) | 设置第index个元素的值为value |
set(array,offset) | 从第offset个元素开始将数组array中的值填充进去 |
length | 数组的长度 |
BYTES_PER_ELEMENT | 数组中每个元素所占的字节数 |
//创建类型化数组方法一:
var vertices = new Float32Array([0.5,-0.5,0.0,0.1,0.4,0.6]);
//创建类型化数组方法二:
var vertices = new Float32Array(4);
1.1.4 将缓冲区对象分配给attribute变量(gl.vertexAttribPointer())
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);//可以将整个缓冲区对象分配给attribute变量。其规范如下:
gl.vertexAttribPointer(location,size,type,normailzed,stride,offset);//将绑定到gl.ARRAY_BUFFER的缓冲区对象分配给由location指定的attribute变量 | ||
location | 指定待分配attribute变量的存储位置 | |
size | 指定缓冲区中每个顶点的分量个数(1到4)。若size比attribute变量需要的分量数小,缺失分量将按照与gl.vertexAttrib[1234]f()相同的规则补全。例如,如果size为1,那么第2,3分量自动设为0,第4分量为1。 | |
type | gl.UNSIGNED_BYTE | 无符号字节,Uint8Array |
gl.SHORT | 短整型,Int16Array | |
gl.UNSIGNED_SHORT | 无符号短整型,Uint16Array | |
gl.INT | 整型,Int32Array | |
gl.UNSIGNED_INT | 无符号整型,Uint32Array | |
gl.FLOAT | 浮点型,Float32Array | |
normalize | true/false | 是否将非浮点型的数据归一化到[0,1]或[-1,1]区间 |
stride | 默认0 | 指定相邻两个顶点间的字节数 |
offset | 指定缓冲区对象中的偏移量,即attribute变量从缓冲区中的何处开始存储。如果从起始位置开始的,则offset为0 | |
返回值 | 无 | |
错误 | INVALID_OPERATION | 不存在当前程序对象 |
INVALID_VALUE | location大于等于attribute变量的最大数目。 |
1.1.5 开启attribute变量
为了使顶点着色器能够访问缓冲区内的数据,需要使用gl.enableVertexAttribArray()方法来开启attribute变量。
gl.enableVertexArray(location);//开启location指定的attribute变量 | ||
参数 | location | 指定attribute变量的存储位置 |
返回值 | 无 | |
错误 | INVALID_VALUE | location大于等于attribute变量的最大数目。 |
执行完该命令后,缓冲区对象和attribute变量之间的连接就真正建立起来了。
同样,也可以使用gl.disableVertexAttribArray()来关闭分配。
1.2 gl.drawArrays()的第2个和第3个参数
gl.drawArrays(mode,first,count);//执行顶点着色器,按照mode参数指定的方式绘制图形 | |
mode | 绘制方式:gl.POINTS、gl.LINES、gl.LINE_STRIP、gl.LINE_LOOP、gl.TRIANGLES、gl.TRIANGLE_STRIP、gl.TRIANGLE_FAN |
first | 指定从哪个顶点开始绘制(整型数) |
count | 指定绘制需要用到多少个顶点(整型数) |
gl.drawArrays(gl.POINTS,0,3);//执行该命令后,顶点着色器被执行了3次,存储在缓冲区中的顶点坐标数据被依次传给attribute变量。
2、绘制三角形
//顶点着色器程序
var VSHADER_SOURCE = `
attribute vec4 a_Position;
void main(){gl_Position = a_Position;//将attribute变量赋值给gl_Position变量
}
`;
//片元着色器程序
var FSHADER_SOURCE = `
precision mediump float;
uniform vec4 u_FragColor;//uniform变量
void main(){gl_FragColor = u_FragColor;//设置颜色
}`function main() {//获取<canvas>元素var canvas = document.getElementById('myCanvas');if (!canvas) {console.log("failed to retrieve the canvas element");return;}//获取WebGL的绘图上下文var gl = canvas.getContext('webgl');if (!gl) {console.log("failed to get webgl context");return;}//初始化着色器if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {console.log("failed to initialize shaders");return;}//*************设置顶点位置start***************/var n = initVertexBuffers(gl);if (n < 0) {console.log("failed to set the positions of the vertices");return;}//*************设置顶点位置end***************/var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');gl.uniform4f(u_FragColor, 1.0, 0.0, 0.0, 1.0);//设置canvas的背景色gl.clearColor(0.0, 0.0, 0.0, 1.0);//黑色//清除canvasgl.clear(gl.COLOR_BUFFER_BIT);//绘制三个点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;//1.1 创建缓冲区对象var vertexBuffer = gl.createBuffer();if (!vertexBuffer) {console.log("failed to create the buffer object");return -1;}//1.2 将缓冲区对象绑定到目标gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);//1.3 向缓冲区对象中写入数据gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);//2.1 获取顶点着色器变量位置var a_Position = gl.getAttribLocation(gl.program, 'a_Position');//2.2 将缓冲区对象分配给a_Positiongl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);//2.3 连接a_Position变量与分配给它的缓冲区对象gl.enableVertexAttribArray(a_Position);return n;
}
画面效果:
与第1章节绘制三个点,本段代码只改动了两个地方:
- gl_PointSize = 10.0;被删除,因为该语句只有在绘制单个点的时候才会起作用。如果不删除,该语句也无效。
- gl.drawArrays()方法的第1个参数从gl.POINTS被改成了gl.TRIANGLES。