WebGL-shader和GLSL


我们之前提到过着色器和GLSL,但是没有涉及细节,你可能已经对此有所了解, 但以防万一,这里将详细讲解着色器和GLSL。

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

顶点着色器

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

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

Attributes 属性

最常用的方法是缓冲和属性,在工作原理 中讲到了缓冲和属性,你可以创建缓冲,

将数据存入缓冲:

然后初始化的时候,在你制作的(着色)程序中找到属性所在地址:

在渲染的时候告诉WebGL怎么从缓冲中获取数据传递给属性:

在WebGL 基础概念 中示范了不做任何运算直接将数据传递给 gl_Position

如果缓冲中存的是裁剪空间坐标就没什么问题。

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

Uniforms 全局变量

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

现在可以把所有顶点偏移一个固定值,首先在初始化时找到全局变量的地址:

然后在绘制前设置全局变量:

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

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

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

一个数组可以一次设置所有的全局变量,例如:

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

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

你需要找到每个元素的地址:

片断着色器

一个片断着色器的工作是为当前光栅化的像素提供颜色值,通常是以下的形式:

每个像素都将调用一次片断着色器,每次调用需要从你设置的特殊全局变量 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)

Textures 纹理(片断着色器中)

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

从纹理中获取的数据取决于很多设置。 至少要创建并给纹理填充数据,例如:

在初始化时找到全局变量的地址:

在渲染的时候WebGL要求纹理必须绑定到一个纹理单元上:

然后告诉着色器你要使用的纹理在那个纹理单元:

Varyings 可变量

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

顶点着色器:

片断着色器:

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

GLSL

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

它同样可以做矩阵乘法以及矢量和矩阵的乘法:

他还为矢量数据提供多种分量选择器,例如 vec4:

  • 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] 表达的是同一个分量。
    它还支持矢量调制,意味者你可以交换或重复分量。

是一样的

同样的

等价

当构造一个矢量或矩阵时可以一次提供多个分量,例如:

是一样的

同样

相同

值得注意的是GLSL是一个强类型的语言。

正确的方式是

上例中 vec4(v.rgb, 1) 不会因为 1 报错,因为 vec4 内部进行了转换类似 float(1) 。

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

T可以是 float, vec2, vec3vec4 。如果你传的是 vec4 返回的也是 vec4, 返回结果对应每个分量的正弦值。换句话说如果 vvec4 类型。那么:

是一样的

有时一个参数是浮点型而剩下的都是 T ,意思是那个浮点数据会作为所有其他参数的一个新分量。 例如如果 v1 和 v2 是 vec4 同时 f 是浮点型,那么:

等价

你可以在 WebGL 引用表 最后一页看到所有GLSL方法的列表。如果你喜欢干货以更详细的东西你可以看看 GLSL 规范

总结

这是当前系列文章的重点。WebGL的全部内容就是创建不同的着色器,向着色器提供数据然后调用 gl.drawArraysgl.drawElements 让WebGL调用当前顶点着色器处理每个顶点,调用当前片断着色器渲染每个像素。