Web 技术研究所

我一直坚信着,Web 将会成为未来应用程序的主流

WebGL(贰) 着色器程序的使用

  上一篇文章中已经说过了如何创建着色器程序,接着我们就要来使用它。普通的应用程序直接双击运行就能使用,可是着色器程序完全不同,它只是提供了一些接口而已。你可以把它当作dll而不是exe。而且着色器的使用还有显存处理的问题,并不是那么容易的。
  不要忘了我们的初衷是绘图。实际上,绘图就是着色器程序的使用。这里说的使用不是把这个着色器程序丢给显卡就OK了,就像你学电脑的使用并不是只学开关机一样,我们说的使用是实际的操作。当然,首先还是得把这个着色器程序丢给显卡(接上一篇的代码)。 //把这个程序放入显存中
webgl.useProgram(program);
  把程序放入显存中后我们就可以使用它了。具体怎么使用呢?这个其实是我们自己给定义的,WebGL只是提供了这个操作的接口而已。那么我们是怎么定义的呢?还记得上一篇中的着色器程序源码吗?我去把它找出来 //顶点着色器源码
attribute vec4 p;
void main(){
  gl_Position=p;
}
//片段着色器源码
void main(){
  gl_FragColor=vec4(1.0,0.0,0.0,1.0);
}
  在顶点着色器的源码中,我们用“attribute”关键字定义了个“vec4”类型的变量“p”。然后,在它的主过程中把“p”传给了一个内置属性“gl_Position”(注意大小写)。片段着色器的源码则是在它的主过程中调用一个“vec4”,把返回值传给“gl_FragColor”这个内置属性。“vec”其实就是“向量(vector)”的缩写,后面的数字4是指这个向量的阶数。在着色器语言中,向量和矩阵都是作为变量类型的,他们可以直接运算。除了上面的“vec4”,还有“vec3”、“vec2”、“mat3”(“mat”是矩阵[matrix]的缩写)等,许多这样的类型,可以参考着色器语言的文档,里面有详细的介绍。那么,为什么是向量?因为在绘图中最关键的两个东西都可以使用向量来表示——坐标和颜色。用向量表示坐标是基本的数学常识吧,不过这里扩展到了四阶向量。这第四个分量不是第四个维度,只是个齐次项而已,可以理解为缩放。表示坐标时,各个分量一般使用x、y、z、w,来描述。用向量表示颜色也是差不多的道理,因为颜色有R(红)、G(绿)、B(蓝)、A(透明),四个通道。所以使用向量可以很容易的表示。至于矩阵,这可是处理向量的神器,无论是坐标的线性变换还是颜色的光照变换都可以用矩阵和向量相乘来完成,这个在线性代数中有吧。
  我们还说到了“attribute”关键字。实际上着色器语言的变量有三种关键字除了“attribute”外还有“uniform”和“varying”,使用这些关键字定义的变量都有特殊的功能,当然也可以不用这些关键定义纯粹的变量。首先是“attribute”关键字,不要被它的名字迷惑而困扰(我就困扰了很久),使用这个关键字定义的变量在WebGL对象上有个接口,可以为这个变量准备数据源,注意是准备数源而不是传入数据。这个“attribute”名字的原意是指顶点属性,但是要绘图时通常使用多个顶点,所以传入的一般是顶点数组。这样再用“顶点属性”去描述就很容易误解,我觉得应该称为“顶点数据源”。除此之外还要注意,既然是“顶点数据源”他们当然是顶点的数据,也就是说只能在顶点着色器中使用,片段着色器中无法使用“attrbute”关键字来定义变量。这个问题可以用“varying”关键字来解决,还有“uniform”关键字这里就先不细说了,因为这个例子中没用到它们。
  既然之前的程序我们用“attribute”关键字定义了个变量“p”,那么我们就可以在WebGL对象中操作它。现在,我们来给它传入一个数据源。但是在传入数据源之前还有个问题,那就是内存与显存的问题。由于这个着色器程序是运行在显卡上的,我们要给他指定数据源当然不能传个内存指针,我们需要把数据先放入显存中,然后再把这个数据的指针传给这个数据源的接口。WebGL有提供显存的操作的接口,所以我们可以使用这个接口来完成内存复制到显存的操作。只是步骤就麻烦了一些,下面是代码 //定义一个顶点数组,这这里有三个坐标
var dat=[0,1,0,1,  -1,-1,0,1,  1,-1,0,1];

//将JavaScript的哈希数组转换为连续的内存数组
dat=new Float32Array(dat);

//在显存中建立一个数据缓冲区
var buf=webgl.createBuffer();

//设置这个数据缓冲区为当前操作对象
webgl.bindBuffer(webgl.ARRAY_BUFFER,buf);

//把内存中的顶点数组复制到当前操作的数据缓冲区中
webgl.bufferData(webgl.ARRAY_BUFFER,dat,webgl.STATIC_DRAW);

//获取“顶点数据源”接口p的序号
var p=webgl.getAttribLocation(program,"p");

//开启p的数组模式
webgl.enableVertexAttribArray(p);

//把当前工作的数据缓冲区指定给p这个接口
webgl.vertexAttribPointer(p,4,webgl.FLOAT,false,0,0);
  执行了这个代码,我们设置的顶点数组就作为数据源放入了“p”这个接口中。这个代码很麻烦吧?现在来解释一下。首先是创建一个顶点数组,这个里我指定了三个顶点,因为我要画一个三角形。而每个顶点有4个坐标,这是我们之前在顶点着色器中定义的接口是4阶向量的缘故。第二步是把这个JavaScript的数组转换成内存数组,因为JavaScript的数组本身是哈希存储的,它在内存中不是连续的,而转换成命名数组后就会变成连续的内存,这样才能完成后面的内存复制工作。关于这个Float32Array,它是JavaScript的强类型数组,如果没接触过可以看看之前的文章“JavaScript的“强类型数组””。准备好内存数据之后,第三步是创建显存上的数据缓冲区,直接调用createBuffer方法就行。接着是第四步把这个缓冲区设置为当前操作对象,第一个参数是缓冲区的类型,第二个参数才是缓冲区的句柄。虽然WebGL有提供显存的操作,但是可用操作是有严格限制的,我们不能利用这个显存胡作非为,所以在使用时候需要告诉WebGL我们用它来干什么。这里我们用它来存储一个顶点数组,所以使用ARRAY_BUFFER常量,当然还有其它类型可用,这就以后再介绍了。下一步是把内存数组复制到显存中,和上面一样需要指定操作类型,并且最后还需要指定这个数据缓冲区的访问方式,这个以后再解释。到这里,数据就已经妥当放入显存了,剩下的三个步骤是把这个数据指定到顶点着色器的接口上。我们先获取了这个接口的序号,其实简单的程序也可以不用刻意去获取。比如我们这个“p”接口,由于它是顶点着色器的第一个“attribute”,所以它的序号是0。不过在多个接口时一般不会直接用数字,那样比较容易出错。获取这个序号之后还需要开启数组模式,因为我们要绘制的是3个顶点,它是顶点数组。其实很少用到非数组模式,这个以后再介绍,记得绘制数组时候需要开启这个模式就对了。最后才是真正给这个接口指定数据缓冲区,这个操作的参数比较多。前三个参数分别是接口序号、单个顶点的大小、数据类型。后三个参数暂时没用到,以后专门说函数时再来介绍了。
  现在,我们已经为这个接口提供了数据源,这个数据源就是我们的顶点数组,一共有三个顶点。最后,我们只需要调用绘制命令,把它绘制出来即可。下面是绘制的代码。 //绘制
webgl.drawArrays(webgl.TRIANGLES,0,3);
  这个绘制函数有三个参数。第一个参数是图元,这个概念很复杂,暂且理解为要绘制的图形吧;第二个参数是顶点数组的开始下标,我们要用上所有顶点,所以下标是从0开始;第三个参数是顶点数量,我们要绘制三个顶点,所以这里是3。在执行完绘制以后就能看到结果了,它是一个红色的三角形。

  为什么是红色的呢?你仔细看看片段着色器中的代码就会发现了,颜色向量的定义是“vec4(1.0,0.0,0.0,1.0)”,RGBA中R和A为1,也就是说红色值是100%,不透明度是100%,其它东西都为0,这样当然是红色啦。另外,为什么我一直称顶点数据是数据源呢?顶点是一个数组,绘制时会遍历每一个顶点来执行顶点着色器程序,类似数据源的作用。也许其他人的理解方式和我不同,总之我就是这么理解的。
  好了,关于着色器可以说的还有很多,这篇就这样初略的介绍下,以后再来细说。一开始以为和上一篇的内容量差不多的,没想到说明这些问题用了这么多篇幅。文章中有什么错误的地方请指出以便完善,谢谢捧场~ 下面是上一篇文章和这一篇文章所有代码组成的例子。
  WebGL着色器的创建和使用
网名:
34.203.245.*
电子邮箱:
仅用于接收通知
提交 悄悄的告诉你,Ctrl+Enter 可以提交哦
神奇海螺
[查看全部主题]
各类Web技术问题讨论区
发起新主题
本模块采用即时聊天邮件通知的模式
让钛合金F5成为历史吧!
次碳酸钴的技术博客,文章原创,转载请保留原文链接 ^_^