OpenGL 学习系列---投影矩阵

OpenGL 学习系列文章

OpenGL 坐标系统 文章中,根据点的坐标变换得出了如下的公式:

$$ V_{clip}=M_{projection} \cdot M_{view} \cdot M_{model} \cdot V_{local} $$

这个公式每左乘一个矩阵,都代表了一种坐标系的变换。

转化为着色器脚本语言如下:

attribute vec4 a_Position;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ProjectionMatrix;
uniform mat4 u_ViewMatrix;
void main()
{
    gl_Position  = u_ProjectionMatrix * u_ViewMatrix * u_ModelMatrix * a_Position;
}

本篇文章就主要是对投影矩阵来分析的。

OpenGL 在观察空间转换到裁剪空间时,需要用到投影矩阵。而在着色器脚本中,也需要提供一个投影矩阵给对应的 u_ProjectionMatrix变量。

首先要在程序里绑定到对应的变量,然后再给变量赋值。

// 绑定到着色器脚本中的对应变量
private static final String U_ProMatrix = "u_ProjectionMatrix";
private int uProMatrixLocation;
uProMatrixLocation = glGetUniformLocation(mProgram,U_ProMatrix);
// 给变量赋值,projectionMatrix 为投影矩阵
glUniformMatrix4fv(uProMatrixLocation,1,false,projectionMatrix,0)

正如前文讲到的,投影矩阵会创建一个视景体对物体坐标进行裁剪,得到的裁剪坐标再经过透视除法之后,就会得到归一化设备坐标。归一化设备坐标再经过视口转换,最终将坐标映射到了屏幕上。

OpenGL 提供了两种投影方式:正交投影和透视投影。

正交投影矩阵

不管是正交投影还是透视投影,最终都是将视景体内的物体投影在近平面上,这也是 3D 坐标转换到 2D 坐标的关键一步。

而近平面上的坐标接着也会转换成归一化设备坐标,再映射到屏幕视口上。

为了解决之前的图像拉伸问题,就是要保证近平面的宽高比和视口的宽高比一致,而且是以较短的那一边作为 1 的标准,让图像保持居中。

OpenGL 提供了 Matrix.orthoM 函数来生成正交投影矩阵。

    /**
     * Computes an orthographic projection matrix.
     *
     * @param m returns the result 正交投影矩阵
     * @param mOffset 偏移量,默认为 0 ,不偏移
     * @param left 左平面距离
     * @param right 右平面距离
     * @param bottom 下平面距离
     * @param top 上平面距离
     * @param near 近平面距离
     * @param far 远平面距离
     */
    public static void orthoM(float[] m, int mOffset,
        float left, float right, float bottom, float top,
        float near, float far)

需要注意的是,我们的左、上、右、下距离都是相对于近平面中心的。

近平面的坐标原点位于中心,向右为 $X$ 轴正方向,向上为 $Y$ 轴正方向,所以我们的 left、bottom 要为负数,而 right、top 要为正数。同时,近平面和远平面的距离都是指相对于视点的距离,所以 near、far 要为正数,而且 $far > near$。

可以在 GLSurfaceView 的 surfaceChanged 里面来设定正交投影矩阵。

  @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        float aspectRatio = width > height ? (float) width / (float) height : (float) height / (float) width;
        if (width > height){
            Matrix.orthoM(projectionMatrix,0,-aspectRatio,aspectRatio,-1f,1f,0f,10f);
        }else {
            Matrix.orthoM(projectionMatrix,0,-1f,1f,-aspectRatio,aspectRatio,0f,10f);
        }
    }

这样的话,就把近平面的宽高比设定与视口的宽高比一致了。解决了之前绘制的图像被拉伸的问题。

top Created with Sketch.