WebGL打开 3D 世界的大门(四):二维变换和矩阵

本篇文章的内容假定你懂的矩阵的基本知识,例如线性变换,其次坐标,平移矩阵等知识。 有了二维变换的知识,我们就可以为三维变化做好准备。好戏开场了,我们先来画一个,字母E如图:

代码地址gitee.com/feng-lianxi...

顶点坐标如下:

markdown 复制代码
    var positions = [
            0, 0,
            300, 0, 
            300, 30,

            0, 0, 
            0, 30,
            300,30,

            0,0,
            0,400,
            30,400,

            0,0,
            30,0,
            30,400,

            0,400,
            300,400,
            0,370,

            0,370,
            300,370,
            300,400,

            0,185,
            200,185,
            200,215,

            0,215,
            0,185,
            200,215
        ];
     drawPoints(gl.TRIANGLES,positions,2,24) //画出字母E,
     

接下来有的同学就会想:那不简单了,我们给出变换矩阵,然后修改每个顶点的坐标,重新画一次不就行了!,我只能告诉你,同学醒醒,我们要充分利用gpu的性能,要节省出cpu,让gpu处理矩阵比cpu快的多。所以我们要修改shader,我们通过修改顶点的shader重新绘制,并且GLSL语言本身就支持向量计算。接下来我们实现一个平移动画,我们手把手实现:

平移变换

  1. 修改shader:顶点着色器。添加转换变量。

    ini 复制代码
       // an attribute will receive data from a buffer
       attribute vec2 a_position;
       uniform vec2 u_resolution;
       // attribute vec4 a_position;
       // all shaders have a main function
       varying vec4 v_color;
       uniform vec2 u_translation;
       void main() {
         // gl_Position is a special variable a vertex shader
         // is responsible for setting
         // 从像素坐标转换到 0.0 到 1.0
         vec2 position = a_position + u_translation;
         vec2 zeroToOne = position - u_resolution / 2.0;
    
         // 再把 0->1 转换 0->2
         vec2 zeroToTwo = zeroToOne / u_resolution * 2.0;
    
         // 把 0->2 转换到 -1->+1 (裁剪空间)
         vec2 clipSpace = zeroToTwo * vec2(1.0,-1.0);
    
         gl_Position = vec4(clipSpace, 0, 1);
         gl_PointSize = 10.0;
         v_color = vec4(clipSpace, 0.5, 1);
       }
       
  2. 修改u_translation变量并绘制:

    scss 复制代码
     var translationLocation = gl.getUniformLocation(program, "u_translation");
     gl.uniform2f(resolutionUniformLocation, translation, 0);
     // Set clear color to black, fully opaque
     gl.clearColor(0.0, 0.0, 0.0, 1.0);
     // Clear the color buffer with specified clear color
     gl.clear(gl.COLOR_BUFFER_BIT);
     gl.drawArrays(gl.TRIANGLES, 0, 24);
  3. 添加动画控制:

    ini 复制代码
     function animateTranslate(duration,distance) {
         let start = Date.now();
         function step() {
             console.log('run step;')
             let timePassed = Date.now() - start;
             let progress = timePassed / duration;
             if (progress <= 1) {
                 drawE(distance*progress);
                 window.requestAnimationFrame(step);
             } else {
                 return;
             }
         }
     
         window.requestAnimationFrame(step);
     }
    
     animateTranslate(3000,150);

变换矩阵

我们常见的几个矩阵是:

javascript 复制代码
   var m3 = {
       //平移矩阵
       translation: function(tx, ty) {
           return [
           1, 0, 0,
           0, 1, 0,
           tx, ty, 1,
           ];
       },
    
        //旋转矩阵
       rotation: function(angleInRadians) {
           var c = Math.cos(angleInRadians);
           var s = Math.sin(angleInRadians);
           return [
           c,-s, 0,
           s, c, 0,
           0, 0, 1,
           ];
       },
    
        //缩放矩阵
       scaling: function(sx, sy) {
           return [
           sx, 0, 0,
           0, sy, 0,
           0, 0, 1,
           ];
       },
   };
   

如果不太清楚为什么是这样的同学,可以补习一下矩阵变换的相关知识。

由于GLSL语言本身是支持矩阵计算的。所以我们可以继续修改shader。如下:

ini 复制代码
  // an attribute will receive data from a buffer
  attribute vec2 a_position;
  uniform vec2 u_resolution;
  uniform mat3 matrix_translate;
  uniform mat3 matrix_rotate;
  uniform mat3 matrix_scale;

  // attribute vec4 a_position;
  // all shaders have a main function
  varying vec4 v_color;
  //uniform vec2 u_translation;
  void main() {
    // gl_Position is a special variable a vertex shader
    // is responsible for setting
    // 从像素坐标转换到 0.0 到 1.0
    // vec2 position = a_position + u_translation;
    vec2 position = (matrix_translate * matrix_rotate * matrix_scale * vec3(a_position, 1)).xy;
    vec2 zeroToOne = position - u_resolution / 2.0;

    // 再把 0->1 转换 0->2
    vec2 zeroToTwo = zeroToOne / u_resolution * 2.0;

    // 把 0->2 转换到 -1->+1 (裁剪空间)
    vec2 clipSpace = zeroToTwo * vec2(1.0,-1.0);

    gl_Position = vec4(clipSpace, 0, 1);
    gl_PointSize = 10.0;
    v_color = vec4(clipSpace, 0.5, 1);
  }

然后就可以对全局变量赋值,进行旋转平移和缩放了。

ini 复制代码
    var translateUniform = gl.getUniformLocation(program, "matrix_translate");
    gl.uniformMatrix3fv(translateUniform, false, m3.translation(100,200));
    
    var rotateUniformn = gl.getUniformLocation(program, "matrix_rotate");
    gl.uniformMatrix3fv(rotateUniformn, false, m3.rotation(1));

    var scaleUniform = gl.getUniformLocation(program, "matrix_scale");
    gl.uniformMatrix3fv(scaleUniform, false, m3.scaling(0.5,0.5));

    gl.drawArrays(gl.TRIANGLES, 0, 24);
    

效果如下:

关于矩阵

有一点需要注意glsl里面的矩阵和向量和数学里面的矩阵向量结构是不相同的。 在数学里面,矩阵的列是向量,但是在编码里面,列是向量不太符合我们的编码习惯,在glsl里面行是向量,其实就是数学矩阵里面的转置矩阵,参考平移矩阵,这是由于历史原因,向量的数字在内存里面是连续的。

总结

我们已经介绍了基本作图和矩阵变换,接下来就让我们一起进入3d的世界吧,我们已经做好了充分的准备。我们来一起了解透视基本原理,下节更加精彩。

相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax