WebGL打开 3D 世界的大门(三):着色器和GLSL语言

这一篇文章将会比较硬核,主要讲解着色器和GLSL语言,内容比较枯燥,我将尽量用通俗易懂的话讲解。在上一节我们提到webgl每次绘制都需要两个着色器,顶点着色器和片元着色器,一个顶点着色器和一个片元着色器放到一个着色器程序中,WebGL应用可以有多个着色器程序。

顶点着色器

通常是这样的,将点转换到裁剪空间(-1到1)。

csharp 复制代码
   void main() {
       gl_Position = transformPointsToClipspaceCoordinates
   }
   

顶点着色器可以通过以下方式获取数据:

  1. Attributes 属性 (从缓冲中获取的数据)

    这个代码例子就是,看过前两节的同学应该比较熟悉了。

    ini 复制代码
    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");
    // 开启从缓冲中获取数据
    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);

    这里不再重复讲解。这也是一种比较主要的方式。

  2. Uniforms 全局变量(在一次绘制中对所有顶点保持一致值)

    ini 复制代码
    attribute vec4 a_position;
    uniform vec4 u_offset; 
    void main() {
        gl_Position = a_position + u_offset;
    }

    让所有的点移动一个固定值,我们可以这样使用它:

    ini 复制代码
    var 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

    全局变量还是比较灵活的的可以是数组也可以是结构体。还可以对数组里面的值单独赋值。例如下面的结构体

    ini 复制代码
       struct 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) - 任意分量为true
  • all(x) - 所有分量为true
  • not(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做图像处理,将来配合参数少的人工智能模型还是比较有机会的。这个抽个时间再专门研究一下。有好的想法的同学欢迎留言。谢谢。

相关推荐
恋猫de小郭14 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端