WebGL基础知识 - GLSL和着色器(Shader)

techbrood 发表于 2019-04-29 18:26:12

标签: webgl, shader, glsl

- +

在本站的WebGL入门教程中,提到绘制管道中有两个着色器,一个是vertex shader(顶点着色器)和一个fragment shader(片段着色器)。本章简介这两个着色器的具体使用。

每个着色器本质上就是一个函数,有特定的输入和输出。着色器函数被串联到同一个着色器程序中。

Vertex Shader

顶点着色器的功能是把原始顶点数据变换到裁减空间坐标。每个顶点都会调用该着色器函数。

顶点着色器的输入数据有如下2种方式:

  1. Attributes (从缓存中获取的数据)

  2. Uniforms (单次绘制中对所有顶点保持不变的值)

Attributes

最常见的方式是通过缓存和属性。首先创建缓存:

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;    // 32bit浮点数
var normalize = false;  // 保持数据原样,不做规范化
var offset = 0;         // 从缓存的起点处开始
var stride = 0;         // 移动到下一个顶点的步幅
                        // 0 = 使用符合当前type和numComponents取值的步幅
 
gl.vertexAttribPointer(positionLoc, numComponents, type, false, stride, offset);

在着色器函数中使用属性数据如下(这里尚未作任何数学处理):

attribute vec4 a_position; 
void main() {
    gl_Position = a_position;
}

属性(Attributes)可以使用的数据类型有:float, vec2, vec3, vec4, mat2, mat3 和 mat4。

Uniforms

Uniforms是传递给着色器的值,这些值对于绘制调用中的所有顶点保持不变。

比如我们可以向上面的顶点着色器添加一个偏移量:

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

这样我们就可以将每个顶点偏移一定的量。

为此,首先我们需要在初始化时得到该uniform的位置:

var offsetLoc = gl.getUniformLocation(someProgram, "u_offset");

然后在渲染前给uniform设置值:

gl.uniform4fv(offsetLoc, [1, 0, 0, 0]);  //将其偏移到屏幕的右半部分

注意,这里uniforms是属于单个着色器程序的,如果你的WebGL应用程序包含多个着色器程序,那么在不同的shader中的同名uniform将拥有他们各自的存储位置和取值。当调用gl.uniformXXX方法时我们只是设置当前着色器程序(shader program)的uniform,当前shader program就是你最近一次调用gl.useProgram所应用的那个。

Uniforms有很多类型,对于每个类型都有对应的方法来设置它的值:

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

比如设置一个二维矢量的数组:

// in shader
uniform vec2 u_someVec2[3];

// in JavaScript at init time
var someVec2Loc = gl.getUniformLocation(someProgram, "u_someVec2");

// at render time
gl.uniform2fv(someVec2Loc, [1, 2, 3, 4, 5, 6]);

Fragment Shader

顶点着色器的输出数据经过光栅化处理后,输入给片段着色器,而片段着色器的功能就是为正在光栅化的当前像素提供颜色。

每个像素都会调用片段着色器。片段着色器的输入数据有如下3种方式:

  1. Uniforms (对于单个绘图调用的每个像素保持相同的值,同上)

  2. Textures (从像素pixels和纹元texels中读取的数据)

  3. Varyings (从顶点着色器传递并插值的数据)

Textures

首先在shader中创建一个sampler2d uniform,我们可以使用glsl函数texture2d从中提取值(需要提供纹理坐标):

precision mediump float;
 
uniform sampler2D u_texture;
 
void main() {
   vec2 texcoord = vec2(0.5, 0.5)  // 从texture的中间位置获取数据
   gl_FragColor = texture2D(u_texture, texcoord);
}

在WebGL程序初始化时得到shader中u_texture的位置:

var someSamplerLoc = gl.getUniformLocation(someProgram, "u_texture");

然后我们需要设置texture的数据(数据格式有很多种),下面是一个简单的示例数据:

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,   // 1个红色像素   
    0, 255, 0, 255,   // 1个绿色像素
]);
gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);

在渲染时告诉WebGL激活某texture单元,并绑定tex到该单元(比如gl.TEXTURE0),然后告诉着色器把someSampler关联到该单元:

var unit = 0;
gl.activeTexture(gl.TEXTURE + unit);
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.uniform1i(someSamplerLoc, unit);

通过上面一连串的绑定关联动作,我们就通过WebGL程序接口把2个像素数据设置到shader中的u_texture中,并最终给到gl_FragColor。

Varyings

变量(Varyings)是一种将值从顶点着色器传递到片段着色器的方法。和常量(Uniforms)不同的是,该值可以被顶点着色器修改,然后在片段着色器中读取(只读)。变量的值是一个插值数据,每个像素都不同。

要使用变量,我们需要在顶点和片段着色器中声明匹配的变量。我们用每个顶点的值来设置顶点着色中的变化。当WebGL绘制像素时,它将在这些值之间进行插值,并将它们传递给片段明暗器中相应的变量。

Vertex shader

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;
}

Fragment shader

precision mediump float; 
varying vec4 v_positionWithOffset; 
void main() {  
    // convert from clipsapce (-1 <-> +1) to color space (0 -> 1).  
    vec4 color = v_positionWithOffset * 0.5 + 0.5  
    gl_FragColor = color;
}


possitive(22) views13001 comments0

发送私信

最新评论

请先 登录 再评论.
相关文章