01.基本原理

WebGL每次绘制需要两个着色器, 一个顶点着色器和一个片断着色器,每一个着色器都是一个方法。 一个顶点着色器和一个片断着色器链接在一起放入一个着色程序中(或者只叫程序)。 一个典型的WebGL应用会有多个着色程序。

02.顶点着色器【VertexShader】

一个顶点着色器的工作是生成裁剪空间坐标值,通常是以下的形式

  1. void main() {
  2. gl_Position = doMathToMakeClipspaceCoordinates
  3. }

每个顶点调用一次(顶点)着色器,每次调用都需要设置一个特殊的全局变量gl_Position, 该变量的值就是裁减空间坐标值。
顶点着色器需要的数据,可以通过以下三种方式获得。

一. Attributes 属性

  1. // 创建缓冲
  2. var buf = gl.createBuffer();
  3. // 将数据存入缓冲
  4. gl.bindBuffer(gl.ARRAY_BUFFER, buf);
  5. gl.bufferData(gl.ARRAY_BUFFER, someData, gl.STATIC_DRAW);
  6. // 然后初始化的时候,在你制作的(着色)程序中找到属性所在地址
  7. var positionLoc = gl.getAttribLocation(someShaderProgram, "a_position" )
  8. // 在渲染的时候告诉WebGL怎么从缓冲中获取数据传递给属性
  9. // 开启从缓冲中获取数据
  10. gl.enableVertexAttribArray(positionLoc);
  11. var numComponents = 3; // (x, y, z)
  12. var type = gl.FLOAT; // 32位浮点数据
  13. var normalize = false; // 不标准化
  14. var offset = 0; // 从缓冲起始位置开始获取
  15. var stride = 0; // 到下一个数据跳多少位内存
  16. // 0 = 使用当前的单位个数和单位长度 ( 3 * Float32Array.BYTES_PER_ELEMENT )
  17. gl.vertexAttribPointer(positionLoc, numComponents, type, false, stride, offset);
  18. // 在WebGL 基础概念中示范了不做任何运算直接将数据传递给gl_Position。
  19. attribute vec4 a_position;
  20. void main() {
  21. gl_Position = a_position;
  22. }

如果缓冲中存的是裁剪空间坐标就没什么问题。
属性可以用 float, vec2, vec3, vec4, mat2, mat3 和 mat4 数据类型。

二. Uniforms 全局变量

全局变量在一次绘制过程中传递给着色器的值都一样,在下面的一个简单的例子中, 用全局变量给顶点着色器添加了一个偏移量

  1. attribute vec4 a_position;
  2. uniform vec4 u_offset;
  3. void main() {
  4. gl_Position = a_position + u_offset;
  5. }
  6. // 现在可以把所有顶点偏移一个固定值,首先在初始化时找到全局变量的地址
  7. var offsetLoc = gl.getUniformLocation(someProgram, "u_offset");
  8. // 然后在绘制前设置全局变量
  9. gl.uniform4fv(offsetLoc, [1, 0, 0, 0]); // 向右偏移一半屏幕宽度

要注意的是全局变量属于单个着色程序,如果多个着色程序有同名全局变量,需要找到每个全局变量并设置自己的值。 我们调用gl.uniform???的时候只是设置了当前程序的全局变量,当前程序是传递给gl.useProgram 的最后一个程序。
全局变量有很多类型,对应的类型有对应的设置方法。

  1. gl.uniform1f (floatUniformLoc, v); // float
  2. gl.uniform1fv(floatUniformLoc, [v]); // float 或 float array
  3. gl.uniform2f (vec2UniformLoc, v0, v1); // vec2
  4. gl.uniform2fv(vec2UniformLoc, [v0, v1]); // vec2 或 vec2 array
  5. gl.uniform3f (vec3UniformLoc, v0, v1, v2); // vec3
  6. gl.uniform3fv(vec3UniformLoc, [v0, v1, v2]); // vec3 或 vec3 array
  7. gl.uniform4f (vec4UniformLoc, v0, v1, v2, v4); // vec4
  8. gl.uniform4fv(vec4UniformLoc, [v0, v1, v2, v4]); // vec4 或 vec4 array
  9. gl.uniformMatrix2fv(mat2UniformLoc, false, [ 4x element array ]) // mat2 或 mat2 array
  10. gl.uniformMatrix3fv(mat3UniformLoc, false, [ 9x element array ]) // mat3 或 mat3 array
  11. gl.uniformMatrix4fv(mat4UniformLoc, false, [ 16x element array ]) // mat4 或 mat4 array
  12. gl.uniform1i (intUniformLoc, v); // int
  13. gl.uniform1iv(intUniformLoc, [v]); // int 或 int array
  14. gl.uniform2i (ivec2UniformLoc, v0, v1); // ivec2
  15. gl.uniform2iv(ivec2UniformLoc, [v0, v1]); // ivec2 或 ivec2 array
  16. gl.uniform3i (ivec3UniformLoc, v0, v1, v2); // ivec3
  17. gl.uniform3iv(ivec3UniformLoc, [v0, v1, v2]); // ivec3 or ivec3 array
  18. gl.uniform4i (ivec4UniformLoc, v0, v1, v2, v4); // ivec4
  19. gl.uniform4iv(ivec4UniformLoc, [v0, v1, v2, v4]); // ivec4 或 ivec4 array
  20. gl.uniform1i (sampler2DUniformLoc, v); // sampler2D (textures)
  21. gl.uniform1iv(sampler2DUniformLoc, [v]); // sampler2D 或 sampler2D array
  22. gl.uniform1i (samplerCubeUniformLoc, v); // samplerCube (textures)
  23. gl.uniform1iv(samplerCubeUniformLoc, [v]); // samplerCube 或 samplerCube array

还有一些类型 bool, bvec2, bvec3, and bvec4。它们可用gl.uniform?f? 或 gl.uniform?i?。
一个数组可以一次设置所有的全局变量

  1. // 着色器里
  2. uniform vec2 u_someVec2[3];
  3. // JavaScript 初始化时
  4. var someVec2Loc = gl.getUniformLocation(someProgram, "u_someVec2");
  5. // 渲染的时候
  6. gl.uniform2fv(someVec2Loc, [1, 2, 3, 4, 5, 6]); // 设置数组 u_someVec2

如果你想单独设置数组中的某个值,就要单独找到该值的地址。

  1. / JavaScript 初始化时
  2. var someVec2Element0Loc = gl.getUniformLocation(someProgram, "u_someVec2[0]");
  3. var someVec2Element1Loc = gl.getUniformLocation(someProgram, "u_someVec2[1]");
  4. var someVec2Element2Loc = gl.getUniformLocation(someProgram, "u_someVec2[2]");
  5. // 渲染的时候
  6. gl.uniform2fv(someVec2Element0Loc, [1, 2]); // set element 0
  7. gl.uniform2fv(someVec2Element1Loc, [3, 4]); // set element 1
  8. gl.uniform2fv(someVec2Element2Loc, [5, 6]); // set element 2

同样的,如果你创建了一个结构体

  1. struct SomeStruct {
  2. bool active;
  3. vec2 someVec2;
  4. };
  5. uniform SomeStruct u_someThing;
  6. var someThingActiveLoc = gl.getUniformLocation(someProgram, "u_someThing.active");
  7. var someThingSomeVec2Loc = gl.getUniformLocation(someProgram, "u_someThing.someVec2");

03.片断着色器【FragmentShader】

三.Textures 纹理(片断着色器中)

在着色器中获取纹理信息,可以先创建一个sampler2D类型全局变量,然后用GLSL方法texture2D 从纹理中提取信息。

  1. precision mediump float;
  2. void main() {
  3. gl_FragColor = doMathToMakeAColor;
  4. }
  5. precision mediump float;
  6. uniform sampler2D u_texture;
  7. void main() {
  8. vec2 texcoord = vec2(0.5, 0.5) // 获取纹理中心的值
  9. gl_FragColor = texture2D(u_texture, texcoord);
  10. }
  11. // 从纹理中获取的数据取决于很多设置。 至少要创建并给纹理填充数据,
  12. var tex = gl.createTexture();
  13. gl.bindTexture(gl.TEXTURE_2D, tex);
  14. var level = 0;
  15. var width = 2;
  16. var height = 1;
  17. var data = new Uint8Array([
  18. 255, 0, 0, 255, // 一个红色的像素
  19. 0, 255, 0, 255, // 一个绿色的像素
  20. ]);
  21. gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
  22. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  23. // 在初始化时找到全局变量的地址
  24. var someSamplerLoc = gl.getUniformLocation(someProgram, "u_texture");
  25. // 在渲染的时候WebGL要求纹理必须绑定到一个纹理单元上
  26. var unit = 5; // 挑选一个纹理单元
  27. gl.activeTexture(gl.TEXTURE0 + unit);
  28. gl.bindTexture(gl.TEXTURE_2D, tex);
  29. // 然后告诉着色器你要使用的纹理在哪个纹理单元
  30. gl.uniform1i(someSamplerLoc, unit);

四.Varyings 可变量

工作原理提到过,可变量是一种顶点着色器给片断着色器传值的方式。
为了使用可变量,要在两个着色器中定义同名的可变量。 给顶点着色器中可变量设置的值,会作为参考值进行内插,在绘制像素时传给片断着色器的可变量。

  1. attribute vec4 a_position;
  2. uniform vec4 u_offset;
  3. varying vec4 v_positionWithOffset;
  4. void main() {
  5. gl_Position = a_position + u_offset;
  6. v_positionWithOffset = a_position + u_offset;
  7. }
  8. // 片元着色器
  9. precision mediump float;
  10. varying vec4 v_positionWithOffset;
  11. void main() {
  12. // 从裁剪空间 (-1 <-> +1) 转换到颜色空间 (0 -> 1).
  13. vec4 color = v_positionWithOffset * 0.5 + 0.5
  14. gl_FragColor = color;
  15. }

04.GLSL

GLSL全称是 Graphics Library Shader Language (图形库着色器语言),是着色器使用的语言。 它有一些不同于JavaScript的特性,主要目的是为栅格化图形提供常用的计算功能。 所以它内建的数据类型例如vec2, vec3和 vec4分别代表两个值,三个值和四个值, 类似的还有mat2, mat3 和 mat4 分别代表 2x2, 3x3 和 4x4 矩阵。 你可以做一些运算例如常量和矢量的乘法。

  1. vec4 a = vec4(1, 2, 3, 4);
  2. vec4 b = a * 2.0;
  3. // b 现在是 vec4(2, 4, 6, 8);
  4. // 它同样可以做矩阵乘法以及矢量和矩阵的乘法
  5. mat4 a = ???
  6. mat4 b = ???
  7. mat4 c = a * b;
  8. vec4 v = ???
  9. vec4 y = c * v;
  10. // 他还为矢量数据提供多种分量选择器,例如 vec4
  11. vec4 v;
  12. // v.x 和 v.s 以及 v.r , v[0] 表达的是同一个分量。
  13. // v.y 和 v.t 以及 v.g , v[1] 表达的是同一个分量。
  14. // v.z 和 v.p 以及 v.b , v[2] 表达的是同一个分量。
  15. // v.w 和 v.q 以及 v.a , v[3] 表达的是同一个分量。
  16. v.yyyy === vec4(v.y, v.y, v.y, v.y)
  17. v.bgra === vec4(v.b, v.g, v.r, v.a)
  18. vec4(v.rgb, 1) === vec4(v.r, v.g, v.b, 1)
  19. vec4(1, 1, 1, 1) === vec4(1)
  20. // 强类型语言
  21. float f = 1; // 错误,1是int类型,不能将int型赋值给float
  22. float f = 1.0; // 使用float
  23. float f = float(1) // 转换integer为float