在工作原理中我们提到,WebGL每次绘制需要两个着色器, 一个顶点着色器和一个片段着色器,每一个着色器都是一个方法。 一个顶点着色器和一个片段着色器链接在一起放入一个着色程序中(或者只叫程序)。 一个典型的WebGL应用会有多个着色程序。
🌙 顶点着色器 Vertex Shader
一个顶点着色器的工作是生成裁剪空间坐标值,通常是以下的形式:
void main() {
// 坐标计算
gl_Position = doMathToMakeClipspaceCoordinates;
}
2
3
4
每个顶点调用一次(顶点)着色器,每次调用都需要设置一个特殊的全局变量gl_Position
, 该变量的值就是裁减空间坐标值。
顶点着色器需要的数据,可以通过以下三种方式获得:
- 1.Attributes 属性 (opens new window) (从缓冲中获取的数据)
- 2.Uniforms 全局变量 (opens new window) (在一次绘制中对所有顶点保持一致值)
- 3.Textures 纹理 (opens new window) (从像素或纹理元素中获取的数据)
🌙 Attributes 属性
最常用的方法是缓冲和属性,创建缓冲:
// 创建缓冲
var buf = gl.createBuffer();
// 将数据存入缓冲
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, someData, gl.STATIC_DRAW);
2
3
4
5
6
初始化时,在着色程序中找到属性所在地址, 比如:
var positionLoc = gl.getAttribLocation(someShaderProgram, "a_position");
在渲染的时候告诉WebGL怎么从缓冲中获取数据传递给属性:
// 开启从缓冲中获取数据
gl.enableVertexAttribArray(positionLoc);
var numComponents = 3; // (x, y, z)
var type = gl.FLOAT; // 32位浮点数据
var normalize = false; // 不标准化
var offset = 0; // 从缓冲起始位置开始获取
var stride = 0; // 到下一个数据跳多少位内存
// 0 = 使用当前的单位个数和单位长度 ( 3 * Float32Array.BYTES_PER_ELEMENT )
gl.vertexAttribPointer(positionLoc, numComponents, type, false, stride, offset);
2
3
4
5
6
7
8
9
10
11
不做任何运算直接将数据传递给gl_Position:
// 属性可以用 float, vec2, vec3, vec4, mat2, mat3 和 mat4 数据类型。
attribute vec4 a_position;
void main() {
gl_Position = a_position;
}
2
3
4
5
6
🌙 vertexAttribPointer 中的 normalizeFlag 参数是什么意思?
标准化标记(normalizeFlag)适用于所有非浮点型数据。如果传递false就解读原数据类型。 BYTE 类型的范围是从 -128 到 127,UNSIGNED_BYTE 类型的范围是从 0 到 255, SHORT 类型的范围是从 -32768 到 32767,等等...
如果标准化标记设为true,BYTE 数据的值(-128 to 127)将会转换到 -1.0 到 +1.0 之间, UNSIGNED_BYTE (0 to 255) 变为 0.0 到 +1.0 之间,SHORT 也是转换到 -1.0 到 +1.0 之间, 但比 BYTE 精确度高。
最常用的是标准化颜色数据。大多数情况颜色值范围为 0.0 到 +1.0。 使用4个浮点型数据存储红,绿,蓝和阿尔法通道数据时,每个顶点的颜色将会占用16字节空间, 如果你有复杂的几何体将会占用很多内存。代替的做法是将颜色数据转换为四个 UNSIGNED_BYTE , 其中 0 表示 0.0,255 表示 1.0。现在每个顶点只需要四个字节存储颜色值,省了 75% 空间。
我们来修改之前代码实现。
当我们告诉WebGL如何获取颜色数据时将这样:
// 告诉颜色属性如何从colorBuffer中提取数据 (ARRAY_BUFFER)
var size = 4; // 每次迭代使用四个单位数据
var type = gl.UNSIGNED_BYTE; // 数据类型是8位的 UNSIGNED_BYTE 类型。
var normalize = true; // 标准化数据
var stride = 0; // 0 = 移动距离 * 单位距离长度sizeof(type)
// 每次迭代跳多少距离到下一个数据
var offset = 0; // 从缓冲的起始处开始
gl.vertexAttribPointer(
colorLocation, size, type, normalize, stride, offset)
2
3
4
5
6
7
8
9
如下向缓冲添加数据:
// 给矩形的两个三角形
// 设置颜色值并发到缓冲
function setColors(gl) {
// 设置两个随机颜色
var r1 = Math.random() * 256; // 0 到 255.99999 之间
var b1 = Math.random() * 256; // 这些数据
var g1 = Math.random() * 256; // 在存入缓冲时
var r2 = Math.random() * 256; // 将被截取成
var b2 = Math.random() * 256; // Uint8Array 类型
var g2 = Math.random() * 256;
gl.bufferData(
gl.ARRAY_BUFFER,
new Uint8Array( // Uint8Array
[ r1, b1, g1, 255,
r1, b1, g1, 255,
r1, b1, g1, 255,
r2, b2, g2, 255,
r2, b2, g2, 255,
r2, b2, g2, 255]),
gl.STATIC_DRAW);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
结果如下:
See the Pen WebGL - Fundamentals by Keekuun (@keekuun) on CodePen.
🌙 Uniforms 全局变量
全局变量在一次绘制过程中传递给着色器的值都一样,在下面的一个简单的例子中, 用全局变量给顶点着色器添加了一个偏移量
attribute vec4 a_position;
uniform vec4 u_offset;
void main() {
gl_position = a_position + u_offset;
}
2
3
4
5
6
在初始化时找到全局变量的地址:
var offsetLoc = gl.getUniformLocation(yourProgram, "u_offset");
// 设置全局变量
gl.uniform4fv(offsetLoc, [1, 0, 0, 0]); // 向右偏移一半屏幕宽度
2
3
要注意的是全局变量属于单个着色程序,如果多个着色程序有同名全局变量,需要找到每个全局变量并设置自己的值。
一个数组可以一次设置所有的全局变量:
// 着色器里
// uniform vec2 u_someVec2[3];
// JavaScript 初始化时
var someVec2Loc = gl.getUniformLocation(someProgram, "u_someVec2");
// 渲染的时候
gl.uniform2fv(someVec2Loc, [1, 2, 3, 4, 5, 6]); // 设置数组 u_someVec2
2
3
4
5
6
7
8
如果你想单独设置数组中的某个值,就要单独找到该值的地址:
// JavaScript 初始化时
var someVec2Element0Loc = gl.getUniformLocation(someProgram, "u_someVec2[0]");
var someVec2Element1Loc = gl.getUniformLocation(someProgram, "u_someVec2[1]");
var someVec2Element2Loc = gl.getUniformLocation(someProgram, "u_someVec2[2]");
// 渲染的时候
gl.uniform2fv(someVec2Element0Loc, [1, 2]); // set element 0
gl.uniform2fv(someVec2Element1Loc, [3, 4]); // set element 1
gl.uniform2fv(someVec2Element2Loc, [5, 6]); // set element 2
2
3
4
5
6
7
8
9
如果是结构体:
struct SomeStruct {
bool active;
vec2 someVec2;
};
uniform SomeStruct u_someThing;
2
3
4
5
那么:
var someThingActiveLoc = gl.getUniformLocation(someProgram, "u_someThing.active");
var someThingSomeVec2Loc = gl.getUniformLocation(someProgram, "u_someThing.someVec2");
2