【笔记】《WebGL编程指南》学习-第9章层次模型(3-initShader()函数)

最后,本章来研究一下以前一直使用的辅助函数 initShaders()。以前的所有程度都使用了这个函数,它隐藏了建立和初始化着色器的细节。本书故意将这一部分内容留到最后,是为了确保你在学习 initShaders()函数中的复杂细节时,对 WebGL 已经有了比较深入的交接。掌握这部分内容并不是必须的,直接使用 initShaders()函数也能够编写处相当不错的 WebGL 程序,但如果你确实很想知道 WebGL 原生 API 是如何将字符串形式的 GLSL ES 代码编译为显卡中运行的着色器程序,那么这一节的内容将大大满足你的好奇心。

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())

虽然每一步看上去都比较简单,但放在一起显得复杂了,我们将逐条讨论。首先,你需要知道这里出现了两种对象:着色器对象程序对象

着色器对象:着色器对象管理一个顶点着色器或一个片元着色器。每一个着色器都有一个着色器对象。

程序对象:程序对象是管理着色器对象的容器。WebGL 中,一个程序对象必须包含一个顶点着色器和一个片元着色器。

着色器对象和程序对象间的关系:

这里写图片描述

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


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

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

这里写图片描述

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

这里写图片描述

注意,如果着色器对象还在使用,那么 gl.deleteShader()并不会立刻删除着色器,而是要等到程序对象不再使用该着色器后,才将其删除。


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

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

这里写图片描述


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

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

这里写图片描述

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

这里写图片描述

调用 gl.getShaderParameter()并将参数 pname 指定为 gl.COMPILE_STATUS,就可以检查着色器编译是否成功。

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

这里写图片描述

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

这里写图片描述

代码的第2行出错了,Chrome 浏览器给出的编译错误信息如下图所示:

这里写图片描述

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


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

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

这里写图片描述

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

这里写图片描述

一旦程序对象被创建之后,需要向程序附上两个着色器。


为程序对象分配着色器对象

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

这里写图片描述

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

这里写图片描述


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

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

这里写图片描述

程序对象进行着色器连接操作,目的是保证:

  1. 顶点着色器和片元着色器的 varying 变量同名同类型,且一一对应;
  2. 顶点着色器对每个varying 变量赋了值;
  3. 顶点着色器和片元着色器的同名uniform 变量也是同类型的,无需一一对应,即某些 uniform 变量可以出现在一个着色器中而不出现在另一个中;
  4. 着色器中的 attribute 变量、uniform 变量和 varying 变量的个数没有超过着色器的上学,等等。

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

这里写图片描述

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

这里写图片描述


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

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

这里写图片描述

这个函数的存在使得 WebGL 具有了一个强大的特性,那就是在会之前准备多个程序对象,然后在绘制的时候根据需要切换程序对象。

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

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


initShaders()函数的内部流程
initShaders() 函数将调用 createProgram()函数,后者负责创建一个连接好的程序对象;createProgram()函数则又会调用 loadShader()函数,后者负责创建一个编译好的着色器对象;这3个函数被一次定义在 cuon-utils.js 文件中。initShaders() 函数定义在该文件的顶部,注意该文件中每个函数前面的注释是按照 JavaDoc 的格式编写,它们可以用来自动化地生成文档。

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 属性。

function createProgram(gl, vshader, fshader) {
  // Create shader object
  var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
  var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
  if (!vertexShader || !fragmentShader) {
    return null;
  }

  // Create a program object
  var program = gl.createProgram();
  if (!program) {
    return null;
  }

  // Attach the shader objects
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);

  // Link the program object
  gl.linkProgram(program);

  // Check the result of linking
  var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (!linked) {
    var error = gl.getProgramInfoLog(program);
    console.log('Failed to link program: ' + error);
    gl.deleteProgram(program);
    gl.deleteShader(fragmentShader);
    gl.deleteShader(vertexShader);
    return null;
  }
  return program;
}

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

createProgram()函数自己负责创建程序对象,然后将前面创建的顶点着色器和偏远着色器分配给程序对象。

接着,该函数连接程序对象,并检查是否连接成功。如果连接成功,就会返回程序对象。

最后来看一下 loadShader()函数:

function loadShader(gl, type, source) {
  // Create shader object
  var shader = gl.createShader(type);
  if (shader == null) {
    console.log('unable to create shader');
    return null;
  }

  // Set the shader program
  gl.shaderSource(shader, source);

  // Compile the shader
  gl.compileShader(shader);

  // Check the result of compilation
  var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (!compiled) {
    var error = gl.getShaderInfoLog(shader);
    console.log('Failed to compile shader: ' + error);
    gl.deleteShader(shader);
    return null;
  }

  return shader;
}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值