美文网首页前端开发
webgl变换:深入图形平移

webgl变换:深入图形平移

作者: yancy_1012 | 来源:发表于2021-03-10 19:19 被阅读0次

    在以前的文章里,不管是绘制图形,绘制点亦或者是改变色值,所有的内容都是静态的。

    webgl 里,图形的运动分为 平移、旋转、缩放 三种类型。

    接下来,我们会从零基础开始,一点一点来深入了解图形如何进行运动。

    首先来从零开始了解下图形的平移

    1. 图形平移

    首先我们来看如何实现图形的平移操作。

    平移的操作就是将图形的原始坐标加上对应的移动距离。首先来看下平移的实现

    const vertexShaderSource = "" +
          "attribute vec4 apos;" + // 定义一个坐标
          "uniform float x;" + // 处理 x 轴移动
          "uniform float y;" + // 处理 y 轴移动
          "void main(){" +
          " gl_Position.x = apos.x + x;" +
          " gl_Position.y = apos.y + y;" +
          " gl_Position.z = 0.0;" + // z轴固定
          " gl_Position.w = 1.0;" +
          "}";
    const fragmentShaderSource = "" +
          "void main(){" +
          " gl_FragColor = vec4(1.0,0.0,0.0,1.0);" +
          "}";
    
    // initShader已经实现了很多次,本次就不再赘述了
    const program = initShader(gl,vertexShaderSource,fragmentShaderSource);
    
    const buffer = gl.createBuffer();
    const data = new Float32Array([
      0.0,0.0,
      -0.5,-0.5,
      0.5,-0.5,
    ]);
    
    gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
    gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);
    
    const aposlocation = gl.getAttribLocation(program,'apos');
    const xlocation = gl.getUniformLocation(program,'x');
    const ylocation = gl.getUniformLocation(program,'y');
    
    gl.vertexAttribPointer(aposlocation,2,gl.FLOAT,false,0,0);
    gl.enableVertexAttribArray(aposlocation);
    
    let x = 0.0;
    let y = 0.0;
    function run () {
      gl.uniform1f(xlocation,x += 0.01);
      gl.uniform1f(ylocation,y += 0.01);
    
      gl.drawArrays(gl.TRIANGLES,0,3);
      // 使用此方法实现一个动画
      requestAnimationFrame(run)
    }
    run()
    

    解释:

    • 首先声明一个变量 x 和变量 y ,用来处理 x轴 和 y轴 的坐标。这里使用的是 uniform 变量,因为平移的操作对于图形上的所有顶点都有影响。
    • 通过 gl_Position.[xyzw] 来分别设置 x、y、z、w 的值。用于改变图形位置。
    • 使用 gl.uniform1f 来为 x 和 y 赋值
    • 使用 requestAnimationFrame 实现一个缓动动画。方便观察效果。
    • 其他的操作,缓冲区,绘制,赋值,激活,

    可以看到,这样处理图形移动的话很好理解,但是因为一个移动,我们声明了两个 uniform 变量来实现。并且分开设置的 xyz 坐标,非常的不方便。

    所以,在处理webgl变换(平移、缩放、旋转)的时候,通常使用矩阵来实现。接下来就来看看,如何使用矩阵实现图形的平移。

    2. 平移矩阵

    推导平移矩阵的步骤:

    • 获取平移前后的图形坐标(三维)
    • 计算平移前后的差值
    • 带入到平移矩阵
    • 处理图形顶点
    • 获得平移后的图形

    2.1 平移矩阵的推导

    首先让我们来看一幅图片。

    平移图解.png

    这幅图片的意义就是我们将橙色的三角形移动到蓝色虚线三角形处。

    移动之后的蓝色虚线三角形的三个坐标分别为

    • x’ = x + x1
    • y' = y + y1
    • z' = z + z1
    • w=1 齐次坐标为1

    2.2 获得平移矩阵

    webgl 中,通常使用矩阵来实现图形变换。下面我们来看看矩阵如何表示。

    平移矩阵.png

    左侧是平移之前的原始坐标,中间的是一个平移矩阵,经过两者相乘,可以得到一个平移之后的坐标。

    现在我们来看下平移矩阵如何计算得出

    首先通过上述图片中的矩阵我们来得到几个方程式。用左侧的列分别乘矩阵的行,可以得到一下公式

    • ax + by + cz + w = x'
    • ex + fy + gz + h = y'
    • ix + jy + kz + l = z'
    • mx + ny + oz + p = w'

    公式合并:

    第一节 里的四个方程式和第二节里的四个方程式合并,可以得到如下结果:

    • ax + by + cz + w = x + x1':只有当 a = 1,b = c = 0, w = x1 的时候,等式左右两边成立
    • ex + fy + gz + h = y + y1':只有当 f = 1, e = g = 0, h = y1 的时候,等式左右两边成立
    • ix + jy + kz + l = z + z1':只有当 k = 1,i = j = 0, l = z1 的时候,等式左右两边成立
    • mx + ny + oz + p = 1':只有当 m = n = o = 0, p = 1 的时候,等式左右两边成立

    经过上述方程式,可以得到一个平移的矩阵:

    | 1 0 0 x |

    | 0 1 0 y |

    | 0 0 1 z |

    | 0 0 0 1 |

    之后将平移矩阵和原始坐标相乘,就可以得到平移之后的坐标。

    3. 矩阵实战

    来看看使用矩阵如何处理图形的平移。

    第一步,创建着色器源代码�
    const vertexShaderSource = "" +
          "attribute vec4 apos;" +
          "uniform mat4 mat;" + // 创建一个 uniform 变量,代表平移矩阵
          "void main(){" +
          " gl_Position = mat * apos;" + // 矩阵与原始坐标相乘
          "}";
    const fragmentShaderSource = "" +
          "void main(){" +
          " gl_FragColor = vec4(1.0,0.0,0.0,1.0);" +
          "}";
    
    第二步,创建平移矩阵
    let Tx = 0.1;    //x坐标的位置
    let Ty = 0.1;    //y坐标的位置
    let Tz = 0.0;    //z坐标的位置
    let Tw = 1.0;    //差值
    const mat = new Float32Array([
      1.0,0.0,0.0,0.0,
      0.0,1.0,0.0,0.0,
      0.0,0.0,1.0,0.0,
      Tx,Ty,Tz,Tw,
    ]);
    

    这里可以看到,使用的矩阵和我们推导出来的矩阵不太一样,推导的平移矩阵里 xyzw 位于矩阵的右侧,现在是位于矩阵的底部,这是为什么呢?

    这是因为在 webgl 中,矩阵的使用需要按照 左上右下 的对角线做一次翻转。所以使用的矩阵,xyzw 位于底部

    第三步,绘制一个三角形
    const program = initShader(gl,vertexShaderSource,fragmentShaderSource);
    const aposlocation = gl.getAttribLocation(program,'apos');
    const data =  new Float32Array([
      0.0,0.0,
      -.3,-.3,
      .3,-.3
    ]);
    
    const buffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
    gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);
    
    gl.vertexAttribPointer(aposlocation,2,gl.FLOAT,false,0,0);
    gl.enableVertexAttribArray(aposlocation);
    
    gl.drawArrays(gl.TRIANGLES,0,3); // 第五步的时候会重写
    
    第四步,获取矩阵变量,给矩阵赋值
    const matlocation = gl.getUniformLocation(program,'mat');
    gl.uniformMatrix4fv(matlocation,false,mat);
    

    这里使用 gl.uniformMatrix4fv 来给矩阵赋值。

    第五步,添加缓动动画
    function run () {
      Tx += 0.01
      Ty += 0.01
      const mat = new Float32Array([
        1.0,0.0,0.0,0.0,
        0.0,1.0,0.0,0.0,
        0.0,0.0,1.0,0.0,
        Tx,Ty,Tz,Tw,
      ]);
      gl.uniformMatrix4fv(matlocation,false,mat);
      gl.drawArrays(gl.TRIANGLES,0,3);
    
      // 使用此方法实现一个动画
      requestAnimationFrame(run)
    }
    run()
    

    4. 完整代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Title</title>
    </head>
    <body>
    <canvas id="webgl" width="500" height="500"></canvas>
    <script>
      const gl = document.getElementById('webgl').getContext('webgl');
      const vertexShaderSource = "" +
        "attribute vec4 apos;" +
        "uniform mat4 mat;" +
        "void main(){" +
        " gl_Position = mat * apos;" +
        "}";
      const fragmentShaderSource = "" +
        "void main(){" +
        " gl_FragColor = vec4(1.0,0.0,0.0,1.0);" +
        "}";
    
      const program = initShader(gl,vertexShaderSource,fragmentShaderSource);
      const aposlocation = gl.getAttribLocation(program,'apos');
      const matlocation = gl.getUniformLocation(program,'mat');
    
      const data =  new Float32Array([
        0.0,0.0,
        -.3,-.3,
        .3,-.3
      ]);
      const buffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
      gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);
    
      gl.vertexAttribPointer(aposlocation,2,gl.FLOAT,false,0,0);
      gl.enableVertexAttribArray(aposlocation);
    
      let Tx = 0.1;    //x坐标的位置
      let Ty = 0.1;    //y坐标的位置
      let Tz = 0.0;    //z坐标的位置
      let Tw = 1.0;    //差值
      function run () {
        Tx += 0.01
        Ty += 0.01
        const mat = new Float32Array([
          1.0,0.0,0.0,0.0,
          0.0,1.0,0.0,0.0,
          0.0,0.0,1.0,0.0,
          Tx,Ty,Tz,Tw,
        ]);
        gl.uniformMatrix4fv(matlocation,false,mat);
        gl.drawArrays(gl.TRIANGLES,0,3);
    
        // 使用此方法实现一个动画
        requestAnimationFrame(run)
      }
      run()
      function initShader(gl,vertexShaderSource,fragmentShaderSource){
        const vertexShader = gl.createShader(gl.VERTEX_SHADER);
        const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    
        gl.shaderSource(vertexShader,vertexShaderSource);
        gl.shaderSource(fragmentShader,fragmentShaderSource);
    
        gl.compileShader(vertexShader);
        gl.compileShader(fragmentShader);
    
        const program = gl.createProgram();
    
        gl.attachShader(program,vertexShader);
        gl.attachShader(program,fragmentShader)
    
        gl.linkProgram(program);
        gl.useProgram(program);
        return program;
      }
    </script>
    </body>
    </html>
    

    至此,通过矩阵控制图形移动就全部实现完成了。

    今天的分享就到这儿了,

    Bye~

    相关文章

      网友评论

        本文标题:webgl变换:深入图形平移

        本文链接:https://www.haomeiwen.com/subject/effgqltx.html