【《WebGL编程指南》读书笔记——着色器和程序对象的准备】

本文为《WebGL编程指南》第九章下半部分读书笔记
总目录链接:https://blog.csdn.net/floating_heart/article/details/124001572

着色器(shader)和程序(program)对象:initShaders()扮演的角色

在此之前,我们一直使用书中封装的initShaders()代替我们执行着色器和着色器程序对象的初始化,在这里,基于之前的知识,我们已经能够理解initShaders()每一步的作用了,在笔者看来,其与缓冲区存储数据分配变量有异曲同工之妙。本节,我们结合书中的内容和函数源码,逐渐了解过程的细节。

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(type):
创建由type指定的着色器对象。
参数:
type: 指定创建着色器的类型,gl.VERTEX_SHADER表示顶点着色器,gl.FRAGMENT_SHADER表示片元着色器。
返回值:
non-null: 创建的着色器
null: 创建失败
错误:
INVALID_ENUM: type参数既不是gl.VERTEX_SHADER也不是gl.FRAGMENT_SHADER

gl.createShader()函数根据传入的参数创建一个顶点着色器或片元着色器。类似的,可以使用gl.deleteShader()函数来删除着色器。

gl.deleteShader(shader):
删除shader指定的着色器对象。
参数:
shader: 待删除的着色器对象。
返回值:
错误:

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


第二步:指定着色器对象的代码(gl.shaderSource())

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

gl.shaderSource(shader, source):
将source指定的字符串形式的代码传入shader指定的着色器。如果之前已经向shader传入过代码了,旧的代码将会被替换掉。
参数:
shader: 指定需要传入代码的着色器对象。
source: 指定字符串形式的代码。
返回值:
错误:


第三步:编译着色器(gl.compileShader())

GLSL ES语言更接近于C或C++,在使用前需要编译成二进制的可执行文件,WebGL系统真正使用的是这种可执行格式。

gl.compileShader(shader):
编译shader指定的着色器中的源代码。
参数:
shader: 待编译的着色器。
返回值:
错误:

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

**gl.getShaderParameter **(shader, pname):
获取shader指定的着色器中,pname指定的参数信息。
参数:
shader: 指定待获取参数的着色器。
pname: 指定待获取参数的类型,可以是gl.SHADER_TYPE、gl.DELETE_STATUS或者gl.COMPILE_STATUS
返回值:
根据pname的不同,返回不同的值
gl.SHADER_TYPE: 返回是顶点着色器(gl.VERTEX_SHADER)还是片元着色器(gl.FRAGMENT_SHADER)
gl.DELETE_STATUS: 返回着色器是否被删除成功(true或false)
gl.COMPILE_STATUS: 返回着色器是否被编译成功(true或false)
错误:
INVALID_ENUM: pname的值无效

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

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

gl.getShaderlnfoLog(shader):
获取shader指定的着色器的信息日志。
参数:
shader: 指定待获取信息日志的着色器。
返回值:
non-null: 包含日志信息的字符串
null: 没有编译错误
错误:

日志信息的具体格式依赖于浏览器对WebGL的实现,大多数WebGL系统给出的错误信息都会包含代码出错行的行号。比如我们常见的:

Failed to compile shader: ERROR: 0:4: 'uniformmat4' : syntax error

代表第四行’uniformmat4’出现问题。通过上面的介绍我们能够了解,日志是记录在着色器中的,只看日志不能区分是哪个着色器出现问题(需要知道日志属于哪个着色器)。


第四步:创建程序对象(gl.createProgram())

程序对象包含了顶点着色器和片元着色器,我们之前使用的获取存储地址的函数gl.getAttribLocation()gl.getUniformLocation()的第一个参数就是程序对象。WebGL中使用gl.createProgram()创建程序对象,函数规范如下:

gl.createProgram():
创建程序对象。
参数:
返回值:
non-null: 新创建的程序对象
null: 创建失败
错误:

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

gl.deleteProgram(program):
删除program指定的程序对象,如果该程序对象正在被使用,则不立即删除,而是等它不再被使用后再删除。
参数:
program: 指定待删除的程序对象。
返回值:
错误:


第五步:为程序对象分配着色器(gl.attachShader())

WebGL系统要运行起来,必须要有两个着色器:一个顶点着色器和一个片元着色器。gl.attachShader()将两个着色器分配给程序对象。

gl.attachShader(program, shader):
将shader指定的着色器对象分配给program指定的程序对象。
参数:
program: 指定程序对象
shader: 指定着色器对象
返回值:
错误:
INVALID_OPERATION: shader已经被分配给了program

着色器在附给程序对象前,并不一定要为其指定代码或进行编译(也就是说,把空的着色器附给程序对象也是可以的)。

类似地,可以使用gl.deleteShader ()函数来解除分配给程序对象的着色器。

gl.detachShader(program, shader):
取消shader指定的着色器对象对program指定的程序对象的分配。
参数:
program: 指定程序对象
shader: 指定着色器对象
返回值:
错误:
INVALID_OPERATION: shader没有被分配给program


第六步:连接程序对象(gl.linkProgram())

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

gl.linkProgram(program):
连接program指定的程序对象中的着色器。
参数:
program: 指定程序对象
返回值:
错误:

程序对象对着色器进行连接,是为了:

  • 顶点着色器和片元着色器的varying变量同名同类型,且一一对应;
  • 顶点着色器对每个varying变量赋了值;
  • 顶点着色器和片元着色器中的同名uniform变量也是同类型的(无需一一对应);
  • 着色器中的attribute变量、uniform变量和varying变量的个数没有超过着色器的上限

在这里插入图片描述

在着色器连接之后,应当检查是否连接成功。

与着色器部分类似,通过调用gl.getProgramParameter()函数来实现。

gl.getProgramParameter (program, pname):
获取program指定的程序对象中pname指定的参数信息。
参数:
program: 指定的程序对象。
pname: 指定待获取参数的类型,可以是g.DELETE_STATUS、 gl.LINK_STATUS、 gl.VALIDATE_STATUS、gl.ATTACHED_SHADERS、gl.ACTIVE_ATTRIBUTES或gl.ACTIVE_UNIFORMS
返回值:
根据pname的不同,返回不同的值
gl.DELETE_STATUS: 程序是否已被删除(true或false)
gl.LINK_STATUS: 程序是否已经成功连接(true或false)
gl.VALIDATE_STATUS: 程序是否已经通过验证(true或false)
gl.ATTACHED_SHADERS: 已被分配给程序的着色器数量
gl.ACTIVE_ATTRIBUTES: 顶点着色器中attribute变量的数量
gl.ACTIVE_UNIFORMS: 程序中uniform变量的数量
错误:
INVALID_ENUM: pname的值无效

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

gl.getProgramlnfoLog(program):
连接program指定的程序对象的信息日志。
参数:
program: 指定待获取信息日志的程序对象
返回值:
包含日志信息的字符串
错误:

程序对象即使连接成功了,也有可能运行失败,比如没有为取样器分配纹理单元。这些错误是在运行阶段而不是连接阶段产生的。在运行阶段进行错误检查的性能开销很大,所以通常只在调试程序时这样做。


第七步:使用程序对象(gl.useProgram())

或者说,告知WebGL系统所使用的程序对象。

经过之前的步骤,完整的程序对象已经准备好,最后,通过调用gl.useProgram()告知WebGL系统绘制时使用哪个程序对象。

gl.useProgram(program):
告知WebGL系统绘制时使用program指定的程序对象。
参数:
program: 指定待使用的程序对象
返回值:
错误:

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


initShaders()函数的内部流程

我们了解了着色器和程序对象初始化的流程,本节看一下initShaders()的组织方式。

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

下面复制源码给出分析。

  • initShaders()的主函数如下。

首先通过createProgram ()创建连接好的程序对象,然后(告知WebGL)使用程序对象。将程序对象挂载在上下文中方便后续调用。

/**
 * Create a program object and make current
 * @param gl GL context
 * @param vshader a vertex shader program (string)
 * @param fshader a fragment shader program (string)
 * @return true, if the program object was created and successfully made current 
 */
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;
}
  • createProgram ()函数内容如下

通过loadShader()获得编译好的顶点着色器和片元着色器;创建程序对象,将两个着色器分配到程序对象中;连接程序对象(内部着色器):连接失败的话,打印程序对象信息日志,删除程序对象和两个着色器(删除顺序符合逻辑,delete操作立即生效),返回null;否则返回创建的程序对象。

/**
 * Create the linked program object
 * @param gl GL context
 * @param vshader a vertex shader program (string)
 * @param fshader a fragment shader program (string)
 * @return created program object, or null if the creation has failed
 */
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;
}
  • loadShader()函数如下

创建着色器;编译着色器;获取编译信息;编译失败时,打印日志信息,删除着色器,返回null;否则返回创建的着色器。

/**
 * Create a shader object
 * @param gl GL context
 * @param type the type of the shader object to be created
 * @param source shader program (string)
 * @return created shader object, or null if the creation has failed.
 */
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;
}

流程总结

最后,用流程图的形式,展示着色器和程序对象初始化的过程,并与initShaders()中的内容进行对照

https://www.processon.com/view/link/6264cce91e085332a3b7fae6

在这里插入图片描述

如果了解了着色器和程序对象的结构(本节开始所述),这样的初始化流程还是比较容易理解的。

着色器和程序对象的初始化,和缓冲区相关操作类似,同样是创建、加载数据、分配。不同的是,此处着色器需要编译和检查编译结果、程序对象需要连接和检查连接结果。WebGL系统的设计风格十分统一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值