欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android OpenGL二 —— 使用投影和相机变换

程序员文章站 2023-12-31 15:14:34
...

OpenGL 的投影和相机视图

书接上文,在Android OpenGL ES一 ——入门中,利用OpenGL绘制了一个三角形。但最终呈现出来的图形和我们预想的正三角形不一致,这是因为你定义的坐标点,并没有被正确的绘制到屏幕上,本文将解决这个问题。

投影视图?相机视图?跟投影仪和相机有关系?这都什么鬼!!!!!

为了不被看官大人喷,先引入一些OpenGL中的一些概念。

OpenGL中的视图

计算机图形的要点是创建三维图像的二维投影,在各种屏幕上的图像必定是二维的。区别在于,OpenGL在决定如何在屏幕上绘制时,采用的是三维坐标的方式考虑。为了将所绘制物体的三维坐标转换为屏幕上的二维坐标。通常需要做如下几步:

  • 相机视图和投影变换(此外还有模型):这些变换通过矩阵乘法的方式,实现移动、旋转、缩放等操作。
  • 裁剪:三维物体投影到特定的屏幕,肯定有不需要看见的边缘部分,需要把他们裁减掉。
  • 视口变换:该操作实现从三维坐标到二维坐标的映射。

这里主要解释相机视图和投影视图。

相机视图和投影视图

看到相机视图,你是不是觉得疑惑。绘图和相机有什么关系。没错,确实没有什么关系。相机只是OpenGL中对于更好解释视图概念的一种形象比喻。

我们使用相机拍照时,通常存在以下操作。

  1. 在固定的位置摆放相机,这就是相机视图的操作,也称作视图变换
  2. 在场景中摆放物体的位置,这是模型变换(仅作了解,本文不会涉及)。
  3. 调整相机焦距,这是投影变换
  4. 其他

上述操作一,相当于设置相机的位置,也可以理解为设置人眼的位置坐标,这决定你站在场景空间中的什么位置观察物体。术语中的解释是,设置视景体的坐标(设置人眼的观察坐标后,相对的,视景体的坐标也被确定)。

操作三,投影变换最终决定三维物体最终投影在屏幕上的形状,当然,这将涉及到立体到平面的坐标变换。投影变换分为透视投影好平面投影。这里这简单介绍透视投影透视投影的特点是,物体越远,在图像中看上去越小(是不是和真是情况一致?)。

看到这里,你应该对相机视图投影视图有了一定了解。接下来进入正文。

两个视图概念在Android中的体现

  • Projection(投影视图变换) - 该变化根据显示他们的GLSurfaceView的高和宽来调整绘制对象的坐标。没有这些坐标的调整计算,OpenGL ES绘制的对象将被不对等的视图窗口所扭曲。典型的投影变换只需要在OpenGL视图比例创建或改变的渲染器的onSurfaceChanged()方法计算即可。
  • Camera View (相机视图变换)- 这个变换根据虚拟相机的位置调整绘制对象的坐标。需要注意的是,OpenGL ES并不是定义一个真是的相机对象,而是通过转换绘制对象的显示来提供虚拟相机的公共方法。当你构建你GLSurfaceView时,相机视图的变化也许只会被计算一次,或者更具用户的活动动态变化。

定义投影

投影坐标变换数据是在GLSurfaceView.RendereronSurfaceChanged()方法中进行的。示例代码中获取GLSurfaceView的宽高,并且通过Matrix.frustumM()填充投影的变换矩阵。

// mMVPMatrix 的全称是 "Model View Projection Matrix"
private final float[] mMVPMatrix = new float[16];
private final float[] mProjectionMatrix = new float[16];
private final float[] mViewMatrix = new float[16];

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

    float ratio = (float) width / height;

    // 定义了一个用于计算透视投影矩阵的**平截头体**,和投影矩阵相乘得到一个矩阵
    // 这个投影矩阵被用于onDrawFrame()中绘制对象的坐标系
    Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}

这段代码填充一个投影矩阵,你可以在onDrawFrame()中mProjectionMatrix 和相机视图变换联合到一起,这将在下面的内容中提及。

在绘制对象时只应用投影变换非常空洞,通常,为了在屏幕上展示任何对象,你也需要同时使用相机视图变换。

定义相机视图

将相机视图变换,作为绘制进程的一部分,加入你的渲染器(renderer)中,即可完成绘制对象的坐标转换操作。在如下代码中,使用Matrix.setLookAtM()计算相机视图变换,然后将之前计算好的投影矩阵与之关联。最后将关联起来的变换举证传递给图形绘制。

@Override
public void onDrawFrame(GL10 unused) {
    ...
         /**
         * 设置相机的位置(相当于观察点的坐标)
         *void setLookAtM(matrix, offset,//视图矩阵和偏移量
         *              eysx, eyey, eyez,//定义目标观察点的位置
         *             centerx, centery, centerz,//指定视线上任意一点
         *              upx, upy, upz)//表示那个方向朝上
         **/
    Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

    // 计算绘制物体最终在屏幕上的投影和视图变换
    Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);

    // 绘制图形
    mTriangle.draw(mMVPMatrix);
}

应用相机和投影变换

public class Triangle {

    private final String vertexShaderCode =
        // 这个矩阵的成员变量提供了一个Hook来操作使用顶点渲染器渲染的对象的坐标
        "uniform mat4 uMVPMatrix;" +
        "attribute vec4 vPosition;" +
        "void main() {" +
        // 矩阵必须包含gl_Position修饰符
        // 注意,为了保证乘积的可靠性,uMVPMatrix元素必须是第一个
        "  gl_Position = uMVPMatrix * vPosition;" +
        "}";

    // 用于设置视图变换
    private int mMVPMatrixHandle;

    ...
}

接下来,修改图形对象的draw()方法,将变换矩阵应用于图形。

public void draw(float[] mvpMatrix) { // 传递已经计算好的变换矩阵
    ...

    // 获取图形变换矩阵的句柄
    mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

    // 将投影和视图变换传递给渲染器
    GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

    // 绘制三角形
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

    // 弃用顶点数组
    GLES20.glDisableVertexAttribArray(mPositionHandle);
}

只要你正确计算并应用了投影和相机视图变换,你的图形对象就能够呈现出正常的比例,比如这样。

Android OpenGL二 —— 使用投影和相机变换

初步接触OpenGL,欢迎评论交流。源码地址:https://github.com/MrHeLi/OpenGLDemo

参看资料:
OpenGL练习主页:https://learnopengl-cn.github.io/01%20Getting%20started/01%20OpenGL/
Android官网:https://developer.android.com/training/graphics/opengl/projection.html
OpenGL编程指南(第七版)。

上一篇:

下一篇: