更新于 

initShaders函数

步骤简述

一句话说清initShaders函数的功能

编译GLSL ES代码,创建和初始化着色器提供WebGL使用。

7个主要步骤

Step1 gl.createShader()

创建着色器对象

Step2 gl.shaderSource()

向着色器对象中填充着色器程序的源代码

Step3 gl.compileShader

编译着色器

Step4 gl.createProgram()

创建程序对象

Step5 gl.attachShader()

为程序对象分配着色器

Step6 gl.linkProgram()

连接程序对象

Step7 gl.useProgram()

使用程序对象

两种对象
一个程序对象对应两个着色器对象
一个程序对象对应两个着色器对象

着色器对象 shader object

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

程序对象 program object

  • 管理 着色器对象 的容器
  • 一个程序对象必须包含一个 顶点着色器 和一个 片元着色器

步骤详述

Step1 创建着色器对象
createShader

参数指定 着色器类型

  • gl.VERTEX_SHADER 顶点着色器
  • gl.FRAGMENT_SHADER 片元着色器
deleteShader

如果着色器对象还在使用,deleteShader不会立刻执行,
而是等待着色器不再被占用后再将其删除。

Step2 指定着色器对象的代码
Step3 编译着色器
compileShader
关于编译的时机

如果调用 shaderSource 重新指定着色器代码,
就一定要使用 compileShader 手动重新编译,
否则WebGL系统中旧代码部分不会被自动替换。

getShaderParameter

是用来 检查着色器的状态的 ,第二个参数pname和返回值有如下关系:

pnamedescriptionreturn
gl.SHADER_TYPE着色器类型gl.VERTEX_SHADER/gl.FRAGMENT_SHADER
gl.DELETE_STATUS是否被删除成功true/false
gl.COMPILE_STATUS是否被编译成功true/false
getShaderInfoLog

编译失败 的情况下使用,
即在 gl.getShaderParameter(shader,gl.COMPILE_STATUS) = false 的情况下。
返回值为编译错误时 WebGL写入着色器的 信息日志(information log)

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

用于 创建程序对象

1
2
let a_Position = gl.getAttribLocation(gl.program,'a_Position');
let u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');

gl.getAttribLocationgl.getUniformLocation 的第一个参数 gl.program 就是 程序对象

gl.deleteProgram()

用于 删除程序对象

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

用于 分配着色器
每个程序必须对应 一个顶点着色器一个片元着色器

把空的着色器附给程序对象也可以

detachShader

用于 解除已分配的着色器

Step6 连接程序对象
linkProgram

用于 连接顶点着色器和片元着色器

连接着色器的目的
  • 对顶点/片元着色器的 varying变量 进行 同名同类型对应
  • 确保顶点着色器的 每个varying都赋了值
  • 对顶点/片元着色器的 uniform变量 进行 同类型对应
  • 确保attribute、uniform、varying变量的个数没有超过 着色器的上限
getProgramParameter

用于检查 着色器是否连接成功

  • 连接成功 向WebGL系统返回一个 二进制可执行模块
  • 连接失败 会在 信息日志 中生产成 连接出错信息
    LINK_STATUS和VALIDATE_STATUS的区别
    • LINK_STATUS 程序是否 成功连接
      • 仅检测 连接阶段
    • VALIDATE_STATUS 程序是否 通过验证
      • 检测 运行阶段
      • 性能开销很大,通常在 调试程序 时这样做
getProgramInfoLog

用于 从信息日志中获取连接出错信息

Step7 告知WebGL系统所使用的程序对象

这也为WebGL提供了一个强大的特性:
只需要准备多个 程序对象
绘制时根据需要 切换程序对象

内部流程

initShaders(gl, vshader, fshader)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function initShaders(gl, vshader, fshader) {
// 1. 创建一个连接好的程序对象
let program = createProgram(gl, vshader, fshader);
if (!program) {
console.log('Failed to create program');
return false;
}

// 2. 告诉WebGL系统来使用这个程序对象
gl.useProgram(program);

// 3. 将程序对象设为gl对象的program属性
gl.program = program;
}
createProgram(gl, vshader, fshader)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function createProgram(gl, vshader, fshader) {
// 1. 创建着色器对象
let vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
let fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
if (!vertexShader || !fragmentShader) {
return null;
}

// 2. 创建程序对象
let program = gl.createProgram();
if (!program) {
return null;
}

// 3. 分配着色器
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);

// 4. 连接着色器
gl.linkProgram(program);

// 5. 检查连接
let linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!linked) {
let 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(gl, type, source)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function loadShader(gl, type, source) {
// 1. 创建着色器对象
let shader = gl.createShader(type);
if (shader == null) {
console.log('unable to create shader');
return null;
}

// 2. 设置着色器源代码
gl.shaderSource(shader, source);

// 3. 编译着色器
gl.compileShader(shader);

// 4. 检查着色器的编译状态
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
let error = gl.getShaderInfoLog(shader);
console.log('failed to compile shader: ' + error);
gl.deleteShader(shader);
return null;
}
return shader;
}