《WebGL 编程指南》 学习笔记(二):着色器变量传递

1,193 阅读5分钟

前言

上一节,我们了解了如何画一个点,学习了绘制点用到的顶点着色器和片元着色器,以及绘制的核心方法gl.drawArrays()

但这只是绘制了固定位置的点,并且将点的位置写死(硬编码)在了顶点着色器中,这样易于理解,但是不够灵活。这一节中,我们将学习把点的位置传给顶点着色器的方法。

数据传递

目前,将js程序中的数据传给顶点着色器,有两种方式:attribute 变量uniform 变量

  • attribute 变量 传输的是与顶点相关的数据。
  • uniform 变量 传输的是对于所有顶点都相同的数据,也就是说与顶点无关的数据

所以,具体使用哪种方式传递数据,取决于数据类型。这节用到的点的位置,明显和顶点有关,所以使用 attribute 变量来传递数据。

下面我们直接看一下整个程序是什么样的。

示例一

// 顶点着色器
var VSHADER_SOURCE = `
  attribute vec4 a_Position;
  void main() {
    gl_Position = a_Position;  // 设置顶点坐标
    gl_PointSize = 10.0;       // 设置点的大小
  }
  `;

// 片元着色器
var FSHADER_SOURCE = `
  void main() {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 设置颜色
  }
  `;

function main() {
    // 获取 <canvas> 元素
    var canvas = document.getElementById('webgl');

    // 获取 WebGL 绘图上下文
    var gl = getWebGLContext(canvas);

    // 初始化着色器
    initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE);

    // 获取 attribute 变量 a_Position 的存储位置
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');

    // 将顶点位置传递给 attribute 变量
    gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);

    // 设置背景颜色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    // 清空颜色缓冲区
    gl.clear(gl.COLOR_BUFFER_BIT);

    // 绘制一个点
    gl.drawArrays(gl.POINTS, 0, 1);
}

为例突出重点,例子中将错误处理给省略了,完整源码可查看 《WebGL 编程指南》源码

整体上,使用 attribute 变量的步骤为:

  1. 在顶点着色器中声明 attribute 变量;
  2. 将 attribute 变量赋值给 gl_Position
  3. 在js程序中,获取 attribute 变量存储位置;
  4. 向 attribute 变量传递数据。

attribute 变量

// <存储限定符> <类型> <变量名>
   attribute vec4 a_Position;

关键字 attribute 被称为存储限定符,表示接下来的变量是一个 attribute 变量。attribute 变量必须声明为全局变量,也就是声明在着色器 main 函数外面。类型和变量名,大家都熟悉了,就不多介绍了。

本书约定,所有 attribute 变量都以 a_ 开头,所有 uniform 变量都以 u_ 开头。

获取 attribute 变量位置

辅助函数 initShaders 中我们在WebGl系统中创建了顶点着色器和片元着色器,WebGl 会对着色器进行解析,辨别出具有 attribute 存储限定符的变量,然后为它们分配存储地址,方便向存储地址中传递数据。

我们使用 gl.getAttribLocation(gl.program, 'a_Position') 获取变量地址:

  • gl.program 是一个程序对象,在 initShaders 辅助函数中创建了它,它包含了顶点着色器和片元着色器。目前不用深究,后续的话我们会详细学习它。
  • 'a_Position' 是变量名

该方法的详细定义为

/**
 * 获取name参数指定的attribute变量的地址
 * @param {*} program 指定包含顶点着色器和片元着色器的程序对象
 * @param {*} name attribute 变量名
 * 
 * @returns 大于0,attribute变量的存储地址;小于0,变量不存在。
 */
 gl.getAttribLocation(program, name);

向 attribute 变量赋值

获取完 attribute 的存储地址,下一步就是向存储地址中传递数据了。我们使用的方法是 gl.vertexAttrib3f()

这个方法第一个参数是 attribute 变量的地址,后面三个参数是三个 float 类型 的参数,表示点x, y, z坐标的位置。

/**
 * 将数据(v0, v1, v2) 传递给 location 参数指定的 attribute 变量
 * @param {*} location attribute 变量的地址
 * @param {*} v0 第一个分量的值
 * @param {*} v1 第二个分量的值
 * @param {*} v2 第三个分量的值
 */
gl.vertexAttrib3f(location, v0, v1, v2);

你有可能注意到 a_Positionvec4 类型的,但是目前只传了三个参数,表表示x, y, z分量。实际上,缺少的那个分量,会自动设置为 1.0 。

vertexAttrib3f 函数

gl.vertexAttrib3f() 是一系列同族函数中的一个,这个系列的函数的作用就是从JavaScript向顶点着色器attribute变量传递数据

这些函数包括:

gl.vertexAttrib1f(location, v0);
gl.vertexAttrib2f(location, v0, v1);
gl.vertexAttrib3f(location, v0, v1, v2);
gl.vertexAttrib4f(location, v0, v1, v2, v3);

还有它们的矢量版本,在 f 后面加一个 v (vector),比如 :

var position = new Float32Array([1.0, 2.0, 3.0, 1.0]);
gl.vertexAttrib4fv(location, position);

画多个点

我们把上面的程序稍做修改:

function main() {
    // 获取 <canvas> 元素
    var canvas = document.getElementById('webgl');

    // 获取 WebGL 绘图上下文
    var gl = getWebGLContext(canvas);

    // 初始化着色器
    initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE);

    // 获取 attribute 变量 a_Position 的存储位置
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');

    // 设置背景颜色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    // 清空颜色缓冲区
    gl.clear(gl.COLOR_BUFFER_BIT);

    var g_potins = [
      -0.5, 0.5,  // 第1个点
      0.5, 0.5,  // 第2个点
      0.5, -0.5,  // 第3个点
      -0.5, -0.5,  // 第4个点
    ]

    for (var index = 0; index < g_potins.length; index += 2) {
        // 将顶点位置传递给 attribute 变量
        gl.vertexAttrib3f(a_Position, g_potins[index], g_potins[index + 1], 0.0);
        // 绘制一个点
        gl.drawArrays(gl.POINTS, 0, 1);
    }
}

最终效果:

改变点的颜色

上面的例子,点的颜色是写死在片元着色器中的,如果我们想给不同点设置其他颜色时,其中一种方式是可以使用 uniform 变量,将颜色传给片元着色器。我们看一下这个例子,将示例一的代码稍作修改:

// 顶点着色器
...

// 片元着色器
var FSHADER_SOURCE =`
  precision mediump float;
  uniform vec4 u_FragColor;
  void main() {
      gl_FragColor = u_FragColor;
  }
  `;

function main() {
  ...

  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
  
  // 获取 uniform 变量 u_FragColor 的存储位置
  var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
  // 将颜色传递给 uniform 变量
  gl.uniform4f(u_FragColor, 0.0, 1.0, 0.0, 1.0);
  
  ...
  // 绘制一个点
  gl.drawArrays(gl.POINTS, 0, 1);
}

运行效果:

uniform 变量

我们知道JavaScript可以向 attribute 变量传递数据,但是只有顶点着色器才能使用 attribute 变量,在片元着色器中只能使用 uniform 变量,或者 varying 变量。varying 变量比较复杂,后面的会进行详细的讲解。

获取 uniform 变量地址

和获取 attribute 变量地址的方法名很类似,使用方式是一样的

/**javascript
 * 获取name参数指定的 uniform 变量的地址
 * @param {*} program 指定包含顶点着色器和片元着色器的程序对象
 * @param {*} name uniform 变量名
 * 
 * @returns 大于0,uniform 变量的存储地址;小于0,变量不存在。
 */
 gl.getUniformLocation(program, name);

向 uniform 变量赋值

使用方式和向 attribute 变量赋值一样,只是方法名有所区别

/**
 * 将数据(v0, v1, v2) 传递给 location 参数指定的 uniform 变量
 * @param {*} location uniform 变量的地址
 * @param {*} v0 第一个分量的值
 * @param {*} v1 第二个分量的值
 * @param {*} v2 第三个分量的值
 * @param {*} v3 第四个分量的值
 */
gl.uniform4f(location, v0, v1, v2, v3);

总结

到这里,我们了解了 WebGL 的一些核心函数,学习了着色器的相关知识,并理解了 JavaScript 是怎么将数据传递给着色器的。后续我们会继续学习更过的 WebGL 函数。