之前用kanzi的3D UI引擎和cocos-2d的時候都有遇到過這個問題,就如何把3D場景中的XY平面的尺寸對映為與螢幕畫素一一對應的,即XY平面上的一個單位對應平面上的一個畫素。這個在3D UI開發過程中似乎並非必須,或者說很少有人這樣用,因為在遊戲場景中,UI可以處於場景的任何位置,並不侷限於XY平面內。
本次的分享總結所述的3D UI應用場景並非在遊戲中,而是注重在GUI應用上(類似QT等),即使用3D繪圖技術實現的一套類似2D UI一樣效果的引擎,由於UI系統是3D的,故能實現3D的動畫效果。把3D場景中的XY平面的尺寸對映為平面畫素一一對應的優點,是能保持並延續我們在2D開發時候的習慣,方便精準地控制UI控制元件在整個螢幕上的位置佈局。
本文的重點是“3D UI場景中把XY平面的尺寸對映為螢幕畫素”,因此需要您有如下的基本知識:
1、基本3D數理知識;
2、Opengl相關知識;
3、對3D計算機圖形學中“攝像機”概念有所瞭解;
本文包括如下內容:
- glm 3D數學庫簡介;
- 透視視錐體介紹;
- 使用glm函式庫生成攝像機矩陣;
- 分析如何調整攝像機和透視視錐體,使的3D場景中的XY平面的尺寸與螢幕畫素對應;
glm 3D 數學庫簡介
什麼是3D數學庫?
所謂3D數學庫,簡單地說就是把在3D計算機程式設計中常用到的資料型別、數學函式、3D處理公式及方法等統一集中起來,方便我們在處理3D場景時使用。
glm 3D數學庫是Opengl官網推薦使用的,包含了幾乎所有我們在處理3D場景是需要的數學函式。
glm的使用也非常簡單,glm提供的原始碼全部都是標頭檔案,我們只需把glm的標頭檔案引用到自己需要使用的工程中即可。
如下例項程式碼中,我們通過glm建立了一個4×4的矩陣,並對該矩陣進行了平移變換(詳細的glm使用介紹,大家可以參考glm官網的教程或文件)。
1 2 3 4 5 6 7 8 |
//示例程式碼 1.0 http://www.cnblogs.com/feng-sc/ #include //注意: glm的工程路徑需要自己配置 #include int main() { glm::mat4 matrix(1.0); matrix = glm::translate(matrix, glm::vec3(100.0f, 0.0f, 0.0f)); return 0; } |
作為簡介,glm的介紹就到此結束。
透視視錐體介紹
所謂的透視不是你所想的眼睛能看穿牆的意思,別多想了!簡單點,透視就是表示物體近大遠小的效果的意思。
如下圖所示,透視視錐體梯體幾何圖形,它類似於人的眼睛所能看到的範圍,在梯體之外的物體將不可見。
在3D數學裡,用什麼表示這個透視視錐體呢?沒錯,是矩陣!
使用glm函式庫能簡單地生成透視視錐體的矩陣,如下例項程式碼:
1 2 3 4 5 6 7 |
// 示例程式碼1.0: www.cnblogs.com/feng-sc/ // fovyInRadians : 弧度表示下圖中FOV // aspect : 視錐體寬與高的比例,可以理解為繪圖區域的寬高比 // zNear : 近平面離攝像機的距離 // zFar :遠平面離攝像機的距離 glm::mat4 projection = glm::perspective(fovyInRadians, aspect, zNear, zFar); |
(透視視錐體)
上訴例項程式碼中,projection又被成為透視矩陣,所有3D世界裡的物體,經過與projection矩陣相乘後,最終得到的物體將呈現如下兩種特點:
1、遠小近大的效果;
2、處於透視視錐外的物體將被忽略;
使用 glm 函式庫生成攝像機矩陣
本段我們先以一段程式碼起頭,如下:
1 2 3 |
// 示例程式碼1.0: www.cnblogs.com/feng-sc/ glm::mat4 view = glm::lookAt(m_position, m_target, m_up); |
lookAt函式得到的結果是一個檢視矩陣。有人把檢視矩陣稱為攝像機,也有人把檢視矩陣和透視投影矩陣合在一起稱為攝像機,我喜歡後者。
結合投影矩陣,我們總結一下,攝像機分別由如下引數決定:
1、透視投影矩陣projection決定了攝像機的視野範圍,包括視覺張角FOV、近平面、遠平面;
2、檢視矩陣決定了攝像機的位置、觀察方向;
最後投影矩陣與檢視矩陣將共同決定我們整個場景的顯示效果。
1 2 3 4 |
// 示例程式碼1.0 www.cnblogs.com/feng-sc/ glm::mat4 projection = glm::perspective(fovyInRadians, aspect, zNear, zFar); glm::mat4 view = glm::lookAt(m_position, m_target, m_up); glm::mat4 vpMat = projection * view |
分析如何調整攝像機和透視視錐體,使的3D場景中的XY平面的尺寸與螢幕畫素對應;
OK,終於來到了本文標題討論的問題點,3D UI場景中把XY平面的尺寸對映為螢幕畫素。
其實到現在為止,我們問題的解決方案也清晰了,如何實現“3D UI場景中把XY平面的尺寸對映為螢幕畫素” 呢?是的,就是調整攝像機的位置、遠/近平面、攝像機視角,使XY平面的單位尺寸恰好與平面畫素的單位對應即可。
那麼現在剩下的問題是:如何調整攝像機,使得我們的XY平面恰好與平面畫素對應呢?
在我們繼續之前,我們先來了解一個概念:齊次座標。
百度百科解釋說:齊次座標就是將一個原本是n維的向量用一個n+1維向量來表示。例如,二維點(x,y)的齊次座標表示為(hx,hy,h)(h可以是任意值)。
我們可以理解為,任何三維的點(hx,hy,h),在二維平面上的投影點均為(x,y)。
(透視視錐體側面平面圖)
上圖為透視視錐體側面平面圖,其中GI為透視視錐體的近平面,BF為遠平面,LS和TZ分別為視錐體的兩個不同位置的截面。
從2D平截視錐體看,透視視錐體GBFI範圍內的三維物體最後均被投影到GI平面上。由齊次座標概念可知,點B、U、M在GI平面上的投影均為點M,同理點F、W、P在GI平面上的投影均為點I。
我們:
假設TZ平面為XY平面且與螢幕畫素對應,螢幕高度畫素為h,角∠BAF = FOV (FOV為攝像機張角)
故,UW = h,UV = h/2;
故,
即,由螢幕寬度和攝像機張角,要使XY平面與螢幕畫素對應,我們求得攝像機位置點距離XY螢幕距離長度為AV。
下面的程式碼設定為螢幕左上角為原點是,攝像機的設定。
1 2 3 4 5 6 7 8 |
// 示例程式碼1.0 www.cnblogs.com/feng-sc/ float fov = 60; glm::perspective(glm::radiansfloat>(fov), (float)width / (float)height, 0.1f, 10000.0f); float z = height / (2 * tan(((float)(fov / 2.0)* glm::pifloat>()) / 180.0)); glm::vec3 positon((float)width / 2.0f, (float)height / 2.0f, -z); glm::vec3 target((float)width / 2.0f, (float)height / 2.0f, 0.0f); glm::vec3 up(0.0f, -1.0f, 0.0f); m_view = glm::lookAt(positon target, up); |