WebGL 初始化着色器

目录

前言

初始化着色器的7个步骤

创建着色器对象(gl.createShader()) 

gl.createShader()规范

gl.deleteShader()规范

 指定着色器对象的代码(gl.shaderSource())

gl.shaderSource()规范

编译着色器(gl.compileShader()) 

gl.compileShader()规范​编辑

gl.getShaderParameter()规范

gl.getShaderInfoLog()规范

创建程序对象(gl.createProgram())

gl.createProgram()规范

为程序对象分配着色器对象(gl.attachShader()) 

gl.attachShader()规范

gl.detachShader()规范

连接程序对象(gl.linkProgram()) 

gl.getProgramPara-meters()规范

gl.getProgramInfoLog()规范

告知WebGL系统所使用的程序对象(gl.useProgram()) 

gl.usePorgram()规范

initShaders()函数的内部流程

initShaders()函数

createProgram()函数

loadShader()函数


前言

本章来研究一下以前一直使用的辅助函数initShaders()。以前的所有程序都使用了这个函数,它隐藏了建立和初始化着色器的细节。但如果你确实很想知道WebGL原生API是如何将字符串形式的GLSL ES代码编译为显卡中运行的着色器程序,那么这一节的内容将大大满足你的好奇心。

初始化着色器的7个步骤

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中,一个程序对象必须包含一个顶点着色器和一个片元着色器。 

着色器对象和程序对象的关系图

WebGL 初始化着色器_第1张图片 

下面就来逐个讨论上述7个步骤。 

创建着色器对象(gl.createShader()) 

所有的着色器对象都必须通过调用gl.createShader()来创建。

gl.createShader()规范

WebGL 初始化着色器_第2张图片

gl.createShader()函数根据传入的参数创建一个顶点着色器或者片元着色器。如果不再需要这个着色器,可以使用gl.deleteShader()函数来删除着色器。

gl.deleteShader()规范

WebGL 初始化着色器_第3张图片 注意,如果着色器对象还在使用(也就是说已经使用gl.attachShader()函数使之附加在了程序对象上,我们马上就会讨论该函数),那么gl.deleteShader()并不会立刻删除着色器,而是要等到程序对象不再使用该着色器后,才将其删除。 

 指定着色器对象的代码(gl.shaderSource())

通过gl.shaderSource()函数向着色器指定GLSL ES源代码。在JavaScript程序中,源代码以字符串的形式存储

gl.shaderSource()规范

WebGL 初始化着色器_第4张图片

编译着色器(gl.compileShader()) 

向着色器对象传入源代码之后,还需要对其进行编译才能够使用。GLSL ES语言和JavaScript不同而更接近C或C++,在使用之前需要编译成二进制的可执行格式,WebGL系统真正使用的是这种可执行格式。使用gl.compileShader()函数进行编译。注意,如果你通过调用gl.shaderSource(),用新的代码替换掉了着色器中旧的代码,WebGL系统中的用旧的代码编译出的可执行部分不会被自动替换,你需要手动地重新进行编译。

gl.compileShader()规范WebGL 初始化着色器_第5张图片

当调用gl.compileShader()函数时,如果着色器源代码中存在错误,那么就会出现编译错误。可以调用gl.getShaderParameter()函数来检查着色器的状态。

gl.getShaderParameter()规范

WebGL 初始化着色器_第6张图片 调用gl.getShaderParameter()并将参数pname指定为gl.COMPILE_STATUS,就可以检查着色器编译是否成功。 

如果编译失败,gl.getShaderParameter()会返回false,WebGL系统会把编译错误的具体内容写入着色器的信息日志(information log),我们可以通过gl.getShaderInfoLog()来获取之。

gl.getShaderInfoLog()规范

WebGL 初始化着色器_第7张图片

虽然日志信息的具体格式依赖于浏览器对WebGL的实现,但大多数WebGL系统给出的错误信息都会包含代码出错行的行号。比如,如果你试图编译如下这样的一个着色器:

WebGL 初始化着色器_第8张图片 代码的第2行出错了(应该是gl_而不是gl.),Chrome浏览器给出的编译错误信息如图9.11所示(cuon-utils.js将错误信息字符串打印在控制台上):

WebGL 初始化着色器_第9张图片

可见,错误信息告诉我们:第2行的变量gl未被定义。

cuon-utils.js:88表示这条错误是在JavaScript文件cuon-utils.js的第88行,即定义initShaders()函数之处检测到的,该函数调用了gl.getShaderInfolog()函数。 

创建程序对象(gl.createProgram())

如前所述,程序对象包含了顶点着色器和片元着色器,可以调用gl.createProgram()来创建程序对象。事实上,之前使用程序对象,gl.getAttribLocation()函数和gl.getUniformLocation()函数的第1个参数,就是这个程序对象。

gl.createProgram()规范

WebGL 初始化着色器_第10张图片

类似地,可以使用gl.deleteProgram()函数来删除程序对象。

WebGL 初始化着色器_第11张图片 一旦程序对象被创建之后,需要向程序附上两个着色器。 

为程序对象分配着色器对象(gl.attachShader()) 

WebGL系统要运行起来,必须要有两个着色器:一个顶点着色器和一个片元着色器。可以使用gl.attachShader()函数为程序对象分配这两个着色器。

gl.attachShader()规范

WebGL 初始化着色器_第12张图片

着色器在附给程序对象前,并不一定要为其指定代码或进行编译(也就是说,把空的着色器附给程序对象也是可以的)。类似地,可以使用gl.detachShader()函数来解除分配给程序对象的着色器。 

gl.detachShader()规范

WebGL 初始化着色器_第13张图片

连接程序对象(gl.linkProgram()) 

在为程序对象分配了两个着色器对象后,还需要将(顶点着色器和片元)着色器连接起来。使用gl.linkProgram()函数来进行这一步操作。

WebGL 初始化着色器_第14张图片

程序对象进行着色器连接操作,目的是保证:(1)顶点着色器和片元着色器的varying变量同名同类型,且一一对应;(2)顶点着色器对每个varying变量赋了值;(3)顶点着色器和片元着色器中的同名uniform变量也是同类型的(无需一一对应,即某些uniform变量可以出现在一个着色器中而不出现在另一个中);(4)着色器中的attribute变量、uniform变量和varying变量的个数没有超过着色器的上限,等等。 

在着色器连接之后,应当检查是否连接成功。通过调用gl.getProgramPara-meters()函数来实现。

gl.getProgramPara-meters()规范

WebGL 初始化着色器_第15张图片

如果程序已经成功连接,我们就得到了一个二进制的可执行模块供WebGL系统使用。如果连接失败了,也可以通过调用gl.getProgramInfoLog()从信息日志中获取连接出错信息。 

gl.getProgramInfoLog()规范

WebGL 初始化着色器_第16张图片

告知WebGL系统所使用的程序对象(gl.useProgram()) 

最后,通过调用gl.usePorgram()告知WebGL系统绘制时使用哪个程序对象。

gl.usePorgram()规范

WebGL 初始化着色器_第17张图片 这个函数的存在使得WebGL具有了一个强大的特性,那就是在绘制前准备多个程序对象,然后在绘制的时候根据需要切换程序对象。

这样,建立和初始化着色器的任务就算完成了。如你所见,initShaders()函数隐藏了大量的细节,我们可以放心地使用该函数来创建和初始化着色器,而不必考虑这些细节。本质上,在该函数顺利执行后,顶点着色器和片元着色器就已经就位了,只需要调用gl.drawArrays()或gl.drawElements()来使整个WebGL系统运行起来。

现在,你对上述诸多WebGL原生API函数已经有了不错的理解,下面来看一下cuon-utils.js中initShaders()函数的内部流程。

initShaders()函数的内部流程

initShaders()函数将调用createProgram()函数,后者负责创建一个连接好的程序对象;createProgram()函数则又会调用loadShader()函数,后者负责创建一个编译好的着色器对象;这3个函数被依次定义在cuon-utils.js文件中。initShaders()函数定义在该文件的顶部,如下所示

initShaders()函数

function initShaders(gl, vshader, fshader) {
  // 创建程序对象
  var program = createProgram(gl, vshader, fshader);
  if (!program) {
    console.log('Failed to create program');
    return false;
  }

  gl.useProgram(program);
  gl.program = program;

  return true;
}

initShaders()函数本身很简单,首先调用createProgram()函数创建一个连接好的程序对象,然后告诉WebGL系统来使用这个程序对象,最后将程序对象设为gl对象的program属性。

createProgram()函数

WebGL 初始化着色器_第18张图片

 createProgram()函数通过调用loadShader()函数,创建顶点着色器和片元着色器的着色器对象(第31和32行)。loadShader()函数返回的着色器对象已经指定过源码并已经成功编译了。

createProgram()函数自己负责创建程序对象(第38行),然后将前面创建的顶点着色器和片元着色器分配给程序对象(第44和45行)。

接着,该函数连接程序对象(第48行),并检查是否连接成功(第51行)。如果连接成功,就返回程序对象(第60行)。

最后看一下loadShader()函数,如下所示。该函数被createProgram()函数调用。

loadShader()函数

WebGL 初始化着色器_第19张图片

loadShader()函数首先创建了一个着色器对象(第72行),然后为该着色器对象指定源代码(第79行),并进行编译(第82行),接着检查编译是否成功(第85行),如果成功编译,没有出错,就返回着色器对象。

你可能感兴趣的:(WebGL,webgl,着色器)