当前位置: 首页 > 编程笔记 >

OpenGL ES正交投影实现方法(三)

湛光明
2023-03-14
本文向大家介绍OpenGL ES正交投影实现方法(三),包括了OpenGL ES正交投影实现方法(三)的使用技巧和注意事项,需要的朋友参考一下

本文实例为大家分享了OpenGL ES正交投影展示的具体代码,供大家参考,具体内容如下

绘制正方形

在最开始绘制的六边形里面好像看起来挺容易的,也没有出现什么问题,接下来不妨忘记前面绘制六边形的代码,让我们按照自己的理解来绘制一个简单的正方形。

按照我的理解,要想在屏幕中间显示一个正方形,效果如下图所示

应该创建的数据如下图所示

即传给渲染管线的顶点数据如下图:

float[] vertexArray = new float[] {
   (float) -0.5, (float) -0.5, 0,
   (float) 0.5, (float) -0.5, 0,
   (float) -0.5, (float) 0.5, 0,
   (float) 0.5, (float) 0.5, 0
  };

于是代码大概是这样子的,这里省略掉与主题无关的html" target="_blank">代码,颜色用纯色填充,因此在片元着色器中指定颜色,也省略掉一系列矩阵变换。顶点着色器中直接将顶点传给渲染管线,片元着色器中给片元设置固定颜色红色。

Rectangle.java

public class Rectangle {
 private FloatBuffer mVertexBuffer;
 private int mProgram;
 private int mPositionHandle;

 public Rectangle(float r) {
  initVetexData(r);
 }

 public void initVetexData(float r) {
  // 初始化顶点坐标
  float[] vertexArray = new float[] {
   (float) -0.5, (float) -0.5, 0,
   (float) 0.5, (float) -0.5, 0,
   (float) -0.5, (float) 0.5, 0,
   (float) 0.5, (float) 0.5, 0
  };

  ByteBuffer buffer = ByteBuffer.allocateDirect(vertexArray.length * 4);
  buffer.order(ByteOrder.nativeOrder());
  mVertexBuffer = buffer.asFloatBuffer();
  mVertexBuffer.put(vertexArray);
  mVertexBuffer.position(0);

  int vertexShader = loaderShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
  int fragmentShader = loaderShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);

  mProgram = GLES20.glCreateProgram();
  GLES20.glAttachShader(mProgram, vertexShader);
  GLES20.glAttachShader(mProgram, fragmentShader);
  GLES20.glLinkProgram(mProgram);

  mPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
 }

 public void draw() {
  GLES20.glUseProgram(mProgram);
  // 将顶点数据传递到管线,顶点着色器
  GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false, 0, mVertexBuffer);
  GLES20.glEnableVertexAttribArray(mPositionHandle);
  // 绘制图元
  GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
 }

 private int loaderShader(int type, String shaderCode) {
  int shader = GLES20.glCreateShader(type);
  GLES20.glShaderSource(shader, shaderCode);
  GLES20.glCompileShader(shader);
  return shader;
 }

 private String vertexShaderCode = "attribute vec3 aPosition;"
   + "void main(){"
   + "gl_Position = vec4(aPosition,1);"
   + "}";

 private String fragmentShaderCode = "precision mediump float;"
   + "void main(){"
   + "gl_FragColor = vec4(1,0,0,0);"
   + "}";

}

RectangleView.java

public class RectangleView extends GLSurfaceView{

 public RectangleView(Context context) {
  super(context);
  setEGLContextClientVersion(2);
  setRenderer(new MyRender());
 }

 class MyRender implements GLSurfaceView.Renderer {
  private Rectangle rectangle;

  @Override
  public void onSurfaceCreated(GL10 gl, EGLConfig config) {
   GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1);
   rectangle = new Rectangle(0.5f);
   GLES20.glEnable(GLES20.GL_DEPTH_TEST);
  }

  @Override
  public void onSurfaceChanged(GL10 gl, int width, int height) {
   GLES20.glViewport(0, 0, width, height);
  }

  @Override
  public void onDrawFrame(GL10 gl) {
   GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
   rectangle.draw();
  }
 }

}

然后出来的效果是这样子的,实际上屏幕上的坐标并不是这样子的,后面可以知道上面画的这个样子其实只是一个归一化的设备坐标。归一化设备坐标可以通过公式映射到实际的手机屏幕,后面会学到。

咦,实际效果好像和想象中的不太一样呀。我的本意是显示一个正方形,但实际上现实的却是一个矩形了,y轴上被拉伸了,并且横屏状态下也是类似的情况。但比较巧的是,如果以屏幕中心做一个坐标轴,就会发现,这个矩形的四个顶点在这个坐标轴x、y范围为[-1,1]的中间。

实际上,要显示的所有物体映射到手机屏幕上,都是要映射到x、y、z轴上的[-1,1]范围内,这个范围内的坐标称为归一化设备坐标,独立于屏幕的实际尺寸和形状。

因此按照这样的规定,我们要创建一个正方形就非常困难了,因为要创建正方形就必须考虑手机的宽高比,传入数据的时候就比较复杂了:不能仅仅站在要绘制物体的自身角度来看了。也就是说,上面的例子中要绘制一个正方形,传入的顶点数据的y坐标要按照比例进行一点转换,比如对16:9的屏幕,将上面传入的顶点数据的y坐标都乘以9/16即可。但同时会发现当处于横屏时,又要处理传入的x坐标的值,显然这不是一个好的方案。

引入投影

实际上,对于一个物体来说它有它自身的坐标,这个空间称为物体空间,也就是设计物体的时候采用的一个坐标空间,物体的几何中心在坐标原点上,归一化后坐标范围在[-1,1]之间,x和y轴分度是一致的。

将在这个空间的物体直接往手机屏幕的归一化坐标绘制时,由于屏幕的宽高比的问题,就会出现和预料结果不一样。所以只需要对物体空间的坐标做一个映射即可。

正交投影就是为了解决这个问题的,

public static void orthoM(float[] m, int mOffset,
  float left, float right, float bottom, float top,
  float near, float far)

正交投影背后的数学

orthoM函数产生的矩阵会把所有的左右之间、上下之间,远近之间的点映射到归一化设备坐标中。

各参数的含义如图所示

正交投影是一种平行投影,投影线是平行的,其视景体是一个长方体,坐标位于视景体中的物体才有效,视景体里面的物体投影到近平面上的部分最终会显示到屏幕的视口中,关于视口后面会降到。

会产生下面的矩阵,z轴的负值会反转z坐标,这是因为归一化设备坐标是左手系统,而OpenGL ES中的坐标系统都是右手系统,这里还涉及到顶点坐标的w分量,目前暂时用不到。


利用矩阵的就可以将物体空间[-1,1]之间的坐标映射到屏幕归一化设备坐标的[-1,1]之间。归一化屏幕坐标是右手坐标系统,原点在屏幕正中心,向右为x轴正方向,向上为y轴正方向,z轴垂直屏幕向外。以竖屏为例,比如设置left=-1,right=1,bottom=-hight/width,top=hight/width,比如我的手机分辨率为1920*1080 =1.8 对上面的正方形点(0.5,0.5)坐标而言经过变化就成了(0.5,0.3)

在屏幕的归一化设备坐标中来看就是一个正方形了,因为y轴范围显然比x轴大,0.3对应的实际长度和x轴的0.5长度是一样的。

上面的代码需要做如下修改,在onSurfaceChanged里面增加如下代码

@Override
  public void onSurfaceChanged(GL10 gl, int width, int height) {
   GLES20.glViewport(0, 0, width, height);
   // 根据屏幕方向设置投影矩阵
   float ratio= width > height ? (float)width / height : (float)height / width;
   if (width > height) {
    // 横屏 
    Matrix.orthoM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 0, 5);
   } else {
    Matrix.orthoM(mProjectionMatrix, 0, -1, 1, -ratio, ratio, 0, 5);
   }
  }

接着在顶点着色器中对顶点乘以投影矩阵

private String vertexShaderCode = "uniform mat4 uProjectionMatrix;" // 增加这一行
   + "attribute vec3 aPosition;"
   + "void main(){"
   + "gl_Position = uProjectionMatrix * vec4(aPosition,1);" // 不是直接赋值而是乘以投影矩阵
   + "}";

最后增加获取着色器中uProjectionMatrix以及传入值的代码部分即可。最终的效果不论横屏还是竖屏,显示的都是我们期望的正方形。

摄像机设置

需要补充的是,上面的参数near、far的含义指的是和视点的距离,视点貌似到目前还未接触到,它指的是摄像机的位置,和实际生活中用相机看物体一样,从不同的角度和位置拍摄同一个物体获得的照片肯定是不一样的,摄像机位置用setLookAtM函数指定。

 public static void setLookAtM(float[] rm, // 生成的摄像机矩阵
        int rmOffset,
   float eyeX, float eyeY, float eyeZ, // 摄像机的位置
   float centerX, float centerY, float centerZ, // 观察目标点的位置
               // 摄像机位置和观察目标点的位置确定了观察方向
   float upX, float upY,float upZ // up向量在x、y、z轴上的分量,我觉得一般应该是和观察方向垂直的
        )

前面提到的确定的视景体就和上面函数指定的摄像机位置和观察方向有关。摄像机默认位置在(0,0,0)处,在上面的设置下,如果将改正方形沿z轴正方向平移1个单位,屏幕上就显示不了,因为已经跑到了设置的视景体外面了。

关于摄像机的参数和投影near和far参数的设置需要注意,肯定不是胡乱设置的!摄像机的位置、方向和投影矩阵定义的视景体最终确定了视景体的位置,如果设置不当就会导致物体没有显示在屏幕上,因为物体的坐标可能位于视景体外面。

视口

前面说过在视景体中的物体最终会投影到近平面上,最终显示到视口上,正如前面在onSurfaceChanged设置的那样。

public static native void glViewport(
  int x,
  int y,
  int width,
  int height
 );

视口中各参数的含义

视口用的屏幕坐标系原点并不在屏幕左上角而是在左下角,x轴向右,y轴向上。其实还不是很准确,准确的说,视口的坐标原点位于该View的左下角,因为GLSurfaceView并不总是占据整个屏幕的。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。

 类似资料:
  • 举个简单的例子来说明正交投影与透视投影照相机的区别。使用透视投影照相机获得的结果是类似人眼在真实世界中看到的有“近大远小”的效果(如下图中的(a));而使用正交投影照相机获得的结果就像我们在数学几何学课上老师教我们画的效果,对于在三维空间内平行的线,投影到二维空间中也一定是平行的(如下图中的(b))。 (a)透视投影,(b)正交投影 那么,你的程序需要正交投影还是透视投影的照相机呢? 一般说来,对

  • 我要直截了当地说。我已经做了一些研究,但仍然没有弄清楚这一点。 我有一个程序,用一种颜色画一个立方体的每个面。然后我在Y轴和X轴上旋转这个立方体(-分别为45°和45°)。 问题在于它没有像我预期的那样呈现,立方体在某个时候被剪切。 代码: 我定义的所有顶点都是用±0.25制作的。所以我认为,至少用我的这些zNear和zFar值,它们仍然适合体积,但当我改变线时 为 它工作得很好 但是它不能与 有

  • 正交投影照相机(Orthographic Camera)设置起来较为直观,它的构造函数是: THREE.OrthographicCamera(left, right, top, bottom, near, far) 这六个参数分别代表正交投影照相机拍摄到的空间的六个面的位置,这六个面围成一个长方体,我们称其为视景体(Frustum)。只有在视景体内部(下图中的灰色部分)的物体才可能显示在屏幕上,

  • 本文向大家介绍OpenGL ES透视投影实现方法(四),包括了OpenGL ES透视投影实现方法(四)的使用技巧和注意事项,需要的朋友参考一下 在之前的学习中,我们知道了一个顶点要想显示到屏幕上,它的x、y、z分量都要在[-1,1]之间,我们回顾一下渲染管线的图元装配阶段,它实际上做了以下几件事:剪裁坐标、透视分割、视口变换。图元装配的输入是顶点着色器的输出,抓哟是物体坐标gl_Position,

  • 本文向大家介绍Three.js学习之正交投影照相机,包括了Three.js学习之正交投影照相机的使用技巧和注意事项,需要的朋友参考一下 前言 Three.js是一个3Djs库,webGL开源框架中比较优秀的一个。除了webGL以外,Three.js还提供了基于Canvas、SVG标签的渲染器,调试建议使用Chrome或者Firefox。 1.照相机   图形学中的照相机定义了三维空间到二维屏幕的投

  • 有很多关于3D的opengl投影矩阵的很棒的教程,但我不是在做3D。我真的很难根据自己的喜好进行正交投影设置。 int宽=320;int高=480; 我使用这些设置创建了一个视图投影矩阵。 创建此视图和投影矩阵并将它们传递给gpu之后。 然后我创建一个从的四边形,以便它的原点位于中心。 然后我为四边形制作一个比例矩阵,这样我就可以在屏幕上看到它了。它呈现为一个完美的正方形,但当然glViewpor