WebGL 实践篇(五)三维图形的绘制及矩阵变换、正射投影

一 三维“F”的绘制

1.着色器

按照上一篇提到的矩阵变换,我们可以直接在顶点着色器中加入相应的矩阵变换,这样就可以简化着色器代码,通过变量传入矩阵的值也便于之后矩阵变换的修改。

三维图形的绘制相比于二维图形只在参数类型上有一些变化(注意vec4以及mat4):

  <script id="vertex-shader-3D" type="x-shader/x-vertex">
    attribute vec4 a_position;

    uniform mat4 u_matrix;

    void main(){
      gl_Position = u_matrix * a_position;
    }
  </script>
  <script id="fragment-shader-3D" type="x-shader/x-fragment">
    precision mediump float;
    uniform vec4 u_color;

    void main(){
      gl_FragColor = u_color;
    }
  </script>

2.绘制信息(主要是位置坐标点)

 如上图,我们可以看出要绘制的三维图形”F"所需要的矩形面总共有16个,而每个矩形面需要2个三角形。

function setGeometry(gl) {
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        // left column front
        0, 0, 0,
        30, 0, 0,
        0, 150, 0,
        0, 150, 0,
        30, 0, 0,
        30, 150, 0,

        // top rung front
        30, 0, 0,
        100, 0, 0,
        30, 30, 0,
        30, 30, 0,
        100, 0, 0,
        100, 30, 0,

        // middle rung front
        30, 60, 0,
        67, 60, 0,
        30, 90, 0,
        30, 90, 0,
        67, 60, 0,
        67, 90, 0,

        // left column back
        0, 0, 30,
        30, 0, 30,
        0, 150, 30,
        0, 150, 30,
        30, 0, 30,
        30, 150, 30,

        // top rung back
        30, 0, 30,
        100, 0, 30,
        30, 30, 30,
        30, 30, 30,
        100, 0, 30,
        100, 30, 30,

        // middle rung back
        30, 60, 30,
        67, 60, 30,
        30, 90, 30,
        30, 90, 30,
        67, 60, 30,
        67, 90, 30,

        // top
        0, 0, 0,
        100, 0, 0,
        100, 0, 30,
        0, 0, 0,
        100, 0, 30,
        0, 0, 30,

        // top rung right
        100, 0, 0,
        100, 30, 0,
        100, 30, 30,
        100, 0, 0,
        100, 30, 30,
        100, 0, 30,

        // under top rung
        30, 30, 0,
        30, 30, 30,
        100, 30, 30,
        30, 30, 0,
        100, 30, 30,
        100, 30, 0,

        // between top rung and middle
        30, 30, 0,
        30, 30, 30,
        30, 60, 30,
        30, 30, 0,
        30, 60, 30,
        30, 60, 0,

        // top of middle rung
        30, 60, 0,
        30, 60, 30,
        67, 60, 30,
        30, 60, 0,
        67, 60, 30,
        67, 60, 0,

        // right of middle rung
        67, 60, 0,
        67, 60, 30,
        67, 90, 30,
        67, 60, 0,
        67, 90, 30,
        67, 90, 0,

        // bottom of middle rung.
        30, 90, 0,
        30, 90, 30,
        67, 90, 30,
        30, 90, 0,
        67, 90, 30,
        67, 90, 0,

        // right of bottom
        30, 90, 0,
        30, 90, 30,
        30, 150, 30,
        30, 90, 0,
        30, 150, 30,
        30, 150, 0,

        // bottom
        0, 150, 0,
        0, 150, 30,
        30, 150, 30,
        0, 150, 0,
        30, 150, 30,
        30, 150, 0,

        // left side
        0, 0, 0,
        0, 0, 30,
        0, 150, 30,
        0, 0, 0,
        0, 150, 30,
        0, 150, 0
      ]), gl.STATIC_DRAW)
    }

3.绘制并渲染

      webgl.useProgram(program);
      webgl.enableVertexAttribArray(positionAttributeLocation);
      webgl.bindBuffer(webgl.ARRAY_BUFFER, positionBuffer);
      webgl.vertexAttribPointer(positionAttributeLocation, 3, webgl.FLOAT, false, 0, 0);

      var matrix = m4.projection(webgl.canvas.clientWidth, webgl.canvas.clientHeight, 400);
      matrix = m4.translate(matrix, translations[0], translations[1], translations[2]);
      matrix = m4.xRotate(matrix, rotations[0]);
      matrix = m4.yRotate(matrix, rotations[1]);
      matrix = m4.zRotate(matrix, rotations[2]);
      matrix = m4.scale(matrix, scale[0], scale[1], scale[2]);

      webgl.uniformMatrix4fv(matrixUniformLocation, false, matrix);
      webgl.uniform4fv(colorUniformLocation, color);

      webgl.drawArrays(webgl.TRIANGLES, 0, 16 * 6);

注意:vertexAttribPointer的第二个参数为3是因为咱们的顶点坐标变成了3维,也就是在bufferData中设定的各面的顶点坐标(x,y,z),而w可以使用默认值1从而能够不传(因为顶点着色器中坐标的变量类型为vec4)。而对于drawArrays中的第三个参数是指总共要绘制多少个顶点,由于我们需要绘制16个矩形,每个矩形包含两个三角形,而每个三角形又包含3个顶点,因此总共需要绘制16*6个顶点。

结果如下:

 二 三维下的矩阵变换

1.屏幕像素空间与裁剪空间之间的关系 - 投影矩阵

在像素空间中,宽取值范围在0到width,高在0到height,深度在-depth/2 到 +depth/2。让我们对裁剪空间中的宽高深度根据投影矩阵进行拆解:

x' = 2/width * x - w,x' ∈ [-1,1]

y' = -2/height * y + w,y' ∈ [-1,1]

z' = 2/depth * w,z' ∈ [-1,1]

在二维当中,z的默认值为1;而在三维当中,w的默认值为1.

因此,反推回去,像素空间中的宽高以及深度也就一目了然了。在之后矩阵变换的传值当中会传一个与width相似的值给depth。

      projection: function (width, height, depth) {
        return [
          2 / width, 0, 0, 0,
          0, -2 / height, 0, 0,
          0, 0, 2 / depth, 0,
          -1, 1, 0, 1
        ]
      },

      translation: function (tx, ty, tz) {
        return [
          1, 0, 0, 0,
          0, 1, 0, 0,
          0, 0, 1, 0,
          tx, ty, tz, 1,
        ];
      },

      xRotation: function (angleInRadians) {
        var c = Math.cos(angleInRadians);
        var s = Math.sin(angleInRadians);

        return [
          1, 0, 0, 0,
          0, c, s, 0,
          0, -s, c, 0,
          0, 0, 0, 1,
        ];
      },

      yRotation: function (angleInRadians) {
        var c = Math.cos(angleInRadians);
        var s = Math.sin(angleInRadians);

        return [
          c, 0, -s, 0,
          0, 1, 0, 0,
          s, 0, c, 0,
          0, 0, 0, 1,
        ];
      },

      zRotation: function (angleInRadians) {
        var c = Math.cos(angleInRadians);
        var s = Math.sin(angleInRadians);

        return [
          c, s, 0, 0,
          -s, c, 0, 0,
          0, 0, 1, 0,
          0, 0, 0, 1,
        ];
      },

      scaling: function (sx, sy, sz) {
        return [
          sx, 0, 0, 0,
          0, sy, 0, 0,
          0, 0, sz, 0,
          0, 0, 0, 1,
        ];
      }

2. 旋转矩阵

二维图形绕着z轴做旋转就好了,而三维图形可以绕着三个轴分别进行旋转,其内里就是将其中一个轴作为旋转轴然后根据其他两个轴来计算旋转后的坐标,原理其实差不多,这里也就不赘述了。

绕z轴旋转:

        x' = x * cos + y * sin;

        y' = x*(-sin) + y * cos;

绕x轴旋转:

        y' = y * cos + z * sin;

        z' = y*(-sin) + z * cos;

绕y轴旋转:

        x' = x * cos + z * sin;

        z' = x * (-sin) + z * cos;

三 可变量的颜色属性

1.着色器代码(利用varying变量将颜色值从顶点着色器到片段着色器):

  <script id="vertex-shader-3D" type="x-shader/x-vertex">
    attribute vec4 a_position;
    attribute vec4 a_color;

    uniform mat4 u_matrix;

    varying vec4 v_color;

    void main(){
      gl_Position = u_matrix * a_position;
      
      v_color = a_color;
    }
  </script>
  <script id="fragment-shader-3D" type="x-shader/x-fragment">
    precision mediump float;
    varying vec4 v_color;

    void main(){
      gl_FragColor = v_color;
    }
  </script>

2.设置渲染颜色(每个面设置不同的颜色以便区分,注意颜色使用的类型与坐标使用的类型不同):

    function setColors(gl) {
      gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array([
        // left column front
        200, 70, 120,
        200, 70, 120,
        200, 70, 120,
        200, 70, 120,
        200, 70, 120,
        200, 70, 120,

        // top rung front
        200, 70, 120,
        200, 70, 120,
        200, 70, 120,
        200, 70, 120,
        200, 70, 120,
        200, 70, 120,

        // middle rung front
        200, 70, 120,
        200, 70, 120,
        200, 70, 120,
        200, 70, 120,
        200, 70, 120,
        200, 70, 120,

        // left column back
        80, 70, 200,
        80, 70, 200,
        80, 70, 200,
        80, 70, 200,
        80, 70, 200,
        80, 70, 200,

        // top rung back
        80, 70, 200,
        80, 70, 200,
        80, 70, 200,
        80, 70, 200,
        80, 70, 200,
        80, 70, 200,

        // middle rung back
        80, 70, 200,
        80, 70, 200,
        80, 70, 200,
        80, 70, 200,
        80, 70, 200,
        80, 70, 200,

        // top
        70, 200, 210,
        70, 200, 210,
        70, 200, 210,
        70, 200, 210,
        70, 200, 210,
        70, 200, 210,

        // top rung right
        200, 200, 70,
        200, 200, 70,
        200, 200, 70,
        200, 200, 70,
        200, 200, 70,
        200, 200, 70,

        // under top rung
        210, 100, 70,
        210, 100, 70,
        210, 100, 70,
        210, 100, 70,
        210, 100, 70,
        210, 100, 70,

        // between top rung and middle
        210, 160, 70,
        210, 160, 70,
        210, 160, 70,
        210, 160, 70,
        210, 160, 70,
        210, 160, 70,

        // top of middle rung
        70, 180, 210,
        70, 180, 210,
        70, 180, 210,
        70, 180, 210,
        70, 180, 210,
        70, 180, 210,

        // right of middle rung
        100, 70, 210,
        100, 70, 210,
        100, 70, 210,
        100, 70, 210,
        100, 70, 210,
        100, 70, 210,

        // bottom of middle rung.
        76, 210, 100,
        76, 210, 100,
        76, 210, 100,
        76, 210, 100,
        76, 210, 100,
        76, 210, 100,

        // right of bottom
        140, 210, 80,
        140, 210, 80,
        140, 210, 80,
        140, 210, 80,
        140, 210, 80,
        140, 210, 80,

        // bottom
        90, 130, 110,
        90, 130, 110,
        90, 130, 110,
        90, 130, 110,
        90, 130, 110,
        90, 130, 110,

        // left side
        160, 160, 220,
        160, 160, 220,
        160, 160, 220,
        160, 160, 220,
        160, 160, 220,
        160, 160, 220
      ]), gl.STATIC_DRAW);
    }

3.启用属性并传值

      webgl.enableVertexAttribArray(colorAttributeLocation);
      webgl.bindBuffer(webgl.ARRAY_BUFFER, colorBuffer);
      webgl.vertexAttribPointer(colorAttributeLocation, 3, webgl.UNSIGNED_BYTE, true, 0, 0);

注意:vertexAttribPointer的第三个参数webgl.UNSIGNED_BYTE和第四个参数true(规范化:即将取值范围设成0-1)

结果如下:

 四 正反面的绘制

1.WebGL中的正反面

WebGL中的三角形有正反面的概念,正面三角形的顶点顺序是逆时针方向,反面三角形的顶点顺序是顺时针方向。

 由上图可以看出,红色是正面,蓝色是背面。红色先绘制而蓝色后绘制的原因在于设置数据的顺序。

2.开启只绘制正面或反面

开启只绘制正面或者反面三角形(这行代码加载refreshDraw的函数里面):

      webgl.enable(webgl.CULL_FACE);

结果如下:

 三角形的正反面是根据着色器的最终计算结果来判定的,如果将X轴缩放-1,那么一个顺时针的三角形就会变成逆时针;如果将顺时针的三角形旋转180°,那么它就会变成逆时针的三角形(相当于按旋转轴进行翻转,原来面向你的面现在背向了你,可以拿张纸实操一下更容易理解)。

结果如下:

 3.修正坐标顺序(即修正朝向)

    function setGeometry(gl) {
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        // left column front
        0, 0, 0,
        0, 150, 0,
        30, 0, 0,
        0, 150, 0,
        30, 150, 0,
        30, 0, 0,

        // top rung front
        30, 0, 0,
        30, 30, 0,
        100, 0, 0,
        30, 30, 0,
        100, 30, 0,
        100, 0, 0,

        // middle rung front
        30, 60, 0,
        30, 90, 0,
        67, 60, 0,
        30, 90, 0,
        67, 90, 0,
        67, 60, 0,

        // left column back
        0, 0, 30,
        30, 0, 30,
        0, 150, 30,
        0, 150, 30,
        30, 0, 30,
        30, 150, 30,

        // top rung back
        30, 0, 30,
        100, 0, 30,
        30, 30, 30,
        30, 30, 30,
        100, 0, 30,
        100, 30, 30,

        // middle rung back
        30, 60, 30,
        67, 60, 30,
        30, 90, 30,
        30, 90, 30,
        67, 60, 30,
        67, 90, 30,

        // top
        0, 0, 0,
        100, 0, 0,
        100, 0, 30,
        0, 0, 0,
        100, 0, 30,
        0, 0, 30,

        // top rung right
        100, 0, 0,
        100, 30, 0,
        100, 30, 30,
        100, 0, 0,
        100, 30, 30,
        100, 0, 30,

        // under top rung
        30, 30, 0,
        30, 30, 30,
        100, 30, 30,
        30, 30, 0,
        100, 30, 30,
        100, 30, 0,

        // between top rung and middle
        30, 30, 0,
        30, 60, 30,
        30, 30, 30,
        30, 30, 0,
        30, 60, 0,
        30, 60, 30,

        // top of middle rung
        30, 60, 0,
        67, 60, 30,
        30, 60, 30,
        30, 60, 0,
        67, 60, 0,
        67, 60, 30,

        // right of middle rung
        67, 60, 0,
        67, 90, 30,
        67, 60, 30,
        67, 60, 0,
        67, 90, 0,
        67, 90, 30,

        // bottom of middle rung.
        30, 90, 0,
        30, 90, 30,
        67, 90, 30,
        30, 90, 0,
        67, 90, 30,
        67, 90, 0,

        // right of bottom
        30, 90, 0,
        30, 150, 30,
        30, 90, 30,
        30, 90, 0,
        30, 150, 0,
        30, 150, 30,

        // bottom
        0, 150, 0,
        0, 150, 30,
        30, 150, 30,
        0, 150, 0,
        30, 150, 30,
        30, 150, 0,

        // left side
        0, 0, 0,
        0, 0, 30,
        0, 150, 30,
        0, 0, 0,
        0, 150, 30,
        0, 150, 0
      ]), gl.STATIC_DRAW)
    }

结果如下:

4.开启深度缓冲(Z-Buffer)

由上图可以看出,呈现出来的“F”还是有些奇怪的,按常理来说有的矩形面应该会被前面的面遮挡住才对呀。这是因为我们还没有开启深度缓存,简单理解深度缓存就是为了遮挡后面元素(即深度值不大的元素)。

      webgl.clear(webgl.COLOR_BUFFER_BIT | webgl.DEPTH_BUFFER_BIT);

      webgl.enable(webgl.DEPTH_TEST);

注意:在开启深度缓冲之前要先清空深度缓冲噢~这个应该是相当于将Z值从裁剪空间([-1,1])转换到深度空间([0,1])。

结果如下(是不是正常多啦~): 

五 正射投影

上面的涉及到的投影矩阵就是我们常说的正射投影啦,只不过真正的正射投影的函数会更完善一些,涉及到各个方向(上下左右前后),通常用ortho或者相关的函数来进行调用。

比如,ortho(left,right,bottom,top,near,far){}

具体的可以参考你所使用的外部库里面对于正射投影函数的定义。

调用时每个参数可以这样设置(作为参考):

ortho(0,webgl.canvas.clientWidth,webgl.canvas.clientHeight,0,400,-400)

发现了吗,就是我们上面提到过的像素空间的取值范围。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值