这一篇文章将会比较硬核,主要讲解着色器和GLSL语言,内容比较枯燥,我将尽量用通俗易懂的话讲解。在上一节我们提到webgl每次绘制都需要两个着色器,顶点着色器和片元着色器,一个顶点着色器和一个片元着色器放到一个着色器程序中,WebGL应用可以有多个着色器程序。
顶点着色器
通常是这样的,将点转换到裁剪空间(-1到1)。
csharp
void main() {
gl_Position = transformPointsToClipspaceCoordinates
}
顶点着色器可以通过以下方式获取数据:
-
Attributes 属性 (从缓冲中获取的数据)
这个代码例子就是,看过前两节的同学应该比较熟悉了。
inivar 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"); // 开启从缓冲中获取数据 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 全局变量(在一次绘制中对所有顶点保持一致值)
iniattribute vec4 a_position; uniform vec4 u_offset; void main() { gl_Position = a_position + u_offset; }
让所有的点移动一个固定值,我们可以这样使用它:
inivar offsetLoc = gl.getUniformLocation(someProgram, "u_offset"); gl.uniform4fv(offsetLoc, [1, 0, 0, 0]); // 向右偏移一半屏幕宽度
要注意的是全局变量属于单个着色程序,如果多个着色程序有同名全局变量,需要找到每个全局变量并设置自己的值。全局变量有很多类型,对应的类型有对应的设置方法。
scss- 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
全局变量还是比较灵活的的可以是数组也可以是结构体。还可以对数组里面的值单独赋值。例如下面的结构体
inistruct SomeStruct { bool active; vec2 someVec2; }; uniform SomeStruct u_someThing; var someThingActiveLoc = gl.getUniformLocation(someProgram, "u_someThing.active");
片元着色器
片元着色器的主要任务栅格化像素的颜色值,片元着色器一般通过以下方式获取数据:
1,Uniforms 全局变量 ,这个和顶点着色器一样,不再重复
2,Varings可变量 ,在我们第二节例子里面有,可以自行查看,就是颜色值取决于顶点,其他颜色进行插值计算。
3,Textures 纹理 一般来说纹理是图片信息,下面给出一个用纹理加载图片的例子:
代码地址: gitee.com/feng-lianxi... 其实代码里面已经写的比较清楚了,现在讲解一下:片元着色器
csharp
// fragment shaders don't have a default precision so we need
// highp:高精度,适合需要高精度的计算。mediump:中等精度,适合一般的计算。lowp:低精度,适合颜色计算。
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D u_image;
void main() {
// gl_FragColor is a special variable a fragment shader
// is responsible for setting
gl_FragColor = texture2D(u_image, v_texCoord);; // return redish-purple
}
texture2D,是片元着色器的内置方法,用来加载图片,这里面有两个参数:有两个参数image和顶点坐标。也就是我们需要把图片加载进来,然后确定图片需要放到什么位置。
加载图片代码:
markdown
//****************************加载图片***********************
var image = new Image();
image.src = "./css/imgs/flower.png"; // 必须在同一域名下
image.onload = function () {
render(image);
}
//****************************将图片放到着色器里面***********************
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set the parameters so we can render any size image.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
// Upload the image into the texture.
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
然后设置图片存放位置。
ini
var texcoordLocation = gl.getAttribLocation(program, "a_texCoord");
var texcoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
1.0, 1.0,
1.0, 0.0,
0.0, 1.0,
0.0, 0.0,
]), gl.STATIC_DRAW);
// bind the texcoord buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.enableVertexAttribArray(texcoordLocation);
var size = 2; // 2 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
texcoordLocation, size, type, normalize, stride, offset);
这样就图片就加载成功了。
扩展一下我们可以直接对图片进行处理,例如锐化,描边,还有各种其他计算。这个回来有空再讲解。
内置函数和方法
下面是一些常见的内置函数:
1. 角度和三角函数
radians(degrees)
- 角度转弧度degrees(radians)
- 弧度转角度sin(angle)
- 正弦cos(angle)
- 余弦tan(angle)
- 正切asin(x)
- 反正弦acos(x)
- 反余弦atan(y, x)
- 反正切sinh(x)
- 双曲正弦cosh(x)
- 双曲余弦tanh(x)
- 双曲正切asinh(x)
- 反双曲正弦acosh(x)
- 反双曲余弦atanh(x)
- 反双曲正切
2. 指数函数
pow(x, y)
- x的y次方exp(x)
- e的x次方log(x)
- 自然对数exp2(x)
- 2的x次方log2(x)
- 以2为底的对数sqrt(x)
- 平方根inversesqrt(x)
- 平方根的倒数
3. 常用数学函数
abs(x)
- 绝对值sign(x)
- 符号函数floor(x)
- 向下取整ceil(x)
- 向上取整fract(x)
- 小数部分mod(x, y)
- 取模min(x, y)
- 最小值max(x, y)
- 最大值clamp(x, minVal, maxVal)
- 限制在范围内mix(x, y, a)
- 线性插值 (x*(1-a) + y*a)step(edge, x)
- 阶跃函数smoothstep(edge0, edge1, x)
- 平滑阶跃
4. 几何函数
length(x)
- 向量长度distance(p0, p1)
- 两点距离dot(x, y)
- 点积cross(x, y)
- 叉积 (仅vec3)normalize(x)
- 标准化向量faceforward(N, I, Nref)
- 翻转法线reflect(I, N)
- 反射向量refract(I, N, eta)
- 折射向量
5. 矩阵函数
matrixCompMult(x, y)
- 矩阵分量乘法outerProduct(c, r)
- 外积transpose(m)
- 转置矩阵determinant(m)
- 行列式inverse(m)
- 逆矩阵
6. 向量关系函数
lessThan(x, y)
- 分量比较 <lessThanEqual(x, y)
- 分量比较 <=greaterThan(x, y)
- 分量比较 >greaterThanEqual(x, y)
- 分量比较 >=equal(x, y)
- 分量比较 ==notEqual(x, y)
- 分量比较 !=any(x)
- 任意分量为trueall(x)
- 所有分量为truenot(x)
- 逻辑非
7. 纹理查询函数
texture2D(sampler, coord)
- 2D纹理采样 (WebGL1)texture(sampler, coord)
- 通用纹理采样 (WebGL2)textureCube(sampler, coord)
- 立方体贴图采样texture2DProj(sampler, coord)
- 投影纹理采样texture2DLod(sampler, coord, lod)
- 带LOD的采样textureSize(sampler, lod)
- 获取纹理尺寸texelFetch(sampler, coord, lod)
- 直接获取纹素
8. 片段处理函数
dFdx(p)
- x方向导数dFdy(p)
- y方向导数fwidth(p)
- 导数总和 (abs(dFdx(p)) + abs(dFdy(p)))
9. 噪声函数 (部分实现可能不支持)
noise1(x)
- 一维噪声noise2(x)
- 二维噪声noise3(x)
- 三维噪声noise4(x)
- 四维噪声
10. 原子操作 (WebGL2/GLSL ES 3.00)
atomicAdd()
atomicMin()
atomicMax()
atomicAnd()
atomicOr()
atomicXor()
atomicExchange()
atomicCompSwap()
11. 图像操作 (WebGL2/GLSL ES 3.00)
imageLoad()
imageStore()
imageAtomicAdd()
imageAtomicMin()
总结
到这里我们讲解了GLSL语法,以及怎么写着色器,怎么传值。3D世界的大门已经被我们推开一点。接下来我们介绍图像的旋转,平移,矩阵变换,为3维度变换做准备。感谢大家观看到这里。
最后
基于webgl,webgpu做图像处理,将来配合参数少的人工智能模型还是比较有机会的。这个抽个时间再专门研究一下。有好的想法的同学欢迎留言。谢谢。