Learn OpenGLES: 正交變換

上一節中,我們遺留了一個Android適配的問題。 這個問題表現爲我們創建的物體長寬比例如果與屏幕寬高比不一致的畫,會被屏幕拉伸。 

爲什麼會拉伸? 
首先我們來認識齊次座標 
 , 這個就是一個齊次座標, 其中x, y, z 分別表示世界座標系中x, y, z軸的座標。 關於空間中一點的座標,你可以想象下有一個原點,基於這個原點建立一個座標系,而物體相對於該原點的位置就是這個座標系下的座標。 
   而w 簡單來說,可以理解爲一個尺度空間。 即如果w = 1時, 這個空間是與這個座標系等同的空間;如果w = 2,即這個空間就會縮小一半。w越大,其空間其實越小, w越小,其空間爲越大。 如果w爲0 時, 那麼x, y,z 全部趨於無窮, 即它們在無窮遠, 再也回不來了。 


關於齊次座標,它其實可以用空間中的一條直線來表示即 
   
即滿足上式的一點 (x, y, z, 1)必在該直線上,如果等式兩邊同時乘以w 則,所表示的點(wx, wy, wz, w)也在該直線上,且爲同一個點。 


其次,瞭解下OpenGL管線流程中的座標變換 
一次是 clip變換,即通過 w 齊次座標,將座標縮放在尺度爲1的空間裏 
一次爲三維變換,即將clip後的座標,映射爲攝像機屏幕上的二維座標。 
一次爲glViewport變換,即將二維座標乘以屏幕的寬和高 變爲屏幕上的圖像。 


對於clip變換,我們已經大體瞭解了,就是縮放。 
而對於三維變換,又可分爲兩種: 透視變換 和 正交變換 
關於這兩種變換,可以形象的用一下兩幅圖來表示: 
 



這兩個圖都是我們看到的三維世界, 但左圖具有很強的透視效果,總體表現爲近大遠小的感覺。 空間中的點與攝像機光心連線與攝像機屏幕所交點即爲屏幕的成像點(小孔成像原理), 因此,較近的物體總能形成較大像。   
而右圖由於離鐵路較近,俯視正對,基本可近似爲一個正交變換。 即所有的空間的點都是平行映射到屏幕上的, 這使得空間中點的Z軸失去作用。 所以,正交變換更適合二維圖像。 


分析我們之前的屏幕拉伸原因: 
opengl中, clip變換後的點都在[-1. 1]之間, 因此,邊界爲-1或 1。只考慮寬和高的話, 則爲一個 2x2的正方形。 
也就是,物體所在空間的高比爲1:1,  假如,我們的屏幕是2:1的, 則最後經過glViewport變換後,寬就被拉伸了2倍。 
下文,我們稱clip變換的空間爲clip空間。 


最後,來看下,正交變換矩陣: 
 

其中, right, left, top, bottom, far, near 爲 clip空間的邊界。 
  
我們假定空間中的一點爲: ,(這裏不考慮z軸座標) 正交變換矩陣對該點作用(變換)後 ,新的座標點爲: 

 
上式表示,變換後的x軸座標, 不管x座標爲何,其變換後的點總會落在(left, right)之間。 opengl中,left爲-1, right 爲1。 
同理有軸座標。 
那麼,進而可得出,如果屏幕的寬高比爲aspect:1,則,應該讓clip空間的bottom 爲-aspect, top 爲aspect。 反之,如果爲1:aspect, 則應該讓left 爲-aspect, right爲-aspect。 
這樣,經過正交變換後,映射到屏幕中的座標經過glViewport 變換,就與屏幕的寬高比就爲1:1,就不會出現拉伸的現象了。 
———————————————————————————————————————————— 
知道了,上述原理後,我們來修改代碼: 
首先,在shader中添加 正交變換。 

複製代碼
1
2
3
4
5
6
7
8
9
10
11
12
attribute vec4 a_Position;
attribute vec4 a_Color;
       
       
uniform mat4 u_Projection;
       
varying vec4 v_Color;
void main()
{
    v_Color = a_Color;
    gl_Position = u_Projection*a_Position;
}
有線性代數的基本知識,知道 一個4x4的矩陣,乘以一個4x1的座標爲一個4x1的座標, 這中間其實完成了一個變換,就像上面我們講的原理一樣。 


接着,定義變量: 

複製代碼
1
2
3
private static final String U_PROJECTION ="u_Projection";
private int uProjectionLocation;
private final float[] projectionMatrix = new float[16];
這裏定義變換矩陣爲一個大小爲16的數組,表示4x4的矩陣, 其中前四個元素表示矩陣的第一列,以此類推。 


然後, 在onSurfaceCreated裏, 找到shader裏投影矩陣的位置, 

複製代碼
1
uProjectionLocation = glGetUniformLocation(program, U_PROJECTION);

在onSurfaceChanged,根據屏幕的寬高比定義 正交變換矩陣: 

複製代碼
1
2
3
4
5
6
7
8
9
10
final float aspectRatio = width>height ?
                (float) width / (float) height:
                (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);
}

orthoM 是我們靜態庫, android.opengl.Matrix裏的函數,它的源代碼基本就是上述的公式。它的參數,第一個,是我們傳遞的數組, 第二個是數組的起始位置,後面依此爲clip空間的 left, right, bottom, top , near, far。 

最後,在onDrawFrame裏添加: 

複製代碼
1
glUniformMatrix4fv(uProjectionLocation, 1false, projectionMatrix, 0); // 將矩陣傳遞給shader


看一下效果: 
 

比起上一帖的豎屏是不是好多了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章