1.寬高比問題
http://blog.csdn.net/liyuanjinglyj/article/details/46624901
我們現(xiàn)在相當(dāng)熟悉這樣一個事實(shí)燥透,在OpenGL里,我們要渲染的一切物體都要映射到X軸和Y軸上[-1融涣,1]的范圍內(nèi)漱抓,對于Z軸也一樣表锻。這個范圍內(nèi)的坐標(biāo)被稱為歸一化設(shè)備坐標(biāo),其獨(dú)立于屏幕實(shí)際尺寸或形狀乞娄。
不幸的是瞬逊,因?yàn)樗鼈儶?dú)立于實(shí)際的屏幕尺寸显歧,如果直接使用它們,我們就會遇到問題确镊,例如在橫屏模式下被壓扁的桌子士骤。
假設(shè)實(shí)際的設(shè)備分辨率以像素為單位是1280*720,這在新的Android設(shè)備上是一個常用的分辨率骚腥。為了使討論更加容易敦间,讓我們也暫時假定OpenGL占用整個顯示屏瓶逃。
如果設(shè)備是在豎屏模式下束铭,那么[-1,1]的范圍對應(yīng)1280像素高,卻只有720像素寬厢绝。圖像會在X軸顯得扁平契沫,如果在橫屏模式,同樣的問題也會發(fā)生在Y軸上昔汉。
歸一化設(shè)備坐標(biāo)假定坐標(biāo)空間是一個正方形懈万,如下圖所示:
然而,因?yàn)閷?shí)際的視口可能不是一個正方形靶病,圖像就會在一個方向上被拉伸会通,在另一個方向上被壓扁。在一個豎屏設(shè)備上娄周,歸一化設(shè)備坐標(biāo)上定義的圖像看上去就是在水平方向上被壓扁了:
在橫屏模式下涕侈,同樣的圖像就在另一個方向上看起來被壓扁的。
2.適應(yīng)寬高比
我們需要調(diào)整坐標(biāo)空間煤辨,以使它把屏幕的形狀考慮在內(nèi)裳涛,可行的一個方法是把較小的范圍固定在[-1,1]內(nèi),而按屏幕尺寸的比例調(diào)整較大的范圍众辨。
舉例來說端三,在豎屏情況下,其寬度是720鹃彻,而高度是1280郊闯,因此我們可以把寬度范圍限定在[-1,1],并把高度范圍調(diào)整為[-1280/720,1280/720]或[-1.78,1.78]蛛株。同理在橫屏模式情況下团赁,把高度范圍設(shè)為[-1.78,1.78],而把高度范圍設(shè)為[-1,1]泳挥。
通過調(diào)整已有的坐標(biāo)空間然痊,最終會改變我們可用的空間。
通過這個方法屉符,不論是豎屏模式還是橫屏模式剧浸,物體看起來就都一樣了锹引。
3.使用虛擬坐標(biāo)空間
調(diào)整坐標(biāo)空間,以便我們把屏幕方向考慮進(jìn)來唆香,我們需要停止直接在歸一化設(shè)備坐標(biāo)上工作嫌变,遙開始在虛擬坐標(biāo)空間里工作。接下來躬它,我們需要找到某種可以把虛擬空間坐標(biāo)轉(zhuǎn)化回歸依化設(shè)備坐標(biāo)的方法腾啥,讓OpenGL可以正確的渲染它們。這種轉(zhuǎn)換應(yīng)該把屏幕方向計(jì)算在內(nèi)冯吓,以使圖像在豎屏模式和橫屏模式看上去都一樣倘待。
我們想要進(jìn)行的操作叫作正交投影。使用正交投影组贺,不管多遠(yuǎn)或者多近凸舵,所有物體看上去大小總是相同的。為了更高的理解這種投影是做什么的失尖,想象一下啊奄,在我們的場景中有一個火車軌道,直接從空中俯瞰掀潮,這些軌道看起來是這樣的:
還有一種特殊類型的正交投影菇夸,被稱為等軸測投影,它是從側(cè)角觀察一種正交投影仪吧。正如在一些城市模擬和策略游戲中看到的庄新,這種類型的投影能用來重新創(chuàng)建一個經(jīng)典的三維角。
當(dāng)我們使用正交投影把虛擬坐標(biāo)變換回歸化設(shè)備坐標(biāo)時邑商,實(shí)際上定義了三維世界內(nèi)部的一個區(qū)域摄咆。在這個區(qū)域內(nèi)的所有東西都會顯示在屏幕上,而區(qū)域外的所有東西都會被剪裁掉人断。
利用正交投影矩陣改變立方體的大小吭从,以使我們可以在屏幕上看到或多或少的場景。我們也能改變立方體的形狀彌補(bǔ)屏幕的寬高比的影響恶迈。
4.線性代數(shù)基礎(chǔ)
OpenGL大量使用了向量和矩陣涩金,矩陣的最重要的用途之一就是建立正交和透視投影。其原因之一是暇仲,從本質(zhì)上來說步做,使用矩陣做投影只涉及對一組數(shù)據(jù)按順序執(zhí)行大量的加法和乘法,這些運(yùn)算在現(xiàn)代GPU上執(zhí)行的非衬胃剑快全度。
4.1向量
一個向量是一個有多個元素的一維數(shù)組。在OpenGL里斥滤,一個位置通常是一個四元素向量将鸵,顏色也一樣勉盅。我們使用的大多數(shù)向量一般都有四個元素。在下面的例子中顶掉, 我們可看到一個位置向量草娜,它有一個X,一個Y痒筒,一個Z宰闰,一個W分量。
4.2矩陣
一個矩陣是一個有多個元素的二維數(shù)組簿透。在OpenGL里移袍,我們一般使用矩陣作向量投影,如正交或者透視投影萎战,并且也用它們旋轉(zhuǎn)物體咐容,平移物體以及縮放物體。我們把矩陣與每個要變換的向量相乘可實(shí)現(xiàn)這些變換蚂维。下面就是一個矩陣:
4.3矩陣與向量的乘法
要讓矩陣乘以一個向量,我們把矩陣放在左邊路狮,向量放在右邊虫啥。如下:
規(guī)則就是矩陣第一行乘以向量第一列,以第一行為例:矩陣第一行第一個元素乘以向量第一列第一個元素奄妨,加上矩陣第一行第二個元素乘以向量第一列第二個元素涂籽,加上矩陣第一行第三個元素乘以向量第一列第三個元素,加上矩陣第一行第四個元素乘以向量第一列第四個元素砸抛。矩陣二评雌,三,四行同理直焙。
4.4單位矩陣
單位矩陣如下:
之所以被稱為單位矩陣景东,是因?yàn)檫@個矩陣乘以任何向量總是得到與原來相同的向量。就像把任何數(shù)字乘以1會得到原來的數(shù)字一樣奔誓。
4.5平移矩陣
既然理解了單位矩陣斤吐,讓我們看一個非常簡單的矩陣類型---平移矩陣。它在OpenGL里十分常用厨喂。使用這種類型的矩陣和措,我們可以把一個物體沿著指定的距離移動。這個矩陣和單位矩陣差不多蜕煌,但在右側(cè)指定了三個額外的元素:
讓我們盾一個位置(2派阱,2)的例子,這個位置Z默認(rèn)是0斜纪,W默認(rèn)是1.我們把這個向量沿X軸平移3贫母,沿Y軸也平移3故响,因此,把Xtranslation賦值為3颁独,Ytranslation賦值為3彩届。結(jié)果如下:
這個位置正是我們所期望和(5,5)誓酒。
5.正交投影
要定義正交投影樟蠕,我們將使用Android的Matrix類,它在android.opengl包中靠柑。這個類有一個稱為orthoM()的方法寨辩,它可以為我們生成一個正交投影。
我們來看一下orthoM()參數(shù):
orthom(float[] m,int mOffset,float left,float rigth,float bottom,float top,float near,float far)
float[] m:目標(biāo)數(shù)組歼冰,這個數(shù)組長度至少有16個元素靡狞,這樣它才能存儲正交投影矩陣。
int mOffset:結(jié)果矩陣起始的偏移值隔嫡。
float left:X軸的最小范圍甸怕。
float right:X軸的最大范圍。
float bottom:Y軸的最小范圍腮恩。
float top:Y軸的最大范圍梢杭。
float near:Z軸的最小范圍。
float far:Z軸的最大范圍秸滴。
當(dāng)我們調(diào)用這個方法的時候武契,它應(yīng)該產(chǎn)生下面的正交投影矩陣:
這個正交投影矩陣會把所有在左右之間,上下之間和遠(yuǎn)近之間的事物映射到歸一化設(shè)備坐標(biāo)中從-1到1的范圍荡含,在這個范圍內(nèi)所有事物在屏幕上都是可見的咒唆。
主要的區(qū)別就是Z軸有一個負(fù)值符號,它的效果是反轉(zhuǎn)Z坐標(biāo)释液。這就意味著全释,物體離得越遠(yuǎn),Z坐標(biāo)的負(fù)值會越來越小均澳。之所以這樣完全是歷史和傳統(tǒng)的原因恨溜。
6.左手與右手坐標(biāo)系統(tǒng)
為了更好的理解Z軸問題,我們需要理解左手坐標(biāo)系統(tǒng)與右手坐標(biāo)系統(tǒng)之間的區(qū)別找前。想知道一個坐標(biāo)系統(tǒng)是左手的還是右手的糟袁,你拿出一只手,把大拇指指向X軸正值方向躺盛,然后把食指指向Y軸正值方向项戴。
現(xiàn)在,把你的中指指向Z軸槽惫。如果你需要用左手做這些周叮,那你看到的就是一個左手坐標(biāo)系統(tǒng)辩撑;如果你需要用右手,那你看到的就是一個右手坐標(biāo)系統(tǒng)仿耽。把你的中指指向Z軸合冀,記住要把大拇指指向X軸方向,食指指向Y軸正值方向项贺。如下圖:
其實(shí)左手還是右手選擇都沒關(guān)系君躺,只是一個簡單的轉(zhuǎn)換。歸一化設(shè)備坐標(biāo)使用的是左手坐標(biāo)系統(tǒng)开缎,而在OpenGL的早期版本棕叫,默認(rèn)使用的確實(shí)右手坐標(biāo)系統(tǒng),其使用Z的負(fù)值增加表示距離增加奕删。這就是為什么Android的Matrix會默認(rèn)生成反轉(zhuǎn)Z的矩陣俺泣。
7.更新程序
7.1更新著色器
修改前一章的頂點(diǎn)著色器中的代碼如下:
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的矩陣伏钠。更新位置負(fù)值那一行:
gl_Position =u_Matrix*a_Position;
我們沒有傳遞數(shù)組中定義的位置,而是傳遞那個位置與一個矩陣的乘積坏怪。它意味著頂點(diǎn)數(shù)組不用再被翻譯為歸一化設(shè)備的坐標(biāo)了贝润,其將被理解為存在于這個矩陣所定義的虛擬坐標(biāo)空間中。這個矩陣會把坐標(biāo)從虛擬坐標(biāo)空間變化回歸一化設(shè)備坐標(biāo)铝宵。
7.2添加矩陣數(shù)組和一個新的uniform
打開上一節(jié)的LYJRenderer添加成員變量:
private static final String U_MATRIX="u_Matrix";
我們還需要一個頂點(diǎn)數(shù)組存儲矩陣:
private final float[] projectionMatrix=new float[16];
我們也需要一個整型值用于保存那個矩陣uniform的位置:
private int uMatrixLocation;
然后我們只需要在onSurfaceCreated()中加入如下代碼:
uMatrixLocation=GLES20.glGetUniformLocation(program,U_MATRIX);
7.3創(chuàng)建正交投影矩陣
更新onSurfaceChanged(),在GLES20.glViewport()調(diào)用后面加入如下代碼:
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);
}
這段代碼會創(chuàng)建一個正交投影矩陣华畏,這個矩陣會把屏幕的當(dāng)前方向計(jì)算在內(nèi)鹏秋。注意在Android中不只有一個Matrix類,因此你要確保導(dǎo)入了android.opengl.Matrix亡笑。
我們首先計(jì)算了寬高比侣夷,它使用寬和高中的較大值除以寬和高的較小值。不管是豎屏還是橫屏仑乌,這個值都一樣百拓。
7.4傳遞矩陣給著色器
在LYJRenderer中的onDrawFrame()中,我們在glClear()調(diào)用之后加入如下代碼:
GLES20.glUniformMatrix4fv(uMatrixLocation,1,false,projectionMatrix,0);