OpenGL ES投影和视图变换

上篇文章介绍了 Android 中的 OpenGL 以及坐标映射等，在 OpenGL ES 环境中，通过投影和相机视图，显示的绘制对象更接近于眼睛看到的实物，这种呈现方式是通过对绘制对象坐标进行数学转换来完成，这里介绍一下投影和相机视图相关知识，文中代码案例变更可以参考前一篇文章：

Android 中的 OpenGL

主要内容如下：

投影类型

定义投影

定义相机视图

应用投影和相机视图

运行效果

投影类型

OpenGL 中主要有两种投影模式，分别是正交投影和透视投影，其特点分别如下：

透视投影：符合人眼习惯，呈现近大远小的效果。

正交投影：所有物体在投影平面上保持原来的大小。

透视投影的视景体是截锥体，正交投影的视景体是长方体，透视投影和正交投影示意图如下：

两者对应的矩阵计算函数如下：

Copy
// 透视投影矩阵
Matrix.frustumM(float[] m, int offset, float left, float right, float bottom, float top, float near, float far);
// 正交投影矩阵
Matrix.orthoM(float[] m, int offset, float left, float right, float bottom, float top, float near, float far);

上述函数参数中 m 用来存储对应的投影矩阵数据，near 和 far 分别表示视景体的近平面、远屏幕距离，left、right、top、bottom 对应的是远平面的参数。

定义投影

根据上一小节内容，这里使用透视投影，填充投影矩阵使用 Matrix.frustumM() ，如下

Copy
private val projectionMatrix = FloatArray(16)
override fun onSurfaceChanged(unused: GL10, width: Int, height: Int) {
GLES20.glViewport(0, 0, width, height)
val ratio: Float = width.toFloat() / height.toFloat()
Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1f, 1f, 3f, 7f)
}

上述代码填充了一个投影矩阵projectionMatrix，其变化参考如下动图：

定义相机视图

相机视图顾名思义就相当于站在相机的角度观察某个物体，使用Matrix.setLookAtM方法来填充视图矩阵，关键参数主要是相机位置、目标位置和相机的正上方向量，然后将投影矩阵和视图矩阵合并为vPMatrix，如下：

Copy
override fun onDrawFrame(gl: GL10?) {
// 绘制当前frame，用于渲染处理具体的内容
Log.d(tag, "onDrawFrame")

// 设置相机位置（视图矩阵）
Matrix.setLookAtM(viewMatrix,0,
0.0f,0.0f,5.0f, // 相机位置
0.0f,0.0f,0.0f, // 目标位置
0.0f,1.0f,0.0f) // 相机正上方向量
// 计算投影和视图变换
Matrix.multiplyMM(vPMatrix,0,projectionMatrix,0,viewMatrix,0)

// 具体绘制
triangle.draw(vPMatrix)
}

如上案例中相机位置 z 坐标为只能在 near 和 far 之间，也就是必须在 3 和 7 之间，不能在其范围外观察，参考如下动图：

应用投影和相机视图

为了适应投影和视图变换，修改上一篇中的着色器代码如下：

Copy
// default
attribute vec4 vPosition;
void main() {
gl_Position = vPosition;
}
// 使用投影和视图变换
uniform mat4 uMVPMatrix;
attribute vec4 vPosition;
void main() {
gl_Position = uMVPMatrix * vPosition;
}

将上一小节中计算得到的的vPMatrix矩阵传入着色器即可：

Copy
fun draw(mvpMatrix: FloatArray) {
// 获取attribute变量的地址索引
// get handle to vertex shader's vPosition member
positionHandle = GLES20.glGetAttribLocation(programHandle, "vPosition").also {
// enable vertex attribute，默认是disable
GLES20.glEnableVertexAttribArray(it)
GLES20.glVertexAttribPointer(
it,
COORDINATE_PER_VERTEX,
GLES20.GL_FLOAT,
false,
vertexStride,
vertexBuffer
)
}
// get handle to fragment shader's vColor member
colorHandler = GLES20.glGetUniformLocation(programHandle, "vColor").also {
GLES20.glUniform4fv(it, 1, color, 0)
}

// get handle to shape's transformation matrix
vPMatrixHandle = GLES20.glGetUniformLocation(programHandle, "uMVPMatrix")
// Pass the projection and view transformation to the shader
GLES20.glUniformMatrix4fv(vPMatrixHandle, 1, false, mvpMatrix, 0)

// draw triangle
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount)
GLES20.glDisableVertexAttribArray(positionHandle)
}

到此通过代码改造应用投影和相机视图，解决了横竖屏切换的变形问题，这种变形自然可以延伸到其他领域，比如 OpenGL 渲染视频时的视频比例等。

运行效果

可以与上一篇文章中的运行效果进行对比，运行效果如下：

可以回复关键字【OpenGL】获取源代码，上文中出现的动图程序回复关键字【OTUTORS】获取。