使用缓冲区对象向着色器传值绘制三角形
1.demo效果
如上图,在黑色背景中绘制了一个绿色的三角形,不过它是通过使用缓冲区对象向着色器传输数据而绘制出来,接下来会介绍相关知识点与绘制过程
2.相关知识点
2.1 什么是缓冲区对象
我们之前介绍了attribute变量和uniform变量,它们分别通过gl.vertexAttrib[1234]f()和gl.uniform[1234]f()向着色器中传入变量,但是它们一次只能传输一个顶点的信息,也就意味着一次只能绘制一个点。我们绘制复杂图形的时候需要一次性的将全部的顶点传给顶点着色器,WebGL提供了一种很方便的机制,即缓冲区对象,它可以一次性的向着色器传入多个顶点的数据
2.2 使用缓冲区对象的步骤
我们可以使用缓冲区对象向顶点着色器传入多个顶点的数据,但是需要遵循一定的步骤
- 创建缓冲区对象-gl.createBuffer()
- 绑定缓冲区对象-gl.bindBuffer()
- 将数据写入缓冲区对象-gl.bufferData()
- 将缓冲区对象分配给一个attribute变量-gl.vertexAttribPointer()
- 开启attribute变量-enableVertexAttribArray()
2.2.1 创建缓冲区对象
创建缓冲区对象比较简单只需要执行语句var vertexBuffer = gl.createBuffer()
即可,接下来我们介绍一下该函数的使用规范
调用示例:gl.createBuffer()
--------------------------------------------------------------------------
函数功能:创建缓冲区对象
--------------------------------------------------------------------------
返回值 非null 新创建的缓冲区对象
null 创建缓冲区失败
--------------------------------------------------------------------------
错误 无
有创建缓冲区对象的方法,那么就会有对应的删除缓冲区对象的方法,使用也非常简单,只需要传入表示缓冲区对象的参数gl.deleteBuffer(buffer)
2.2.2 绑定缓冲区
绑定缓冲区实际上是将缓冲区对象绑定到指定的目标上,这里的目标表示的缓冲区对象的用途,具体请参照下面的函数规范说明
调用示例:gl.bindBuffer(target, buffer)
--------------------------------------------------------------------------
函数功能:将buffer表示的缓冲区对象绑定到target表示的目标上
--------------------------------------------------------------------------
参数
target 参数可以是以下中的一个
gl.ARRAY_BUFFER 表示缓冲区对象中包含了顶点的数据
gl.ELEMENT_ARRAY_BUFFER 表示缓冲区对象中包含了顶点的索引值
buffer 由gl.createBuffer()返回的缓冲区对象
--------------------------------------------------------------------------
返回值 无
--------------------------------------------------------------------------
错误 INVALID_ENUM taget不是上述值之一
2.2.3 向缓冲区对象写入数据
如果已经通过gl.bindBuffer()方法将缓冲区对象绑定到gl.ARRAY_BUFFER或gl.ELEMENT_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 taget不是上述值之一
2.2.4 将缓冲区对象分配给attribute变量
你已经知道通过gl.vertexAttrib[1234]f()方法向着色器传值,但一次只能传一个顶点的数据,现在我们需要一次传输多个顶点的数据,那么就要用到 gl.vertexAttribPointer() 方法了
接下来介绍gl.vertexAttribPointer()的规范
调用示例:gl.vertexAttribPointer(location, size, type, normalized, stride, offset)
----------------------------------------------------------------------------------
函数功能:将绑定到gl.ARRAY_BUFFER的缓冲区对象分配给由location指定的attribute变量
----------------------------------------------------------------------------------
参数
location 待分配的attribute变量的存储地址
size 缓冲区中每个顶点的分量个数(1~4)
type 用以下类型之一来指定数据格式
gl.UNSIGNED_BYTE 无符号字节,Uint8Array
gl.SHORT 短整形,int16Array
gl.UNSIGNED_SHORT 无符号短整形,Uint16Array
gl.INT 整形,int32Array
gl.UNSIGNED_INT 无符号整形,Uint32Array
gl.FLOAT 浮点型,Float32Array
normalized 传入true或false,表明是否非浮点型的数据归一
化到[0,1]或[-1,1]区间
stride 指定相邻两个顶点间的字节数,默认为0
offset 指定缓冲区对象中的偏移量(以字节为单位),即
attribute变量从缓冲区的何处开始存储,0代表起始位置
----------------------------------------------------------------------------------
返回值 无
----------------------------------------------------------------------------------
错误 INVALID_OPERATION 不存在当前程序对象
INVALID_VALUE location大于等于attribute变量的最大数据,默认为8,
或者stride或offset是负值
2.2.5 开启attribute变量
经过以上四个步骤已经向绑定的缓冲区对象中写入了数据,但是顶点着色器还不能访问缓冲区内的数据,这时需要 gl.enableVertexAttribArray() 方法来帮忙开启attribute变量,接下来看看该函数的使用规范
调用示例:gl.enableVertexAttribArray(location)
--------------------------------------------------------------------------
函数功能:开启location指定的attribute变量
--------------------------------------------------------------------------
返回值 无
--------------------------------------------------------------------------
错误 INVALID_VALUE location大于等于attribute变量的最大数据,默认为8
与之对应的有一个方法用来关闭分配,使用时只需要传入表示attribute变量存储地址的参数即可gl.deleteBuffer(location)
2.3 类型化数组
为了优化性能,WebGL引入了一种特殊的数组- 类型化数组 ,我们先来看一下类型化数组的种类
数组类型 | 每个元素所占字节数 | 描述(C语言还在那个的数据类型) |
---|---|---|
Int8Array | 1 | 8位整形数(signed char) |
UInt8Array | 1 | 8位无符号整形数(unsigned char) |
Int16Array | 2 | 16位整形数(signed short) |
UInt16Array | 2 | 16位无符号整形数(unsigned short) |
Int32Array | 4 | 32位整形数(signed int) |
UInt32Array | 4 | 32位无符号整形数(unsigned int) |
Float32Array | 4 | 32位单精度浮点数(float) |
Float64Array | 8 | 64位双精度浮点数(double) |
类型化数组与JavaScript中的数组有所不同,它不支持pus(),pop()等方法,但是它有自己一系列的方法和属性,具体请查阅下表
方法、属性和常量 | 描述 |
---|---|
get(index) | 获取第index个元素的值 |
set(index, value) | 设置第index个元素的值为value |
set(array, offset) | 从第offset个元素开始将数组array中的值填充进去 |
length | 数组的长度 |
BYTES_PER_ELEMENT | 数组中每个元素所占的字节数 |
2.4 gl.drawArray()第一个参数mode说明
gl.drawArray()方法是一个强大的函数可以绘制各种图形,它的第一个参数可以取众多常量:gl.POINTS,gl.LINES,gl.LINE_STRIP,gl.LINE_LOOP,gl.TRIANGLES,gl.TRIANGLE_STRIP,gl.TRIANGLE_FAN,接下来通过一张图来解释一下每一个值代表的意思
3.demo代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<!--通过canvas标签创建一个400px*400px大小的画布-->
<canvas id="webgl" width="400" height="400"></canvas>
<script>
//顶点着色器
var VSHADER_SOURCE = '' +
'attribute vec4 a_Position;\n' + //声明attribute变量a_Position,用来存放顶点位置信息
'void main(){\n' +
' gl_Position = a_Position;\n' + //变量a_Position赋值给顶点着色器内置变量gl_Position
'}\n';
//片元着色器
var FSHADER_SOURCE = '' +
'precision mediump float;\n' + // 设置精度
'uniform vec4 u_FragColor;\n' + //声明uniform变量u_FragColor,用来存放顶点颜色信息
'void main(){\n' +
//通过u_FragColor变量设置片元颜色
' gl_FragColor = u_FragColor;\n' +
'}\n';
//初始化着色器函数
function initShader(gl, VSHADER_SOURCE, FSHADER_SOURCE) {
//创建顶点着色器对象
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
//创建片元着色器对象
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
//引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, VSHADER_SOURCE);
gl.shaderSource(fragmentShader, FSHADER_SOURCE);
//编译顶点、片元着色器
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
//创建程序对象program
var program = gl.createProgram();
//附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
//链接program
gl.linkProgram(program);
//使用program
gl.useProgram(program);
gl.program = program
//返回程序program对象
return program;
}
function init() {
//通过getElementById()方法获取canvas画布
var canvas = document.getElementById('webgl');
//通过方法getContext()获取WebGL上下文
var gl = canvas.getContext('webgl');
//初始化着色器
initShader(gl, VSHADER_SOURCE, FSHADER_SOURCE);
// 设置canvas的背景色
gl.clearColor(0.0, 0.0, 0.0, 1.0);
//清空canvas
gl.clear(gl.COLOR_BUFFER_BIT);
//给片元着色器uniform变量u_FragColor赋值
setFragColor(gl)
//初始化顶点
var n = initVertexBuffers(gl)
//绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, n)
}
//给片元着色器uniform变量u_FragColor赋值
function setFragColor(gl) {
//获取片元着色器uniform变量u_FragColor的存储地址
var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
//向片元着色器uniform变量u_FragColor传值
gl.uniform4f(u_FragColor, 0.0, 1.0, 0.0, 1.0) //绿色
}
//初始化顶点
function initVertexBuffers(gl) {
var vertices = new Float32Array([
0.0, 0.5,
-0.5, -0.5,
0.5, -0.5
])
//1.创建缓冲区对象
var vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
console.log('创建缓冲区对象失败!')
return -1
}
//2.将缓冲区对象绑定到目标
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
//3.向缓冲区对象中写入数据
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
var a_Position = gl.getAttribLocation(gl.program, 'a_Position'); //获取着色器attribute变量a_Position的存储地址
//4.将缓冲区对象分配给a_Position变量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
//5.连接a_Position变量与分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Position)
return vertices.length / 2
}
init()
</script>
</body>
</html>