NO.5 OpenGl 第三维度

零蚀


前言

    • 这节本来想就着纹理一块,但是感觉又多又乱,算了,每一个节点都打磨清楚才是最重要的,贪多嚼不烂。
  • 内容:
    • 1、z&w,
    • 2、加入透视投影,解释原理、用法

第三维度

  • 简介
    • 其实在绘制方面,古老的艺术家们,会通过绘制一些看不见的点,来故意“诱拐”我们的视觉思维,让我们感觉我们正在欣赏一个三维的立体空间,而OpenGL则不是这么干的,这里OpenGL用到的理念,叫做line projection(线性投影)。其实这也很好理解,就像一束平行光看到某个物体,产生一种透视的效果。(但是我觉得书上关于线性投影的解释不够严谨)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ODzdVIBM-1591273228671)(media/15906367507236/15907161390246.jpg)]

    • clip space:这里我觉得怎么翻译都很变扭,clip是切的意思,其实就是一个范围空间,这个范围空间制定了我们的 x,y,z的取值范围是在[-w,w]之间,而opengl的设置的空间在[-1,1],也就是w=1,-w=-1,如果我们的图形超过了这个空间,那么绘制就不会被显示在界面上。

    • Perspective Division(透视分配法):在点由逻辑坐标绘制到机器上的物理坐标之前,他会进行一个步骤:Perspective Division,这个步骤,将我们的的x,y, z点都除以w,当w为距离时,越远的点,越靠近屏幕中心,opengl 以此来让我们误以为这是个3D的图形。

    • 我们在坐标中加入一个w变量,而不是用x , y除以z,以z为标准的做法,这样做,一方面是为x,y,z解藕,另一方面,为后面的深度缓冲区提供便利(后话)。

    • 然后我们了解一下齐次坐标,齐次坐标,就是一类同等倍数的坐标,例如,(1,1,1,2)、(2,2,2,4)、(9,9,9,18)他们都可以化为(1,1,1,2),则称此类坐标为齐次坐标。

  • 添加透视坐标W
    • 我们的坐标模型现在是(X,Y,Z,W,R,Q,B),所以我们可以如下修改我们的坐标参数。

      public class AirHockeyConstant {
      
          static float tableVertices[] = {
                  // coordinates:X Y Z W R G B
                     0f,    0f, 0f, 1.5f,    1f,   1f,   1f,
                  -0.5f, -0.8f, 0f, 1f,    0.7f, 0.7f, 0.7f,
                   0.5f, -0.8f, 0f, 1f,    0.7f, 0.7f, 0.7f,
      
                    0f,    0f, 0f, 1.5f,     1f,   1f,   1f,
                  0.5f, -0.8f, 0f,   1f,   0.7f, 0.7f, 0.7f,
                  0.5f,  0.8f, 0f,   2f,   0.7f, 0.7f, 0.7f,
      
                     0f,   0f, 0f, 1.5f,       1f,   1f,   1f,
                   0.5f, 0.8f, 0f,   2f,     0.7f, 0.7f, 0.7f,
                  -0.5f, 0.8f, 0f,   2f,     0.7f, 0.7f, 0.7f,
      
                     0f,   0f,  0f, 1.5f,      1f,   1f,   1f,
                  -0.5f, 0.8f,  0f,   2f,    0.7f, 0.7f, 0.7f,
                  -0.5f, -0.8f, 0f,   1f,    0.7f, 0.7f, 0.7f,
      
      
                  // 定义中位线
                  -0.5f, 0f, 0f, 1.5f,    1f, 0f, 0f,
                   0.5f, 0f, 0f, 1.5f,    1f, 0f, 0f,
                  // 定义曲棍球的曲棍位置
                  0f, -0.4f, 0f, 1.25f,   0f, 0f, 1f,
                  0f,  0.4f, 0f, 1.75f,   1f, 0f, 0f
          };
      }
      
      
    • 然后修改我们的坐标参数个数POSITION_COMPONENT_COUNT=4,我们就可以得到如下的图像。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3VDAQRdv-1591273228673)(media/15906367507236/15907282558232.jpg)]

    • 这里我们z轴为0 ,我们是通过w的比例变化,来让x,y的大小发生比例大小的变换,这样,我们就看起来好像确实更像3D效果了,但是我们想动态改变这种显示样式,我们该如何做呢?对w值进行硬编码,肯定不会这样,答案是利用矩阵,这就用了我们之前所说的透视投影(Perspective Projection)

  • Perspective Projection(透视投影)
    • 平截头体(Frustum):平截头体就是指刚好能包裹着个物体对象的(紫色边框,黄色填充)棱台。这是通过中心投影,分割而成的空间。这里的眼睛相当于所有的线性的线相交于一处的点,而这个点我们称之为焦点,这个点到棱台的上底面的距离叫做焦距。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kpUjdRBX-1591273228674)(media/15906367507236/15907568742448.jpg)]

    • 我们通常会使用透视投影来处理3D问题,一般我们将焦点放在z轴上,然后由w来控制投影的距离,这中控制方式都是通过矩阵来完成的。在应用中,有frustumM()perspectiveM()来完成透视投影的功能,但是frustumM()在一些功能上是存在bug的,而perspectiveM()是只支持4以上的Android手机系统。

    • 构建我们的透视矩阵,这里和书上不一样,我们使用的是perspectiveM()来完成一个透视矩阵。首先我们来看一下透视矩阵的方法使用。透视投影是构建了一个大型的平截头体,将内容包含在里面,这个可是控件的平截头体,就是我们的透视矩阵。平截头体内只会显示内部的物体,一旦物体超出了这个区间,就不会再显示,它的方法解释如下

      • 第一个参数 m ,是我们的的项目绘制矩阵projectionMatrix
      • 第二个参数 offset ,是第一个矩阵参数中元素的偏移量
      • 第三个参数是我们的视角角度fovy,一般真实的视角感官是45度
      • 第四个参数是我们的宽高比。
      • 第五个参数 zNear 是视觉的内容平截头体距离平截头体的距离如下图所示,一般zNear设置为0.1,zFar设置为100f,如果zNear设置过大,那么在[0~zNear]之间的物体会被剪切掉,就像游戏里有个物体距离你10m,结果走近一步,他就不见了,或者进入它内部。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MxlUBFSs-1591273228676)(media/15906367507236/15912456646252.jpg)]

      Matrix.perspectiveM(float[] m, int offset,float fovy, float aspect, float zNear, float zFar);
      
    • 当我们设置了平截头体之后,我们运行发现任然无法看到我们想看到的图形,如果我们设置的zNear是近视觉的近距离,这个坐标点位于z轴的负轴上,距离为到原点的距离,zFar同理。而我们的视角默认是处在z轴的原点位置。下图就是我们看不到物体的原因,所以我们得偏移我们的视觉空间内。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DiWCsOBl-1591273228677)(media/15906367507236/15912474937207.jpg)]

    • 但是我们是位置数据是有矩阵完成的, 我们该如何运算才能进行我们想要的平移呢?我们可一用一个单位矩阵(原子矩阵),通过矩阵之间的相加减来使得图像发生变化

      @Override
      public void onSurfaceChanged(GL10 gl, int width, int height) {
          GLES20.glViewport(0,0,width,height);
          final float aspectRatio = width > height ? (float)width/(float)height:(float)height/(float)width;
          if(width>height){
              // 横屏 landscapes
              Matrix.orthoM(projectionMatrix,0,-aspectRatio,aspectRatio,-1f, 1f, -1f, 1f);
          }else{
              // 竖屏 portrait
              Matrix.orthoM(projectionMatrix,0,-1f, 1f, -aspectRatio,aspectRatio,-1f, 1f);
      
          }
          Matrix.perspectiveM(projectionMatrix, 0, 45f, aspectRatio, 0f, 10f);
          float[] modelMatrix = new float[16];
          Matrix.setIdentityM(modelMatrix, 0);
          Matrix.translateM(modelMatrix, 0, 0f, 0f, -2f);
          final float[] temp = new float[16];
          Matrix.multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0);
          System.arraycopy(temp, 0, projectionMatrix, 0, temp.length);
      }
      
    • 在这里我们定义了一个单位矩阵modelMatrix,方法是setIdentityM然后我们将单位举证向后z的负半轴移动了2各单位,坐标点是-2。这样就将图像移到了我们视线的平截头体内。

    • 然后我们用Matrix.multiplyMM将连个矩阵相乘保留最后的结果到temp这个临时矩阵中,最后我们将temp的result拷贝进我们的projectionMatrix矩阵中。

    • 当然除了平移,我们还可以使用其他的方法,比如说旋转。第一个参数是矩阵,第二个参数是偏移量,第三个是角度,后面是(x,y,z)旋转的旋转轴(以坐标和原点连线为轴),淡然我们可以把这个设置成一个动态的过程。

      Matrix.rotateM(modelMatrix, 0, -60f, 1f, 0f, 0f);
      

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-orUsSGaF-1591273228678)(media/15906367507236/opengl.gif)]

    • 代码可以这么写,防燃你为了方便可以把它移到onDrawFrame里面,这样就不用什么线程了。大致代码如下:

      // 正投影矩阵
      private final float[] projectionMatrix = new float[16];
      // 矩阵的结果
      private final float[] resultMatrix = new float[16];
      
      ...
       @Override
      public void onSurfaceChanged(GL10 gl, int width, int height) {
          ...
      
          new Thread(){
              @Override
              public void run() {
                  while(true){
      
      
                      SystemClock.sleep(100);
                      i=(i+1)%360;
                      float[] modelMatrix= new float[16];
                      Matrix.setIdentityM(modelMatrix, 0);
                      Matrix.translateM(modelMatrix, 0, 0f, 0f, -2f);
                      Matrix.rotateM(modelMatrix, 0, i, 1f, 0f, 0f);
      
                      final float[] temp = new float[16];
                      Matrix.multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0);
                      System.arraycopy(temp, 0, resultMatrix, 0, temp.length);
      
                  }
              }
          }.start();
      
      
      
      }
      
       public void onDrawFrame(GL10 gl) {
          GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
          GLES20.glUniformMatrix4fv(matrixLocation, 1, false, resultMatrix, 0);
          GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 12);
          GLES20.glDrawArrays(GLES20.GL_LINES,12,2);
          GLES20.glDrawArrays(GLES20.GL_POINTS, 14, 1);
          GLES20.glDrawArrays(GLES20.GL_POINTS, 15, 1);
      
      }
      
      

🔗 前言
🔗 OpenGL ES 篇
🔗 NO.1 OpenGL ES 前言
🔗 NO.2 从OpenGL与GC的矛盾说起
🔗 NO.3 OpenGl 着色器-绘制
🔗 NO.4 OpenGl 色彩过渡&正投影
🔗 NO.6 OpenGl 纹理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

零蚀zero eclipse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值