每个顶点调用一次(顶点)着色器,每次调用都需要设置一个特殊的全局变量gl_Position, 该变量的值就是裁减空间坐标值。
顶点着色器需要的数据,可以通过以下三种方式获得。
Attributes
属性 (从缓冲中获取的数据)Uniforms
全局变量 (在一次绘制中对所有顶点保持一致值)Textures
纹理 (从像素或纹理元素中获取的数据)
Attributes 属性
最常用的方法是缓冲和属性, 你可以创建缓冲
const buf = gl.createBuffer();
将数据存入缓冲
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, someData, gl.STATIC_DRAW);
然后初始化的时候,在你制作的(着色)程序中找到属性所在地址
const 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);
如果缓冲中存的是裁剪空间坐标就没什么问题。
属性可以用 float
, vec2
, vec3
, vec4
, mat2
, mat3
和 mat4
数据类型。
Uniforms 全局变量
全局变量在一次绘制过程中传递给着色器的值都一样, 在下面的一个简单的例子中, 用全局变量给顶点着色器添加了一个偏移量
attribute vec4 a_position;
uniform vec4 u_offset;
void main() {
gl_Position = a_position + u_offset;
}
现在可以把所有顶点偏移一个固定值,首先在初始化时找到全局变量的地址
const offsetLoc = gl.getUniformLocation(someProgram, "u_offset");
然后在绘制前设置全局变量
// 向右偏移一半屏幕宽度
gl.uniform4fv(offsetLoc, [1, 0, 0, 0]);
要注意的是全局变量属于单个着色程序,如果多个着色程序有同名全局变量,需要找到每个全局变量并设置自己的值。 我们调用 gl.uniform???(4fv)
的时候只是设置了当前程序的全局变量,当前程序是传递给 gl.useProgram
的最后一个程序。
全局变量有很多类型, 对应的类型有对应的设置方法(下一篇介绍)
还有一些类型 bool
, bvec2
, bvec3
, bvec4
. 它们可用 gl.uniform?f?
或 gl.uniform?i?
片断着色器
一个片断着色器的工作是为当前光栅化的像素提供颜色值,通常是以下的形式
precision mediump float;
void main() {
gl_FragColor = doMathToMakeAColor;
}
每个像素都将调用一次片断着色器,每次调用需要从你设置的特殊全局变量 gl_FragColor
中获取颜色信息。
片断着色器所需的数据,可以通过以下三种方式获取:
Uniforms
全局变量 (values that stay the same for every pixel of a single draw call)Textures
纹理 (data from pixels/texels)Varyings
可变量 (data passed from the vertex shader and interpolated)
在着色器中获取纹理信息,可以先创建一个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);
}
从纹理中获取的数据取决于很多设置, 至少要创建并给纹理填充数据, 例如
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
const level = 0;
const width = 2;
const height = 1;
const 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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
在初始化时找到全局变量的地址
const someSamplerLoc = gl.getUniformLocation(someProgram, "u_texture");
在渲染的时候WebGL要求纹理必须绑定到一个纹理单元上
const 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;
}
上方的示例几乎没有意义,通常情况下直接将裁剪空间的值传给片断着色器当作颜色值是没有意义的, 虽然它可以运行并且可以生成颜色值。
顶点着色器代码参考: https://github.com/bajiu/blog_code/blob/master/webGL/class/class_05.js