调整屏幕的宽高比

一.前言

我们将上一篇文章中写的应用程序再次运行起来,然后将屏幕横过来,我们会发现空气曲棍球的桌子被压扁了。这之所以会发生,是因为我们没有考虑屏幕的宽高比,直接将坐标传递给了OpenGL。在这片文章中,我们会弄清楚为什么桌子被压扁了,以及如何使用投影解决这个问题。

二.宽高比的问题

我们现在都知道一个事实:在OpenGL中,我们要渲染的一切物体,都要映射到x,y和z轴的[-1,1]范围内,这个范围内的坐标被称为归一化设备坐标,其独立于屏幕实际的形状和尺寸。不幸的是,由于它独立于实际的屏幕尺寸和形状,我们直接使用就会出现问题,例如横屏模式下桌子被压扁了。

我们现在假设设备的分辨率是1280x720,并且OpenGL占据整个屏幕,那么[-1,1]的范围对应1280像素的高,却只有720像素的宽,图像在x轴上就会显得扁平,同样的问题在y轴上也会发生。想要解决这个问题,我们需要调整坐标空间,以使它把屏幕形状考虑在内。我们可以把较小的范围固定在[-1,1]内,而按屏幕尺寸的比例调整较大的范围。举例来说,在竖屏模式下,可以把宽度限制在[-1,1]内,把高度限制在[-1280/720,1280/720]内。同理,在横屏模式下,可以将高度限制在[-1,1]中,而把高度限制在[-1280/720,1280/720]中。通过这个方法,无论是在竖屏还是横屏下,物体的形状都是一样的,我们所进行的操作就是正交投影。

三.定义正交投影

要定义正交投影,我们要借助Android的Matrix类,这个类有一个称为orthoM()的方法,它可以为我们生成一个正交投影,这个函数的定义如下:

复制代码
public static void orthoM(
  float[] m, //目标数组,这个目标数组的长度至少16个元素,这样才能存储正交投影矩阵
  int mOffset,//结果矩阵起始的偏移值
  float left, //x轴的最小范围
  float right, //x轴的最大范围
  float bottom, //y轴的最小范围
  float top,//y轴的最大范围
  float near, //z轴的最小范围
  float far//z轴的最大范围
)

当我们调用这个函数的时候,它会给我们生成一个4x4的矩阵,这个正交投影矩阵会把所有在左右之间,上下之间和远近之间的事物映射到归一化设备坐标中[-1,1]的范围中,在这个范围内的东西在屏幕上都是可见的。

四.加入正交投影

让我们加入正交投影,并修复那个被压扁的桌子。首先我们需要修改顶点着色器,在里面接收这个投影矩阵,代码修改如下:

复制代码
#version 300 es
layout(location=0) in vec4 a_Position;
layout(location=0) uniform mat4 u_Matrix;
layout(location=1) in vec4 a_Color;
out vec4 v_Color;
void main() {
    gl_Position=u_Matrix*a_Position;
    v_Color=a_Color;
    gl_PointSize=10.0;
}

然后在MyRenderer类的顶部加入如下代码:private val projectionMatrix:FloatArray=FloatArray(16)//存储投影矩阵

接着再更新onSurfaceChanged()函数,在末尾加入如下代码:

复制代码
     //根据屏幕方向生成投影矩阵
        val aspectRatio=if(width>height) width.toFloat()/height.toFloat() else height.toFloat()/width.toFloat()
        if(width>height){
            Matrix.orthoM(projectionMatrix,0,-aspectRatio,aspectRatio,-1f,1f,-1f,1f)
        }
        else{
            Matrix.orthoM(projectionMatrix,0,-1f,1f,-aspectRatio,aspectRatio,-1f,1f)
        }

最后,将生成的投影矩阵传入顶点着色器,在onDrawFrame()函数中的glClear()函数后加入如下代码即可:

复制代码
//传入投影矩阵
glUniformMatrix4fv(0,1,false,projectionMatrix,0)

第一个参数指uniform变量的位置,第二个参数指矩阵的个数。

最后,我们可以修改下桌子的结构,让桌子更高点,这样看起来效果更好,只需要修改y值即可。更新后的结构如下:

复制代码
val tableVertices=floatArrayOf(
            //Triangle fan
            0f,0f,1f,1f,1f,
            -0.5f,-0.8f,0.7f,0.7f,0.7f,
            0.5f,-0.8f,0.7f,0.7f,0.7f,
            0.5f,0.8f,0.7f,0.7f,0.7f,
            -0.5f,0.8f,0.7f,0.7f,0.7f,
            -0.5f,-0.8f,0.7f,0.7f,0.7f,
            //Mid Line
            -0.5f,0f,1f,0f,0f,
            0.5f,0f,1f,0f,0f,
            //Mallets
            0f,-0.4f,0f,0f,1f,
            0f,0.4f,1f,0f,0f
        )

最后,可以运行程序了,看看效果是不是和我们期待的那样。