顶点着色器和片元着色器是自定义绘图的主要工作,当把数据准备好之后,就需要在两个着色器里面进行表演了。
在着色器代码发挥作用之前,有很大一部分工作是准备数据,通常会在 JS 里定义类型数组来向着色器传递坐标或者颜色数据,可以看定义数组传递浮点型数值、矩阵等其他数据。通过 WebGL API 将 JS 里的数据传给着色器,在着色器中也可以写一些坐标、颜色计算相关的逻辑代码,进而实现丰富的变换动效。
下面的例子很简单,为了理解着色器的基本流程,只是单纯的把传入的坐标数据直接绘制,没有进行位置变换和颜色效果。
一、顶点着色器
// 创建顶点着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
// 着色器代码
const vertexShaderSource = `
attribute vec4 a_position; // buffer 中读取的顶点坐标
attribute vec2 a_texCoord; // buffer 中读取的纹理坐标
varying vec2 v_texCoord; // 传给片元着色器的纹理坐标
void main() {
gl_Position = a_position; // 对每一个顶点坐标进行操作
v_texCoord = a_texCoord; // 进行赋值,会传给片元着色器
}
`;
// 绑定着色器,提供着色器代码和数据
gl.shaderSource(vertexShader, vertexShaderSource);
// 编译着色器
gl.compileShader(vertexShader);
顶点着色器的任务是计算点的的位置。主要功能是通过顶点着色器代码语言 GLSL实现的,是一种类似 c 语言的语法,可以定义变量和方法,其中 main 方法是着色器运行的入口;后面会详细学习GLSL语言。
二、片元着色器
// 创建片元着色器
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
// 着色器代码
const fragmentShaderSource = `
varying vec2 v_texCoord; // 从顶点着色器传入的纹理坐标
uniform sampler2D u_image; // 纹理单元,默认是第0个
void main() {
// 对片元颜色进行操作
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
`;
// 绑定着色器,提供着色器代码和数据
gl.shaderSource(fragmentShader, fragmentShaderSource);
// 编译着色器
gl.compileShader(fragmentShader);
片元着色器的任务就是计算当前正在绘制图形的每个像素的颜色。重要的是片元着色器代码,类似顶点着色器代码, main 方法是运行的入口;
三、向着色器输入数据
WebGL 用 WeGLBuffer(数据缓冲区)与 WebGLTexture(纹理)作为一些数据存储的主要仓库🎪,存储JS传来的数据给WebGL的着色器使用,另一些数据可以直接作为全局变量直接传给WebGL的着色器使用。
顶点着色器需要的数据可以通过以下三种方式获得:
- Attributes 属性 (从缓冲中获取的数据)
- Uniforms 全局变量 (在一次绘制中对所有顶点保持一致值)
- Textures 纹理 (从像素或纹理元素中获取的数据)
片元着色器所需的数据可以通过以下三种方式获取:
- Uniforms 全局变量 (在一次绘制中对所有顶点保持一致值)
- Textures 纹理 (从像素或纹理元素中获取的数据)
- Varyings 可变量 (从顶点着色器插值之后传来的值)
3.1、Attributes-属性
Attributes 只能出现在顶点着色器中,可以从缓冲区中获取,被用来从外部向 WebGL 内部中传递顶点信息。
通常缓冲区中还可以存放包括坐标、法向量、纹理坐标、顶点颜色值等。 你可以存储任何数据。
变量名通常以 _a 开头。
Attributes 可以用以下数据类型:
- float:浮点型数据
- vec2:两个数字的数组,例如向量
- vec3:三个数字的数组,例如坐标
- vec4:四个数字的数组,例如齐次坐标
- mat2:2x2 矩阵
- mat3:3x3 矩阵
- mat4:4x4 矩阵
const fragmentShaderSource = `
attribute vec4 a_position; // 定义一个属性变量,将会从缓冲中获取数据
`;
数据输入和读取
往着色器中传递 Attributes 属性数据我们需要使用WebGLBuffer这个对象,通过 bindBuffer 和 bufferData 来把 JavaScript 的数据传给 WebGLBuffer,然后通过getAttribLocation和enableVertexAttribArray指定Buffer中的数据是给顶点着色器中哪一个变量使用,使用vertexAttribPointer启用数据读取。
详见 WebGLBuffer数据读写 这篇文章中。
3.2、uniforms-全局变量
全局变量,可以在顶点着色器和片元着色器中使用,在着色程序运行前赋值,在运行过程中全局有效。
一般用来对物体做整体变化、 旋转、缩放。
变量名通常以 _u 开头。
const fragmentShaderSource = `
uniform vec2 u_resolution; // 定义另一个全局变量,已经通过WebGL方法赋值了
`;
数据输入和读取
uniform 全局变量的数据读写较为简单。
往着色器中传递 uniform 类型的数据的时候,可以直接使用 WebGL 提供的 APIgl.uniform*??(),不需要额外的对象作为媒介了。
let offsetLoc = gl.getUniformLocation(someProgram, "u_offset"); // 找到变量位置
gl.uniform4fv(offsetLoc, [1, 0, 0, 0]); // 设置变量的值
可以直接设置int、float、vec*、vec* array、mat* array、bool、bvec2、bvec3、bvec4等各类的 uniform 数据:
- gl.uniform1f,它表示传递 1 个浮点数;
- gl.uniform1fv, 它表示传递一个 1 维的浮点数向量;
- gl.uniform2f,它表示传递 2 个浮点数;
- gl.uniform2fv, 它表示传递一个 2 维的浮点数向量;
- ..........
依次类推,具体的 API 可以参考developer.mozilla.org/zh-CN/docs/…
要注意的是:全局变量属于单个着色程序,如果多个着色程序有同名全局变量,需要找到每个全局变量并设置自己的值。 我们调用gl.uniform*??的时候只是设置了当前程序(当前程序是传递给 gl.useProgram 的最后一个程序)的全局变量。
3.3、textures-纹理
纹理可以在着色程序运行中随意读取其中的数据。大多数情况存放的是图像数据(来自Image或者数组),其本质上是存放的是一个数据序列,因此也可以随意存放除了颜色数据以外的其它数据。纹理往往需要搭配纹理坐标来使用(需要注意纹理坐标系和顶点坐标系不一致),纹理坐标一般是顶点着色器插值生成传给片元着色器的,通过纹理坐标获取对应片元的颜色。
const fragmentShaderSource = `
uniform sampler2D u_image; // sampler2D类型的全局变量,纹理单元,默认是第0个
gl_FragColor = texture2D(u_texture, texcoord); // 用GLSL方法texture2D 从纹理中提取信息
`;
纹理数据输入和读取
往着色器中传递纹理数据我们需要使用 WebGLTexture 这个对象,通过texImage2D这个方法可以把 Javascript 里的图像数据存到 WebGLTexture中,会自动存储放到片元着色器的第0个纹理单元,当然也可以手动指定。
详见 WebGLTxture 数据读写 这篇文章中。
3.4、varyings-可变量
可变量是顶点着色器和片元着色器之间的连接桥梁,通过在顶点着色器和片元着色器中使用相同的变量名称,将顶点着色器的变量值传给片元着色器。
根据渲染的图元是点、线还是三角形,顶点着色器中设置的可变量会在片元着色器运行中获取不同的插值。
变量名通常以 _v 开头。
const fragmentShaderSource = `
varying vec2 v_texCoord; // 从顶点着色器传入的纹理坐标
`;
四、调试着色器代码
getProgramParameter 这个方法用来判断 我们着色器 glsl 语言写的是不是对的, 然后你可以通过 getProgramInfoLog这个方法 类似于打 日志 去发现❌了。
// 创建着色器程序
const program = gl.createProgram()
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接 并使用着色器
gl.linkProgram(program)
gl.useProgram(program)
var program = createProgram(gl, vertexShader, fragmentShader);
const success = gl.getProgramParameter(program, gl.LINK_STATUS)
if (success) {
gl.useProgram(program)
}
console.error(gl.getProgramInfoLog(program), 'test---')
gl.deleteProgram(program)
这是WebGL 系列的入门文章,免费订阅,如有帮助请点赞收藏,纰漏之处欢迎指正!
也欢迎关注公众号交流知识哇😄