美文网首页我爱编程
WebGL-学习笔记(四)

WebGL-学习笔记(四)

作者: Patrick浩 | 来源:发表于2018-04-15 21:44 被阅读0次
    WebGL学习笔记(四).png

    最近学习构建三维图形的时候,深感几何功底不够,一个视图变化矩阵看了几天也没想过来,只勉强理解原理,细节部分自己还需要加强学习

    1. 视图变换

    在二维图形绘制的时候,不用考虑z轴,但是绘制的三维图形处于一个立体空间,就要使用z轴了。由于存在z轴,那么在物体观察的时候首先就是要确定观察坐标系

    1.1 视点,观察点,正方向

    观察坐标系可以通过定义:视点,观察点,正方向确定

    视点:是眼睛所在的位置,可以认为是观察坐标系的原点
    观察点:是观察对象所在位置,虽然观察对象是一个三维物体,但是物体都是有点组成,那么可以确认任何一个点作为观察点
    视线:视点和观察点之间的连线
    正方向:我的理解是观察坐标系下y轴的正方向,也就是当人观察事物的时候,头顶的朝向

    当确定好以上三个要素以后,就可以确定一个观察的坐标系了,为什么?
    目前使用坐标系都是直角坐标系,也就是在y轴和视线方向确定好以后,y轴和视线组成了一个平面,那么x轴需要和这个平面垂直,并且经过视点(因为默认视点为原点),之后z轴需要和y轴还有x轴垂直,所以也就确定下来了。
    在使用WebGL绘制点的时候都是使用的WebGL的坐标系,但是现在的视点并不一定是WebGL坐标系中的原点,所以这个时候,需要把视点转换为WebGL坐标系中的原点,才能正确的利用WebGL绘制图形,将视点转换为WebGL原点的且y轴正方向和WebGL的y轴重合的变换矩阵就是视图矩阵。
    连续看了几天也没有特别明白这个转换矩阵是如何运作的,所以这里先暂时贴出公式(代码摘抄自WebGL编程指南)

    Matrix4.prototype.setLookAt = function(eyeX, eyeY, eyeZ, centerX,     centerY, centerZ, upX, upY, upZ) {
      var e, fx, fy, fz, rlf, sx, sy, sz, rls, ux, uy, uz;
      fx = centerX - eyeX;
      fy = centerY - eyeY;
      fz = centerZ - eyeZ;
      // Normalize f.
      rlf = 1 / Math.sqrt(fx*fx + fy*fy + fz*fz);
      fx *= rlf;
      fy *= rlf;
      fz *= rlf;
      // Calculate cross product of f and up.
      sx = fy * upZ - fz * upY;
      sy = fz * upX - fx * upZ;
      sz = fx * upY - fy * upX;
      // Normalize s.
      rls = 1 / Math.sqrt(sx*sx + sy*sy + sz*sz);
      sx *= rls;
      sy *= rls;
      sz *= rls;
      // Calculate cross product of s and f.
      ux = sy * fz - sz * fy;
      uy = sz * fx - sx * fz;
      uz = sx * fy - sy * fx;
      // Set to this.
      e = this.elements;
      e[0] = sx;
      e[1] = ux;
      e[2] = -fx;
      e[3] = 0;
      e[4] = sy;
      e[5] = uy;
      e[6] = -fy;
      e[7] = 0;
      e[8] = sz;
      e[9] = uz;
      e[10] = -fz;
      e[11] = 0;
      e[12] = 0;
      e[13] = 0;
      e[14] = 0;
      e[15] = 1;
      // Translate.
      return this.translate(-eyeX, -eyeY, -eyeZ);
    };
    Matrix4.prototype.translate = function(x, y, z) {
      var e = this.elements;
      e[12] += e[0] * x + e[4] * y + e[8]  * z;
      e[13] += e[1] * x + e[5] * y + e[9]  * z;
      e[14] += e[2] * x + e[6] * y + e[10] * z;
      e[15] += e[3] * x + e[7] * y + e[11] * z;
      return this;
    };
    

    PS:公式主要是将点进行了反向的旋转和平移变换

    2. 可视范围

    我们观察时候的坐标系是我们自定定义的尺度,而WebGL坐标系中坐标范围是(-1.0,1.0),一旦超出的坐标就不会绘制了,从而导致图形缺失。如果要让视野所见的所有内容都包含在坐标系中,就可能需要对坐标进行缩放和平移,来改变可视范围,常用的方式有两种

    2.1 正射投影

    长方体的可视范围,是一种盒状空间,用于建筑平面设计。利用近裁剪面和远裁剪面来确定可视区域,因此使用六个参数可以确定正射投影:left,right,top,bottom,near,far,具体正射投影矩阵构建公式如下:

    Matrix4.prototype.setOrtho = function(left, right, bottom, top, near, far) {
      var e, rw, rh, rd;
      if (left === right || bottom === top || near === far) {
        throw 'null frustum';
      }
      rw = 1 / (right - left);
      rh = 1 / (top - bottom);
      rd = 1 / (far - near);
      e = this.elements;
      e[0]  = 2 * rw;
      e[1]  = 0;
      e[2]  = 0;
      e[3]  = 0;
      e[4]  = 0;
      e[5]  = 2 * rh;
      e[6]  = 0;
      e[7]  = 0;
      e[8]  = 0;
      e[9]  = 0;
      e[10] = -2 * rd;
      e[11] = 0;
      e[12] = -(right + left) * rw;
      e[13] = -(top + bottom) * rh;
      e[14] = -(far + near) * rd;
      e[15] = 1;
      return this;
    };
    

    2.2 透视矩阵

    透视矩阵是四棱锥的可视空间,一般用于游戏设计,符合现实场景(近大远小的效果)。也存在近裁剪面和远裁剪面,需要4个参数来确定相关变换矩阵:fovy(可视空间顶面和底面的夹角),aspect(裁剪面高宽比),near,far

    Matrix4.prototype.setPerspective = function(fovy, aspect, near, far) {
      var e, rd, s, ct;
      if (near === far || aspect === 0) {
        throw 'null frustum';
      }
      if (near <= 0) {
        throw 'near <= 0';
      }
      if (far <= 0) {
        throw 'far <= 0';
      }
      fovy = Math.PI * fovy / 180 / 2;
      s = Math.sin(fovy);
      if (s === 0) {
        throw 'null frustum';
      }
      rd = 1 / (far - near);
      ct = Math.cos(fovy) / s;
      e = this.elements;
      e[0]  = ct / aspect;
      e[1]  = 0;
      e[2]  = 0;
      e[3]  = 0;
      e[4]  = 0;
      e[5]  = ct;
      e[6]  = 0;
      e[7]  = 0;
      e[8]  = 0;
      e[9]  = 0;
      e[10] = -(far + near) * rd;
      e[11] = -1;
      e[12] = 0;
      e[13] = 0;
      e[14] = -2 * near * far * rd;
      e[15] = 0;
      return this;
    };
    

    3. 深度处理

    3.1 隐藏面消除

    WebGL默认深度的并不会对深度进行处理,会按照我们对点/面的绘制顺序进行绘制,也就最后绘制的内容会在最前面,违背了本来的意图,不过WebGL提供了对应的方法来处理深度关系:

    首先,利用gl.enable(gl.DEPTH_TEST)开启隐藏面消除功能
    然后,利用深度清理gl.clear(gl.DEPTH_BUFFER_BIT),可以让WebGL自己处理好深度关系

    3.2 深度冲突

    由于存在两个平面处在同一个深度的情况,这个时候WebGL绘制会出现深度冲突,表现为图形绘制结果看上去表面斑驳,于是WebGL提供了多边形偏移的功能,让即使深度一致的两个表面也会发生一定深度的偏移

    首先,利用gl.enable(gl.POLYGON_OFFSET_FILL)开启多边形偏移功能
    然后,利用gl.polygonOffset(1.0, 1.0)来指定计算偏移量的参数

    4. 绘制立方体

    要绘制立方体,仍然可以使用gl.drawArrays(),利用缓冲区数据来绘制表面,除此之外,为了高效利用图形中的坐标信息,可以使用gl.drawElements()配合将序列放到gl.ELEMENT_BUFFER_ARRAY来绘制表面。
    具体做法如下:

    function initPoint(gl, a_Position, a_Color) {
        let pointData = new Float32Array([
            0.0, 0.5, 0.0, 1.0, 0.0, 0.0,
            -0.5, -0.5, 0.5, 1.0, 0.0, 0.0,
            0.5, -0.5, 0.5, 1.0, 0.0, 0.0,
    
            0.0, 0.5, 0.0, 0.0, 1.0, 0.0,
            0.5, -0.5, 0.5, 0.0, 1.0, 0.0,
            0.0, -0.5, -0.5, 0.0, 1.0, 0.0,
    
            0.0, 0.5, 0.0, 0.0, 0.0, 1.0,
            0.0, -0.5, -0.5, 0.0, 0.0, 1.0,
            -0.5, -0.5, 0.5 ,0.0, 0.0, 1.0,
    
            -0.5, -0.5, 0.5, 1.0, 0.0, 1.0,
            0.0, -0.5, -0.5, 1.0, 0.0, 1.0,
            0.5, -0.5, 0.5, 1.0, 0.0, 1.0
        ]);
        // 利用的坐标序列
        let indexData = new Uint8Array([
            0, 1, 2,
            3, 4, 5,
            6, 7, 8,
            9, 10, 11
        ])
        let FSIZE = pointData.BYTES_PER_ELEMENT;
        let buffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ARRAY_BUFFER, pointData, gl.STATIC_DRAW);
        gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 6*FSIZE, 0);
        gl.enableVertexAttribArray(a_Position);
        gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 6*FSIZE, 3*FSIZE);
        gl.enableVertexAttribArray(a_Color);
        // 将坐标序列信息存储到ELEMENT_ARRAY_BUFFER
        let indexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW);
    }
    

    5. 总结

    构建三维图形相比二维图形来说,需要使用视图转换和可视范围的矩阵信息,因此坐标信息就变为了:gl_Position = 可视矩阵 X 视图矩阵 X 变换矩阵 X 原始坐标,同时要利用深度规则消除深度影响,最后可以利用gl.drawElements(),定义序列,复用坐标信息

    6. 参考

    《WebGL编程指南》

    相关文章

      网友评论

        本文标题:WebGL-学习笔记(四)

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