前言
上一节,我们了解了如何画一个点,学习了绘制点用到的顶点着色器和片元着色器,以及绘制的核心方法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 变量的步骤为:
- 在顶点着色器中声明 attribute 变量;
- 将 attribute 变量赋值给
gl_Position; - 在js程序中,获取 attribute 变量存储位置;
- 向 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_Position 是 vec4 类型的,但是目前只传了三个参数,表表示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 函数。