这一节,我们研究一下以前一直使用的辅助函数initShader()。
之前教程当中故意把这个函数中留到最后讲解,是为了确保学习initShaders()函数中的复杂细节时,对WebGL已经有了比较深入的了解。掌握这一部分内容不是必须的,直接使用initShaders()函数也能够编写出相当不错的WebGL程序。
initShaders()函数的作用是,编译GLSL ES代码,创建和初始化着色器供WebGL使用。具体分为以下7个步骤:
(1)创建着色器对象(gl.createShader())
(2)向着色器对象中填充着色器程序的源代码(gl.shaderSource())
(3)编译着色器(gl.compileShader())
(4)创建程序对象(gl.createProgram())
(5)为程序对象分配着色器(gl.attachShader())
(6)链接程序对象(gl.linkProgram())
(7)使用程序对象(gl.useProgram())
在这里出现了两种对象:着色器对象(shader object)和程序对象(program object)。
着色器对象:着色器对象管理一个顶点着色器或一个片元着色器。每一个着色器都有一个着色器对象。
程序对象:程序对象是管理着色器对象的容器。WebGL中,一个程序对象必须包含一个顶点着色器和一个片元着色器。
创建着色器对象(gl.createShader())
所有的着色器对象都必须通过调用gl.createShader()来创建
gl.createShader()函数根据传入的参数创建一个顶点着色器或者片元着色器。如果不需要这个着色器,可以使用gl.deleteShader()函数来删除着色器。
注意,如果着色器对象还在使用(也就是说已经使用gl.attachShader()函数使之附加在了程序对象上),那么gl.deleteShader()并不会立刻删除着色器,二十要等到程序对象不再使用该着色器后,才将其删除。
指定着色器对象的代码(gl.shaderSource())
通过gl.shaderSource()函数向着色器指定GLSL ES源代码。
编译着色器(gl.compileShader())
向着色器对象传入源代码之后,还需要对其进行编译才能够使用。GLSL ES语言和JavaScript不同而更接近C或C++,在使用之前需要编译成二进制的可执行格式,WebGL系统真正使用的是这种可执行格式。使用gl.compileShader()函数进行编译。注意,如果你通过调用gl.shaderSource(),用新的代码替换掉了着色器中旧的代码,WebGL系统中的用旧的代码编译出的可执行部分不会被自动替换,你需要手动地重新进行编译。
当调用gl.compileShader()函数时,如果着色器源代码中存在错误,那么就会出现编译错误。可以调用gl.getShaderParameter()函数来检查着色器的状态。
调用gl.getShaderParameter()并将参数pname指定为gl.COMPILE_STATUS,就可以检查着色器编译是否成功。
如果编译失败,gl.getShaderParameter()会返回false,WebGL系统会把编译错误的具体内容写入着色器的信息日志(information log),我们可以通过gl.getShaderInfoLog()来获取。
虽然日志信息的具体格式依赖于浏览器对WebGL的实现,但大多数WebGL系统给出的错误信息都会包含代码出错行的行号。比如,如果你视图编译如下这样的一个着色器:
//片元着色器
var FSHADER_SOURCE = "" +
"varying vec4 v_Color;\n" +
"void main(){" +
" gl.FragColor = v_Color;\n" +
"}\n";
代码的第二行出错了,在控制台显示的错误信息是:
创建程序对象(gl.createProgram())
程序对象包含了顶点着色器和片元着色器,可以调用gl.createProgram()来创建程序对象。事实上,之前使用程序对象,gl.getAttribLocation()和gl.getUniformLocation()函数的第一个函数,就是这个程序对象。
可以使用gl.deleteProgram()函数来删除程序对象。
一旦程序对象被创建之后,需要向程序附上两个着色器。
为程序对象分配着色器对象(gl.attachShader())
WebGL系统要运行起来,必须要有两个着色器:一个顶点着色器和一个片元着色器。可以使用gl.attachShader()函数为程序对象分配这两个着色器。
.
着色器在附给程序对象前,并不一定要为其制定代码或进行编译(也就是说,把空的着色器附给程序对象也是可以的)。类似的,可以使用gl.detachShader()函数来解除分配给程序对象的着色器。
连接程序对象(gl.linkProgram())
在为程序对象分配了两个着色器对象后,还需要将顶点着色器和片元着色器连接起来。使用gl.linkProgram()函数来进行这一步操作。
程序对象进行着色器连接操作,目的是保证:
(1)顶点着色器和片元着色器的varying变量同名同类型,且一一对应
(2)顶点着色器对每个varying变量赋了值
(3)顶点着色器和片元着色器中的同名uniform变量也是同类型的(无需一一对应,即某些uniform变量可以出现在一个着色器中而不出现在另一个中)
(4)着色器中的attribute变量、uniform变量和varying变量的个数没有超过着色器的上线,等等。
在着色器连接之后,应当检查是否连接成功。通过调用gl.getProgramPara-meters()函数来实现。(程序对象即使连接成功了,也有可能运行失败,比如没有为取样器分配纹理单元。这写错误是在运行阶段而不是连接阶段产生的。在运行阶段进行错误检查的性能开销很大,所以通常只在调试程序时这样做。)
如果程序已经成功连接,我们就得到了一个二进制的可执行模块供WebGL系统使用。如果连接失败了,也可以通过调用gl.getProgramInfoLog()从信息日志中获取连接出错信息。
告知WebGL系统所使用的程序对象(gl.useProgram())
最后,通过调用gl.useProgramUI告知WebGL系统绘制时使用哪个程序对象。
这个函数的存在使得WebGL具有了一个强大的特性,那就是在绘制前准备多个程序对象,然后在绘制的时候根据需要切换对象。