1.寬高比問題
http://blog.csdn.net/liyuanjinglyj/article/details/46624901
我們現在相當熟悉這樣一個事實,在OpenGL里,我們要渲染的一切物體都要映射到X軸和Y軸上[-1,1]的范圍內,對于Z軸也一樣。這個范圍內的坐標被稱為歸一化設備坐標,其獨立于屏幕實際尺寸或形狀。
不幸的是,因為它們獨立于實際的屏幕尺寸,如果直接使用它們,我們就會遇到問題,例如在橫屏模式下被壓扁的桌子。
假設實際的設備分辨率以像素為單位是1280*720,這在新的Android設備上是一個常用的分辨率。為了使討論更加容易,讓我們也暫時假定OpenGL占用整個顯示屏。
如果設備是在豎屏模式下,那么[-1,1]的范圍對應1280像素高,卻只有720像素寬。圖像會在X軸顯得扁平,如果在橫屏模式,同樣的問題也會發生在Y軸上。
歸一化設備坐標假定坐標空間是一個正方形,如下圖所示:
然而,因為實際的視口可能不是一個正方形,圖像就會在一個方向上被拉伸,在另一個方向上被壓扁。在一個豎屏設備上,歸一化設備坐標上定義的圖像看上去就是在水平方向上被壓扁了:
在橫屏模式下,同樣的圖像就在另一個方向上看起來被壓扁的。
2.適應寬高比
我們需要調整坐標空間,以使它把屏幕的形狀考慮在內,可行的一個方法是把較小的范圍固定在[-1,1]內,而按屏幕尺寸的比例調整較大的范圍。
舉例來說,在豎屏情況下,其寬度是720,而高度是1280,因此我們可以把寬度范圍限定在[-1,1],并把高度范圍調整為[-1280/720,1280/720]或[-1.78,1.78]。同理在橫屏模式情況下,把高度范圍設為[-1.78,1.78],而把高度范圍設為[-1,1]。
通過調整已有的坐標空間,最終會改變我們可用的空間。
通過這個方法,不論是豎屏模式還是橫屏模式,物體看起來就都一樣了。
3.使用虛擬坐標空間
調整坐標空間,以便我們把屏幕方向考慮進來,我們需要停止直接在歸一化設備坐標上工作,遙開始在虛擬坐標空間里工作。接下來,我們需要找到某種可以把虛擬空間坐標轉化回歸依化設備坐標的方法,讓OpenGL可以正確的渲染它們。這種轉換應該把屏幕方向計算在內,以使圖像在豎屏模式和橫屏模式看上去都一樣。
我們想要進行的操作叫作正交投影。使用正交投影,不管多遠或者多近,所有物體看上去大小總是相同的。為了更高的理解這種投影是做什么的,想象一下,在我們的場景中有一個火車軌道,直接從空中俯瞰,這些軌道看起來是這樣的:
還有一種特殊類型的正交投影,被稱為等軸測投影,它是從側角觀察一種正交投影。正如在一些城市模擬和策略游戲中看到的,這種類型的投影能用來重新創建一個經典的三維角。
當我們使用正交投影把虛擬坐標變換回歸化設備坐標時,實際上定義了三維世界內部的一個區域。在這個區域內的所有東西都會顯示在屏幕上,而區域外的所有東西都會被剪裁掉。
利用正交投影矩陣改變立方體的大小,以使我們可以在屏幕上看到或多或少的場景。我們也能改變立方體的形狀彌補屏幕的寬高比的影響。
4.線性代數基礎
OpenGL大量使用了向量和矩陣,矩陣的最重要的用途之一就是建立正交和透視投影。其原因之一是,從本質上來說,使用矩陣做投影只涉及對一組數據按順序執行大量的加法和乘法,這些運算在現代GPU上執行的非??臁?/p>
4.1向量
一個向量是一個有多個元素的一維數組。在OpenGL里,一個位置通常是一個四元素向量,顏色也一樣。我們使用的大多數向量一般都有四個元素。在下面的例子中, 我們可看到一個位置向量,它有一個X,一個Y,一個Z,一個W分量。
4.2矩陣
一個矩陣是一個有多個元素的二維數組。在OpenGL里,我們一般使用矩陣作向量投影,如正交或者透視投影,并且也用它們旋轉物體,平移物體以及縮放物體。我們把矩陣與每個要變換的向量相乘可實現這些變換。下面就是一個矩陣:
4.3矩陣與向量的乘法
要讓矩陣乘以一個向量,我們把矩陣放在左邊,向量放在右邊。如下:
規則就是矩陣第一行乘以向量第一列,以第一行為例:矩陣第一行第一個元素乘以向量第一列第一個元素,加上矩陣第一行第二個元素乘以向量第一列第二個元素,加上矩陣第一行第三個元素乘以向量第一列第三個元素,加上矩陣第一行第四個元素乘以向量第一列第四個元素。矩陣二,三,四行同理。
4.4單位矩陣
單位矩陣如下:
之所以被稱為單位矩陣,是因為這個矩陣乘以任何向量總是得到與原來相同的向量。就像把任何數字乘以1會得到原來的數字一樣。
4.5平移矩陣
既然理解了單位矩陣,讓我們看一個非常簡單的矩陣類型---平移矩陣。它在OpenGL里十分常用。使用這種類型的矩陣,我們可以把一個物體沿著指定的距離移動。這個矩陣和單位矩陣差不多,但在右側指定了三個額外的元素:
讓我們盾一個位置(2,2)的例子,這個位置Z默認是0,W默認是1.我們把這個向量沿X軸平移3,沿Y軸也平移3,因此,把Xtranslation賦值為3,Ytranslation賦值為3。結果如下:
這個位置正是我們所期望和(5,5)。
5.正交投影
要定義正交投影,我們將使用Android的Matrix類,它在android.opengl包中。這個類有一個稱為orthoM()的方法,它可以為我們生成一個正交投影。
我們來看一下orthoM()參數:
orthom(float[] m,int mOffset,float left,float rigth,float bottom,float top,float near,float far)
float[] m:目標數組,這個數組長度至少有16個元素,這樣它才能存儲正交投影矩陣。
int mOffset:結果矩陣起始的偏移值。
float left:X軸的最小范圍。
float right:X軸的最大范圍。
float bottom:Y軸的最小范圍。
float top:Y軸的最大范圍。
float near:Z軸的最小范圍。
float far:Z軸的最大范圍。
當我們調用這個方法的時候,它應該產生下面的正交投影矩陣:
這個正交投影矩陣會把所有在左右之間,上下之間和遠近之間的事物映射到歸一化設備坐標中從-1到1的范圍,在這個范圍內所有事物在屏幕上都是可見的。
主要的區別就是Z軸有一個負值符號,它的效果是反轉Z坐標。這就意味著,物體離得越遠,Z坐標的負值會越來越小。之所以這樣完全是歷史和傳統的原因。
6.左手與右手坐標系統
為了更好的理解Z軸問題,我們需要理解左手坐標系統與右手坐標系統之間的區別。想知道一個坐標系統是左手的還是右手的,你拿出一只手,把大拇指指向X軸正值方向,然后把食指指向Y軸正值方向。
現在,把你的中指指向Z軸。如果你需要用左手做這些,那你看到的就是一個左手坐標系統;如果你需要用右手,那你看到的就是一個右手坐標系統。把你的中指指向Z軸,記住要把大拇指指向X軸方向,食指指向Y軸正值方向。如下圖:
其實左手還是右手選擇都沒關系,只是一個簡單的轉換。歸一化設備坐標使用的是左手坐標系統,而在OpenGL的早期版本,默認使用的確實右手坐標系統,其使用Z的負值增加表示距離增加。這就是為什么Android的Matrix會默認生成反轉Z的矩陣。
7.更新程序
7.1更新著色器
修改前一章的頂點著色器中的代碼如下:
uniform mat4 u_Matrix;
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main()
{
v_Color=a_Color;
gl_Position =u_Matrix*a_Position;
gl_PointSize = 10.0;
}
我們這里添加了一個新的uniform定義的“u_Matrix”,并把它定義為一個mat4類型,意思是這個uniform代表一個4*4的矩陣。更新位置負值那一行:
gl_Position =u_Matrix*a_Position;
我們沒有傳遞數組中定義的位置,而是傳遞那個位置與一個矩陣的乘積。它意味著頂點數組不用再被翻譯為歸一化設備的坐標了,其將被理解為存在于這個矩陣所定義的虛擬坐標空間中。這個矩陣會把坐標從虛擬坐標空間變化回歸一化設備坐標。
7.2添加矩陣數組和一個新的uniform
打開上一節的LYJRenderer添加成員變量:
private static final String U_MATRIX="u_Matrix";
我們還需要一個頂點數組存儲矩陣:
private final float[] projectionMatrix=new float[16];
我們也需要一個整型值用于保存那個矩陣uniform的位置:
private int uMatrixLocation;
然后我們只需要在onSurfaceCreated()中加入如下代碼:
uMatrixLocation=GLES20.glGetUniformLocation(program,U_MATRIX);
7.3創建正交投影矩陣
更新onSurfaceChanged(),在GLES20.glViewport()調用后面加入如下代碼:
final float aspectRatio=width>height?(float)width/(float)heigth:(float)height/(float)width;
if(width>height){
orthoM(projectionMatrix,0,-aspectRatio,aspectRatio,-1f,1f,-1f,1f);
}else{
orthoM(projectionMatrix,0,-1f,1f,-aspectRatio,aspectRatio,-1f,1f);
}
這段代碼會創建一個正交投影矩陣,這個矩陣會把屏幕的當前方向計算在內。注意在Android中不只有一個Matrix類,因此你要確保導入了android.opengl.Matrix。
我們首先計算了寬高比,它使用寬和高中的較大值除以寬和高的較小值。不管是豎屏還是橫屏,這個值都一樣。
7.4傳遞矩陣給著色器
在LYJRenderer中的onDrawFrame()中,我們在glClear()調用之后加入如下代碼:
GLES20.glUniformMatrix4fv(uMatrixLocation,1,false,projectionMatrix,0);