【笔记】《WebGL编程指南》学习-第3章绘制和变换三角形(1-绘制多个点)

目标:绘制多个点组成的图形。本节的例子将会在屏幕上绘制三个红色小点。
结果:
这里写图片描述


前一章有一个示例程序 ClickedPoints,它在鼠标点击的位置会致点。ClickedPoints 将所以后的点的坐标数据存储在一个JS数组 g_points[]中,然后使用了一个循环遍历该数组,每次遍历就向着着色器传入一个点,并调用gl.drawArrays()将这个点绘制出来。

for(var i = 0; i < len; i+=2){
    //将点的位置传递到变量中
    gl.vertexAttrib3f(a_Position, g_points[i], g_points[i+1], 0.0);

    //绘制点
    gl.drawArrays(gl.POINTS, 0, 1);
}

显然,这种方法只能绘制一个点。对那些由多个顶点组成的图形,比如三角形、矩形和立方体来说,你需要一次性地将图形的顶点全部传入顶点着色器,然后才能把图形画出来。

WebGL 提供了一种很方便的机制,即缓冲区对象,它可以一次性地向着色器传入多个顶点的数据。缓冲区对象是 WebGL 系统中的一块内存区域,我们可以一次性地向缓冲区对象中填充大量的顶点数据,然后将这些数据保存在其中,供顶点着色器使用。


示例程序

本例子的流程与第2章中的 ClickPoints.js 与 ColoredPoints.js 的流程基本一致,唯一的不同就是加入了一个新的步骤:设置点的坐标信息。

这里写图片描述

MultiPoints.js

//顶点着色器程序
var VSHADER_SOURCE =
    'attribute vec4 a_Position;'+
    'void main(){'+
    'gl_Position=a_Position;'+
    'gl_PointSize=10.0;'+
    '}';

//片元着色器程序
var FSHADER_SOURCE=
    'void main(){'+
    'gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);'+
    '}';

function main() {
    //获取canvas元素
    var canvas = document.getElementById("webgl");
    if(!canvas){
        console.log("Failed to retrieve the <canvas> element");
        return;
    }

    //获取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 initialize shaders.");
        return;
    }

    //设置顶点位置
    var n = initVertexBuffers(gl);
    if (n < 0) {
        console.log('Failed to set the positions of the vertices');
        return;
    }


    //指定清空<canvas>颜色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    //清空<canvas>
    gl.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; //点的个数

    //创建缓冲区对象
    var vertexBuffer = gl.createBuffer();
    if(!vertexBuffer){
        console.log("Failed to create thie buffer object");
        return -1;
    }

    //将缓冲区对象保存到目标上
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

    //向缓存对象写入数据
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    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;
    }

    //将缓冲区对象分配给a_Postion变量
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

    //连接a_Postion变量与分配给它的缓冲区对象
    gl.enableVertexAttribArray(a_Position);

    return n;
}

新加入的函数 initVertexBuffers()的任务是创建定点缓冲区对象,并将多个顶点的数据保存在缓冲区中,然后将缓冲区传给顶点着色器。

函数的返回值是待绘制顶点的数量,保存在变量n中。注意,如果函数内发生错误,返回的负值。

示例程序仅调用了一次 gl.drawArrays()函数就完成了绘图操作,这与之前的不同。调用这个函数时,本例传入的第3个参数是 n,而不是1。

//绘制三个点
gl.drawArrays(gl.POINTS, 0, n);

因为我们在 initVertexBuffers()函数中利用缓冲区对象向顶点着色器传输了多个顶点的数据,所以还需要通过第3个参数告诉 gl.drawArrays()函数需要绘制多少个顶点。


使用缓冲区对象

先创建一个缓冲区,然后向其写入顶点数据,你就能一次性地向顶点着色器中传入多个顶点的 attribute 变量的数据。

这里写图片描述

在示例程序中,向缓冲区对象写入数据是一种特殊的JS数组(Float32Array):

 var vertices = new Float32Array([
    0.0, 0.5, -0.5, -0.5, 0.5, -0.5
]);

使用缓冲区对象向点点着色器传入多个顶点的数据,需要遵循以下五个步骤:

  1. 创建缓冲区对象(gl.creatBuffer())。
  2. 绑定缓冲区对象(gl.bindBuffer())。
  3. 将数据写入缓冲区对象(gl.bufferData())。
  4. 将缓冲区对象分配给一个 attribute 变量(gl.vertexAttribPointer())。
  5. 开启attribute 变量(gl.enableVertexAttribArray)。

这里写图片描述

创建缓冲区对象

在使用缓冲区对象之前,你必须创建它。

 //创建缓冲区对象
var vertexBuffer = gl.createBuffer();

下面的图示意了该方法执行前后 WebGL 系统的中间状态,上面一张图是执行前的状态,下面一张图是执行后的状态。执行该方法的结果就是,WebGL 系统中多了一个新创建出来的缓冲区对象。

这里写图片描述

下面是 gl.creatBuffer()的函数规范

这里写图片描述

绑定缓冲区

创建缓冲区之后的第2个步骤就是将缓冲区对象绑定到 WebGL 系统中已经存在的“目标”上。这个“目标”表示缓冲区对象的用途(在这里,就是向顶点着色器提供传给 attribute 变量的数据),这样 WebGL 才能正确处理其中的内容:

//将缓冲区对象保存到目标上
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

下面是gl.bindBuffer( )的函数规范

这里写图片描述

在示例程序中,我们将缓冲区对象绑定到了 gl.ARRAY_BUFFER 目标上,缓冲区对象中存储着的关于顶点的数据。在这个函数执行完毕后,WebGL 系统内部状态发生了改变:

这里写图片描述

接下来,我们就可以向缓冲区对象中写入数据了。

向缓冲区对象写入数据

第3步是,开辟空间并向缓冲区中写入数据。

//向缓存对象写入数据
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

该方法的效果是,将第2个参数 vertices 中的数据写入了绑定到第1个参数 gl.ARRAY_BUFFER 上的缓冲区对象。我们不能直接向缓冲区写入数据,而只能向“目标”写入数据,所以要向缓冲区写数据,必须先绑定。该方法执行后,WebGL系统的内部状态如图:

这里写图片描述

从上图你可以看到,定义在JS程序中的数据被写入了绑定在 gl.ARRAY_BUFFER 上的缓冲区对象。下面是对 gl.bufferData()的范围。

这里写图片描述

现在我们来看看 gl,bufferData()方法向缓冲区中传入了什么数据。该方法使用了一个特殊的数组 vertices 将数据传给顶点着色器。我们是用 new 运算符,并以<第一个顶点的x坐标和y坐标><第二个顶点的x坐标和y坐标>,等等的形式创建这个数组:

如你所见,我们使用了 Float32Array 对象,而不是JS中更常见的 Array 对象。这是因为JS中通用的数组 Array 是一种通用的类型,即可以在里面存储数字也可以存储字符串,而并没有对“大量元素都是同一种类型”这种情况进行优化。为了解决这个问题,WebGL 引入了类型化数组,Float32Array 就是其中之一。

类型化数组

为了绘制三维图形,WebGL 通常需要同时处理大量相同类型的数据,例如顶点的坐标和颜色数据,为了优化性能,WebGL 为每种基本数据类型引入了一种特殊的数组(类型化数组)。浏览器事先知道数组中的数据类型,所以处理起来也更加有效率。

例子中的Float32Array 就是一种类型化数组,通常用来存储顶点的坐标或颜色数据。应当牢记,WebGL 中的很多操作都要用到类型化数组,比如 gl.bufferData()中的第2个参数 data。

下表列举了各种可用的类型化数组。

这里写图片描述

与JS中的 Array 数组相似,类型化数组也有一系列方法和属性,如下表所示。注意,与普通的 Array 数组不同,类型化数组不支持 push()和 pop()方法。

这里写图片描述

和普通的数组一样,类型化数组可以通过 new 运算符调用构造函数并传入数据而被创造出来。注意,创建类型化数组的唯一方法就是使用 new 运算符,不能使用 []运算符。

此外,你也可以通过指定数组元素的个数来创建一个空的类型化数组,例如:

var vertices = new Float32Array(4);

到目前为止,你就完成了建立和使用穿冲去的前三个步骤(创建缓冲区,绑定缓冲区对象到目标,向缓冲区对象中写入数据)。我们们来看看在接下俩的两步中,WebGL 是如何真正使用缓冲区来进行绘图的。

向缓冲区对象分配给 attribute 变量

如第2章所述,你可以使用 gl.vertexAttrib[1234]f系列函数为 attribute 变量分配值。但是,这些方法一次只能向 attribute 变量分配一个值。而现在,你需要将整个数组中的所有值一次性地分配给一个 attribute 变量。

gl.vertexAttribPointer()方法解决了这个问题,它可以将整个缓冲区对象(实际上是缓冲区对象的引用或指针)分配给 attribute 变量。示例程序将缓冲区对象分配给 attribute 变量 a_Position。

//将缓冲区对象分配给a_Postion变量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

gl.vertexAttribPointer()的规范如下所示:
这里写图片描述

执行完第4步后,我们就将整个缓冲区对象分配给了 attribute 变量,为 WebGl 绘图进行的准备工作(即向 location 处的 attribute 变量传入缓冲区)就差最后一步了:进行最后的“开启”,使这次分配真正生效,如下图所示:

这里写图片描述

开启 attribute 变量

为了使顶点着色器能够访问缓冲区内的数据,我们需要使用 gl.enableVertexAttribArray()方法来开启 attribute 变量。

//连接a_Postion变量与分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Position);

注意,虽然函数的名称似乎表示该函数是用来处理“顶点函数”的,但实际上它处理的对象是缓冲区。

这里写图片描述

当你执行 gl.enableVertexAttribArray()并传入一个已经分配好缓冲区的 attribute 变量后,我们就开启了该变量,也就是说,缓冲区对象和 attribute 变量之间的连线就真正建立起来了。

这里写图片描述

现在你只需要orange顶点着色器运行起来,它会自动将缓冲区中的顶点画出来。如第2章,你使用 gl.drawArrays()方法绘制了一个点,现在你要画多个点,所用的方法仍是 gl.drawArrays()方法,但使用的是方法中的第2个和第3个参数。

注意,开启 atrribute 变量后,你就不能再用gl.vertexAtrrib[1234]f 向它传数据了,除非你显式地关闭该 attribute 变量。实际上,你无法同时使用这两个函数。

gl.drawArrays()的第2个和第3个参数

在对 gl.drawArray()作进一步详细说明之前,看一下他的规范:

这里写图片描述

这个示例程序如下调用这个方法:

gl.drawArrays(gl.POINTS, 0, n);

当程序运行到这个方法时,实际上顶点着色器执行了count次,我们通过存储在缓冲区中的顶点坐标数据被依次传给 attribute 变量,如下图所示

这里写图片描述

注意,每次执行顶点着色器,a_Postion 的 z 和 w 分量值都会被自动设为0.0或1.0,因为 a_Position 需要4个分量,而你只提供了两个。


运行出结果:
这里写图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值