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

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);
}

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

这里写图片描述

初步接触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编程指南(第七版)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值