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

顶点着色器

  • 功能:生成裁剪空间坐标值
  • 每个顶点调用一次(顶点)着色器,每次调用都需要设置一个特殊的全局变量gl_Position, 该变量的值就是裁减空间坐标值
  • 它所需要的数据可以通过以下三种方式获得:
    • Attributes 属性 (从缓冲中获取的数据)
    • Uniforms 全局变量 (在一次绘制中对所有顶点保持一致值)
    • Textures 纹理 (从像素或纹理元素中获取的数据)

缓冲和属性

最常用的方法是缓冲和属性,就如WebGL 系列一展示的那样。

属性可以用 float, vec2, vec3, vec4, mat2, mat3mat4 数据类型。

//创建缓冲
var buf = gl.createBuffer();

//将数据存入缓冲
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, someData, gl.STATIC_DRAW);

//初始化的时候,在着色程序中找到属性所在地址
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);

Uniforms 全局变量

在一次绘制过程中传递给着色器的值都一样。

attribute vec4 a_position;
uniform vec4 u_offset; // 着色器里
 
void main() {
   gl_Position = a_position + u_offset;
}
var offsetLoc = gl.getUniformLocation(someProgram, "u_offset"); // JavaScript 初始化时

gl.uniform4fv(offsetLoc, [1, 0, 0, 0]);  // 向右偏移一半屏幕宽度 渲染的时候

全局变量有很多类型,对应的类型有对应的设置方法。

gl.uniform1f (floatUniformLoc, v);                 // float
gl.uniform1fv(floatUniformLoc, [v]);               // float 或 float array
gl.uniform2f (vec2UniformLoc,  v0, v1);            // vec2
gl.uniform2fv(vec2UniformLoc,  [v0, v1]);          // vec2 或 vec2 array
gl.uniform3f (vec3UniformLoc,  v0, v1, v2);        // vec3
gl.uniform3fv(vec3UniformLoc,  [v0, v1, v2]);      // vec3 或 vec3 array
gl.uniform4f (vec4UniformLoc,  v0, v1, v2, v4);    // vec4
gl.uniform4fv(vec4UniformLoc,  [v0, v1, v2, v4]);  // vec4 或 vec4 array
 
gl.uniformMatrix2fv(mat2UniformLoc, false, [  4x element array ])  // mat2 或 mat2 array
gl.uniformMatrix3fv(mat3UniformLoc, false, [  9x element array ])  // mat3 或 mat3 array
gl.uniformMatrix4fv(mat4UniformLoc, false, [ 16x element array ])  // mat4 或 mat4 array
 
gl.uniform1i (intUniformLoc,   v);                 // int
gl.uniform1iv(intUniformLoc, [v]);                 // int 或 int array
gl.uniform2i (ivec2UniformLoc, v0, v1);            // ivec2
gl.uniform2iv(ivec2UniformLoc, [v0, v1]);          // ivec2 或 ivec2 array
gl.uniform3i (ivec3UniformLoc, v0, v1, v2);        // ivec3
gl.uniform3iv(ivec3UniformLoc, [v0, v1, v2]);      // ivec3 or ivec3 array
gl.uniform4i (ivec4UniformLoc, v0, v1, v2, v4);    // ivec4
gl.uniform4iv(ivec4UniformLoc, [v0, v1, v2, v4]);  // ivec4 或 ivec4 array
 
gl.uniform1i (sampler2DUniformLoc,   v);           // sampler2D (textures)
gl.uniform1iv(sampler2DUniformLoc, [v]);           // sampler2D 或 sampler2D array
 
gl.uniform1i (samplerCubeUniformLoc,   v);         // samplerCube (textures)
gl.uniform1iv(samplerCubeUniformLoc, [v]);         // samplerCube 或 samplerCube array

还有一些类型 bool, bvec2, bvec3, and bvec4。它们可用gl.uniform?f?gl.uniform?i?

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

// 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

要注意的是全局变量属于单个着色程序,如果多个着色程序有同名全局变量,需要找到每个全局变量并设置自己的值。

如果你创建了一个结构体,你需要找到每个元素的地址。

纹理

片段着色器 中的 纹理描述

片断着色器

  • 功能:为当前光栅化的像素提供颜色值
  • 每个像素都将调用一次片断着色器,每次调用需要从你设置的特殊全局变量gl_FragColor中获取颜色信息。
  • 片断着色器所需的数据,可以通过以下三种方式获取:
    1. Uniforms 全局变量 (values that stay the same for every pixel of a single draw call)
    2. Textures 纹理 (data from pixels/texels)
    3. Varyings 可变量 (data passed from the vertex shader and interpolated)

Uniform 全局变量

Uniforms

Textures 纹理

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

precision mediump float;
 
uniform sampler2D u_texture;
 
void main() {
   vec2 texcoord = vec2(0.5, 0.5)  // 获取纹理中心的值
   gl_FragColor = texture2D(u_texture, texcoord);
}

//从纹理中获取的数据取决于很多设置
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
var level = 0;
var width = 2;
var height = 1;
var data = new Uint8Array([
   255, 0, 0, 255,   // 一个红色的像素
   0, 255, 0, 255,   // 一个绿色的像素
]);
gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);

//在初始化时找到全局变量的地址
var someSamplerLoc = gl.getUniformLocation(someProgram, "u_texture");

//在渲染的时候WebGL要求纹理必须绑定到一个纹理单元上
var unit = 5;  // 挑选一个纹理单元
gl.activeTexture(gl.TEXTURE0 + unit);
gl.bindTexture(gl.TEXTURE_2D, tex);

//然后告诉着色器你要使用的纹理在那个纹理单元
gl.uniform1i(someSamplerLoc, unit);

Varyings 可变量

可变量是一种顶点着色器给片断着色器传值的方式。为了使用可变量,要在两个着色器中定义同名的可变量。

给顶点着色器中可变量设置的值,会作为参考值进行内插,在绘制像素时传给片断着色器的可变量。

顶点着色器

attribute vec4 a_position;
 
uniform vec4 u_offset;
 
varying vec4 v_positionWithOffset;
 
void main() {
  gl_Position = a_position + u_offset;
  v_positionWithOffset = a_position + u_offset;
}

片断着色器

precision mediump float;
 
varying vec4 v_positionWithOffset;
 
void main() {
  // 从裁剪空间 (-1 <-> +1) 转换到颜色空间 (0 -> 1).
  vec4 color = v_positionWithOffset * 0.5 + 0.5
  gl_FragColor = color;
}

上方的示例几乎没有意义,通常情况下直接将裁剪空间的值传给片断着色器当作颜色值是没有意义的, 虽然它可以运行并且可以生成颜色值。

GLSL

GLSL全称是 Graphics Library Shader Language (图形库着色器语言),是着色器使用的语言。 它有一些不同于JavaScript的特性,主要目的是为栅格化图形提供常用的计算功能。

vec4 a = vec4(1, 2, 3, 4);
vec4 b = a * 2.0;
// b 现在是 vec4(2, 4, 6, 8);


mat4 a = ???
mat4 b = ???
mat4 c = a * b;
 
vec4 v = ???
vec4 y = c * v;

vec4 v;
// v.x 和 v.s 以及 v.r , v[0] 表达的是同一个分量。
// v.y 和 v.t 以及 v.g , v[1] 表达的是同一个分量。
// v.z 和 v.p 以及 v.b , v[2] 表达的是同一个分量。
// v.w 和 v.q 以及 v.a , v[3] 表达的是同一个分量。


// 它还支持矢量调制,意味者你可以交换或重复分量。
v.yyyy
// 等同于
vec4(v.y, v.y, v.y, v.y)


v.bgra
// 等同于
vec4(v.b, v.g, v.r, v.a)

vec4(v.rgb, 1)
// 等同于
vec4(v.r, v.g, v.b, 1)

vec4(1)
// 等同于
vec4(1, 1, 1, 1)

GLSL有一系列内置方法,其中大多数运算支持多种数据类型,并且一次可以运算多个分量,例如 T sin(T angle)

T可以是 float, vec2, vec3 或 vec4 。如果你传的是 vec4 返回的也是 vec4, 返回结果对应每个分量的正弦值。

总结

WebGL的全部内容就是创建不同的着色器,向着色器提供数据然后调用gl.drawArraysgl.drawElements

让WebGL调用当前顶点着色器处理每个顶点,调用当前片断着色器渲染每个像素。