原文鏈接:https://glumes.com/post/opengl/opengl-tutorial-projection-matrix/
在 OpenGL 坐標(biāo)系統(tǒng) 文章中,根據(jù)點(diǎn)的坐標(biāo)變換得出了如下的公式:
這個(gè)公式每左乘一個(gè)矩陣,都代表了一種坐標(biāo)系的變換。
轉(zhuǎn)化為著色器腳本語言如下:
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;
}
本篇文章就主要是對(duì)投影矩陣來分析的。
OpenGL 在觀察空間轉(zhuǎn)換到裁剪空間時(shí),需要用到投影矩陣。而在著色器腳本中,也需要提供一個(gè)投影矩陣給對(duì)應(yīng)的 u_ProjectionMatrix變量。
首先要在程序里綁定到對(duì)應(yīng)的變量,然后再給變量賦值。
// 綁定到著色器腳本中的對(duì)應(yīng)變量
private static final String U_ProMatrix = "u_ProjectionMatrix";
private int uProMatrixLocation;
uProMatrixLocation = glGetUniformLocation(mProgram,U_ProMatrix);
// 給變量賦值,projectionMatrix 為投影矩陣
glUniformMatrix4fv(uProMatrixLocation,1,false,projectionMatrix,0)
正如前文講到的,投影矩陣會(huì)創(chuàng)建一個(gè)視景體對(duì)物體坐標(biāo)進(jìn)行裁剪,得到的裁剪坐標(biāo)再經(jīng)過透視除法之后,就會(huì)得到歸一化設(shè)備坐標(biāo)。歸一化設(shè)備坐標(biāo)再經(jīng)過視口轉(zhuǎn)換,最終將坐標(biāo)映射到了屏幕上。
OpenGL 提供了兩種投影方式:正交投影和透視投影。
正交投影矩陣

不管是正交投影還是透視投影,最終都是將視景體內(nèi)的物體投影在近平面上,這也是 3D 坐標(biāo)轉(zhuǎn)換到 2D 坐標(biāo)的關(guān)鍵一步。
而近平面上的坐標(biāo)接著也會(huì)轉(zhuǎn)換成歸一化設(shè)備坐標(biāo),再映射到屏幕視口上。
為了解決之前的圖像拉伸問題,就是要保證近平面的寬高比和視口的寬高比一致,而且是以較短的那一邊作為 1 的標(biāo)準(zhǔn),讓圖像保持居中。
OpenGL 提供了 Matrix.orthoM 函數(shù)來生成正交投影矩陣。
/**
* Computes an orthographic projection matrix.
*
* @param m returns the result 正交投影矩陣
* @param mOffset 偏移量,默認(rèn)為 0 ,不偏移
* @param left 左平面距離
* @param right 右平面距離
* @param bottom 下平面距離
* @param top 上平面距離
* @param near 近平面距離
* @param far 遠(yuǎn)平面距離
*/
public static void orthoM(float[] m, int mOffset,
float left, float right, float bottom, float top,
float near, float far)

需要注意的是,我們的左、上、右、下距離都是相對(duì)于近平面中心的。
近平面的坐標(biāo)原點(diǎn)位于中心,向右為 軸正方向,向上為
軸正方向,所以我們的 left、bottom 要為負(fù)數(shù),而 right、top 要為正數(shù)。同時(shí),近平面和遠(yuǎn)平面的距離都是指相對(duì)于視點(diǎn)的距離,所以 near、far 要為正數(shù),而且
。
可以在 GLSurfaceView 的 surfaceChanged 里面來設(shè)定正交投影矩陣。
@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);
}
}
這樣的話,就把近平面的寬高比設(shè)定與視口的寬高比一致了。
透視投影矩陣
OpenGL 提供了兩個(gè)函數(shù)來創(chuàng)建透視投影矩陣:frustumM 和 perspectiveM。
frustumM
frustumM 函數(shù)創(chuàng)建的視景體是一個(gè)錐形。

它的視景體有點(diǎn)類似于正交投影,在參數(shù)理解上基本都相同的。
/**
* Defines a projection matrix in terms of six clip planes.
*
* @param m the float array that holds the output perspective matrix
* @param offset the offset into float array m where the perspective
* matrix data is written
* @param left
* @param right
* @param bottom
* @param top
* @param near
* @param far
*/
public static void frustumM(float[] m, int offset,
float left, float right, float bottom, float top,
float near, float far)
需要注意的是 near 和 far 變量的值必須要大于 0 。因?yàn)樗鼈兌际窍鄬?duì)于視點(diǎn)的距離,也就是照相機(jī)的距離。
當(dāng)用視圖矩陣確定了照相機(jī)的位置時(shí),要確保物體距離視點(diǎn)的位置在 near 和 far 的區(qū)間范圍內(nèi),否則就會(huì)看不到物體。
由于透視投影會(huì)產(chǎn)生近大遠(yuǎn)小的效果,當(dāng)照相機(jī)位置不變,改變 near 的值時(shí)也會(huì)改變物體大小,near 越小,則離視點(diǎn)越近,相當(dāng)于物體越遠(yuǎn),那么顯示的物體也就越小了。
當(dāng)然也可以 near 和 far 的距離不動(dòng),改變攝像機(jī)的位置來改變觀察到的物體大小。
perspectiveM

OpenGL 還提供了 perspectiveM 函數(shù)來創(chuàng)建投影矩陣,它的視景體和 frustumM 函數(shù)相同,但是構(gòu)造的參數(shù)有所不同。
/**
* Defines a projection matrix in terms of a field of view angle, an
* aspect ratio, and z clip planes.
*
* @param m the float array that holds the perspective matrix
* @param offset the offset into float array m where the perspective
* matrix data is written
* @param fovy field of view in y direction, in degrees
* @param aspect width to height aspect ratio of the viewport
* @param zNear
* @param zFar
*/
public static void perspectiveM(float[] m, int offset,
float fovy, float aspect, float zNear, float zFar)
視景體不再需要確定近平面左、上、右、下距離了。
通過視角來決定我們能看到的視野大小。視角就是圖中所示的那個(gè)夾角。另外的參數(shù)是視口的寬高比,還有近平面和遠(yuǎn)平面的距離,參數(shù)個(gè)數(shù)減少了。


上述圖片左邊是 90 視角,右邊是 45 度視角。顯然,視野角度越大,則看到的內(nèi)容更多,但是物體顯得更小,而視野角度越小,則看的內(nèi)容更少,但物體顯得更大。
和 frustumM不同的是,一旦確定了視角和寬高比,那么整個(gè)攝像機(jī)視野也就確定了,此時(shí)完整的錐形視野已經(jīng)形成了,也就是說物體的近大遠(yuǎn)小效果已經(jīng)完成了。這時(shí),近平面距離和遠(yuǎn)平面距離只是想要截取錐形視野中的那一部分了。不像在frustumM函數(shù)中,近、遠(yuǎn)平面的距離還能夠調(diào)整近大遠(yuǎn)小的效果。
一起交流學(xué)習(xí),答疑解惑,有問題,我們星球見~~~

參考
- 《OpenGL ES 應(yīng)用開發(fā)實(shí)踐指南》
- 《OpenGL ES 3.x 游戲開發(fā)》
具體代碼詳情,可以參考我的 Github 項(xiàng)目:
https://github.com/glumes/AndroidOpenGLTutorial
OpenGL 系列文章: