目录
目录
为了将顶点坐标传入着色器,需要遵循一下五步:
但是!!!
示例代码:
gl.vertexAttribPointer()的函数规范
stride参数
顶点坐标数据
offset参数
顶点尺寸数据
首先,分析如何实现下方示例为三个不同大小位置的点
为了将顶点坐标传入着色器,需要遵循一下五步:
1. 创建缓冲区对象
2. 将缓冲区对象绑定到target上
3. 将顶点坐标数据写入缓冲区对象
4. 将缓冲区对象分配给对应的attribute对象
5. 激活开启attribute变量
当然你可以创建两个缓冲区对象分别存储坐标数据和非坐标数据,随之传给各自对应的着色器变量 a_Position 和 a_pointSize
但是!!!
使用多个缓冲区对象向着色器传递多种数据,比较适合数据量不大的情况。当程序中的复杂三维图形具有成千上万个顶点时,维护所有的顶点数据是很困难的。想象一下,如果程序中的三维模型有几千几万个顶点会怎样。所以,WebGL允许我们把顶点的坐标和尺寸数据打包到同一个缓冲区对象中,并通过某种机制分别访问缓冲区对象中不同种类的数据。比如,可以将顶点的坐标和尺寸数据按照如下方式交错组织(interleaving),如下图所示:
可见,一旦我们将几种“逐顶点”的数据(坐标和尺寸)交叉存储在一个数组中,并将数组写入一个缓冲区对象。WebGL就需要有差别地从缓冲区中获取某种特定数据(坐标或尺寸),即使用gl.vertexAttribPointer()函数的第5个参数stride和第6个参数offset。下面让我们来看看代码。
示例代码:
// 顶点着色器程序
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute float a_PointSize;\n' +
'void main() {\n' +
' gl_Position = a_Position;\n' +
' gl_PointSize = a_PointSize;\n' +
'}\n';
// 片元着色器程序
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
'}\n';
function main() {
// Retrieve <canvas> element
var canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
// 设置顶点坐标和点的尺寸
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the vertex information');
return;
}
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, n);
}
function initVertexBuffers(gl) {
var verticesSizes = new Float32Array([
// 顶点坐标和点的尺寸
0.0, 0.5, 10.0, // 第一个点
-0.5, -0.5, 20.0, // 第二个点
0.5, -0.5, 30.0 // 第三个点
]);
var n = 3; // The number of vertices
// 创建缓冲区对象
var vertexSizeBuffer = gl.createBuffer();
if (!vertexSizeBuffer) {
console.log('Failed to create the buffer object');
return -1;
}
// 将顶点坐标和尺寸写入缓冲区并开启
gl.bindBuffer(gl.ARRAY_BUFFER, vertexSizeBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesSizes, gl.STATIC_DRAW);
var FSIZE = verticesSizes.BYTES_PER_ELEMENT;
// 获取a_Position的存储位置,分配缓冲区并开启
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if (a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return -1;
}
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 3, 0);
gl.enableVertexAttribArray(a_Position); // 开启分配
// 获取a_PointSize的存储位置,分配缓冲区并开启
var a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize');
if(a_PointSize < 0) {
console.log('Failed to get the storage location of a_PointSize');
return -1;
}
gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, FSIZE * 3, FSIZE * 2);
gl.enableVertexAttribArray(a_PointSize); // 开启缓冲区分配
return n;
}
首先,我们定义了一个类型化数组verticesSizes,接下来:创建缓冲区对象,绑定之,把数据写入缓冲区对象。然后,我们将verticeSize数组中每个元素的大小(字节数)存储到FSIZE中,稍后将会用到它。类型化数组具有BYTES_PER_ELEMENT属性,可以从中获知数组中每个元素所占的字节数。
注意,这里的verticesSizes参数设置就与单纯的传入顶点坐标有所不同了,因为在缓冲区对象中存储了两种类型的数据:顶点坐标和顶点尺寸。
gl.vertexAttribPointer()的函数规范
stride参数
参数stride表示,在缓冲区对象中,单个顶点的所有数据(这里,就是顶点的坐标和大小)的字节数,也就是相邻两个顶点间的距离,即步进参数。
顶点坐标数据
在缓冲区只含有一种数据时,例顶点的坐标,我们将其设置为0即可。然而,在这里中,当缓冲区中有了多种数据(比如这里的顶点坐标和顶点尺寸)时,我们就需要考虑参数stride的值,如下图所示:
如上图所示,每一个顶点有3个数据值(两个坐标数据和一个尺寸数据),因此应该设置为每项数据大小的三倍,即3×FSIZE(Float32Array中每个元素所占的字节数)。
offset参数
参数offset表示当前考虑的数据项距离首个元素的距离,即偏移参数。在verticesSizes数组中,顶点的坐标数据是放在最前面的,所以应当为0。因此,我们调用gl.vertexAttribArray()函数时,如下所示传入stride参数和offset参数
这样一来,我们就把缓冲区中的那部分顶点坐标数据分配给了着色器中的attribute变量a_Position,并开启了该变量。
顶点尺寸数据
接下来对顶点尺寸数据采取相同的操作:将缓冲区对象中的顶点尺寸数据分配给a_PointSize。然而在这个例子中,缓冲区对象还是原来那个,只不过这次关注的数据不同,我们需要将参数设置为顶点尺寸数据在缓冲区对象中的初始位置。在关于某个顶点的三个值中,前两个是顶点坐标,后一个是顶点尺寸,因此应当设置为FSIZE*2(参见上面stride解释图)。我们如下调用gl.vertexAttribArray()函数,并正确设置stride参数和offset参数
在开启已被分配的缓冲区对象的a_PointSize变量之后,剩下的任务就只有调用gl.drawArrays()进行绘制操作了。
再次执行顶点着色器时,WebGL系统会根据stride和offset参数从缓冲区中正确地抽取出数据,依次赋值给着色器中的各个attribute变量,并进行绘制(如下图所示)。