OpenGL ES 2.0 (iOS)[04]:坐標空間 與 OpenGL ES 2 3D空間


目錄

一、多坐標系

1.  世界坐標系
2.  物體(模型)坐標系
3.  攝像機坐標系
4.  慣性坐標系

二、坐標空間

1.  世界空間
2.  模型空間
3.  攝像機空間
4.  裁剪空間
5.  屏幕空間

三、OpenGL ES 2 3D 空間

1.  變換發生的過程
2.  各個變換流程分解簡述
3.  四次變換與編程應用

四、工程例子

五、參考書籍


一、多坐標系

1. 世界坐標系

  • 即物體存在的空間,以此空間某點為原點,建立的坐標系

  • 世界坐標系是最大的坐標系,世界坐標系不一定是指“世界”,準確來說是一個空間或者區域,就是足以描述區域內所有物體的最大空間坐標,是我們關心的最大坐標空間;

  • 例子

    • ep1:
      比如我現在身處廣州,要描述我現在所在的空間,對我而言最有意義就是,我身處廣州的那里,而此時的廣州就是我關心的“世界坐標系”,而不用描述我現在的經緯坐標是多少,不需要知道我身處地球的那個經緯位置。
      這個例子是以物體的方向思考的最合適世界坐標系;(當然是排除我要與廣州以外的區域進行行為交互的情況咯!)
  • ep2:
    如果現在要描述廣州城的全貌,那么對于我們而言,最大的坐標系是不是就是廣州這個世界坐標系,也就是所謂的我們最關心的坐標系;
    這個例子是以全局的方向思考的最合適世界坐標系;

  • 世界坐標系主要研究的問題:

  1. 每個物體的位置和方向
  2. 攝像機的位置和方向
  3. 世界的環境(如:地形)
  4. 物體的運動(從哪到哪)

2. 物體(模型)坐標系

  • 模型自身的坐標系,坐標原點在模型的某一點上,一般是幾何中心位置為原點

  • 模型坐標系是會跟隨模型的運動而運動,因為它是模型本身的 “一部份” ;

  • 模型內部的構件都是以模型坐標系為參考進而描述的;

  • ep:
    比如有一架飛機,機翼位于飛機的兩側,那么描述機翼最合適的坐標系,當然是相對于飛機本身,機翼位于那里;飛機在飛行的時候,飛機本身的坐標系是不是在跟隨運動,機翼是不是在飛機的坐標中同時運動著。

3. 攝像機坐標系

  • 攝像機坐標系就是以攝像機本身為原點建立的坐標系,攝像機本身并不可見,它表示的是有多少區域可以被顯示(渲染)

  • 白色線所圍成的空間,就是攝像機所能捕捉到的最大空間,而物體則位于空間內部;

  • 位于攝像機捕捉空間外的圖形會直接被剔除掉;

4. 慣性坐標系

  • 它的 X 軸與世界坐標系的 X 軸平行且方向相同,Y 軸亦然,它的原點與模型坐標系相同

  • 它的存在的核心價值是,簡化坐標系的轉換,即簡化模型坐標系到世界坐標系的轉換;


二、坐標空間

坐標空間就是坐標系形成的空間

1. 世界空間

世界坐標系形成的空間,光線計算一般是在此空間統一進行;

2. 模型空間

模型坐標系形成的空間,這里主要包含模型頂點坐標和表面法向量的信息;


第一次變換
模型變換(Model Transforms):就是指從模型空間轉換到世界空間的過程


3. 攝像機空間

攝像機空間

攝像機空間,就是黃色區域所包圍的空間;
攝像機空間在這里就是透視投影,透視投影用于 3D 圖形顯示,反映真實世界的物體狀態;

透視知識擴展 《透視》


第二次變換
視變換(View Transforms):就是指從世界空間轉換到攝像機空間的過程


  • 攝像機空間,也被稱為眼睛空間,即可視區域;
  • 其中,LookAt(攝像機的位置) 和 Perspective(攝像機的空間) 都是在調整攝像空間;

4. 裁剪空間

圖形屬于裁剪空間則保留,圖形在裁剪空間外,則剔除(Culled)

攝像機 帶注解

標號(3)[視景體] ,所指的空間即為裁剪空間,這個空間就由 Left、Right、Top、Bottom、Near、Far 六個面組成的四棱臺,即視景體。


視景體

圖中紫色區域為視場角


fov & zoom

從而引出,視場縮放為:


zoom
  • 其次,頂點是用齊次坐標表示{x, y, z, w}, 3D 坐標則為{x/w, y/w, z/w}而 w 就是判斷圖形是否屬于裁剪空間的關鍵:
錐面 關系
Near z < -w
Far z > w
Bottom y < -w
Top y > w
Left x < -w
Right x > w

即坐標值,不符合這個范圍的,都會被裁剪掉

坐標 值范圍
x [-w , w]
y [-w, w]
z [-w, w]

第三次變換
投影變換(Projection Transforms): 當然包括正交、透視投影了,就是指從攝影機空間到視景體空間的變換過程


5. 屏幕空間

它就是顯示設備的物理屏幕所在的坐標系形成的空間,它是 2D 的且以像素為單位,原點在屏幕的幾何中心點

屏幕坐標空間.jpg


第四次變換(最后一次)
視口變換(ViewPort Transforms): 指從裁剪空間到屏幕空間的過程,即從 3D 到 2D


這里主要是關注像素的分布,即像素縱橫比;因為圖形要從裁剪空間投影映射到屏幕空間中,需要知道真實的環境的像素分布情況,不然圖形就會出現變形;

《OpenGL ES 2.0 (iOS)[02]:修復三角形的顯示》這篇文章就是為了修復屏幕像素比例不是 1 : 1 引起的拉伸問題,而它也就是視中變換中的一個組成部分。

  • 像素縱橫比計算公式
像素縮放比

三、OpenGL ES 2 3D 空間

1. 變換發生的過程

OpenGL ES 2 變換流程圖
  • 這個過程表明的是 GPU 處理過程(渲染管線);

  • 變換過程發生在,頂點著色與光柵化之間,即圖元裝配階段;

  • 編寫程序的時候,變換的操作是放在頂點著色器中進行處理;

  • 右下角寫明了,總共就是四個變換過程:模型變換、視變換、投影變換、視口變換,經過這四個變換后,圖形的點就可以正確并如愿地顯示在用戶屏幕上了;

  • 側面反應,要正確地渲染圖形,就要掌握這四種變換;

2. 各個變換流程分解簡述

  • 階段一:追加 w 分量為 1.0 (第一個藍框)

    這個階段不需要程序員操作

    這里的原因是,OpenGL 需要利用齊次坐標去進行矩陣的運算,核心原因當然就是方便矩陣做乘法咯(R(4x4) 點乘 R(4x1) 嘛)!

  • 階段二:用戶變換 (第二個藍框)

    這個階段需要程序員操作,在 Vertex Shader Code 中進行操作

    這個階段主要是把模型正確地通過 3D 變換(旋轉、縮放、平移)放置于攝像機的可視區域(視景體)中,包括處理攝像機的位置、攝像機的可視區域占整個攝像機空間的大小。

    這個階段過后,w 就不在是 1.0 了

  • 階段三:重新把齊次坐標轉換成 3D 坐標 (第三個藍框)

    這個階段不需要程序員操作

    要重新轉換回來的原因,也很簡單 ---- 齊次坐標只是為了方便做矩陣運算而引入的,而 3D 坐標點才是模型真正需要的點位置信息。

    這個階段過后,所有的點坐標都會標準化(所謂標準化,就是單位為1),x 和 y 值范圍均在 [-1.0, 1.0 ]之間,z 就在 [ 0.0, 1.0 ] 之間;

    x 和 y 值范圍均在 [-1.0, 1.0 ]之間,才能正確顯示,原因是 OpenGL 的正方體值范圍就是 [ -1.0, 1.0 ] 不存在其它范圍的值;而 z 的值范圍是由攝像機決定的,攝像機所處的位置就是 z = 0,的位置,所以 0 是指無限近,攝像機可視區的最遠處就是 z = 1, 所以 1 是指無限遠;

  • 階段四:重新把齊次坐標轉換成 3D 坐標 (第四個藍框)

    *這個階段需要程序員操作,在圖形渲染前要進行操作,即在 gldraw 前 **

    這個階段核心的就是 ViewPort 和 DepthRange 兩個,前者是指視口,后者是深度,分別對應的 OpenGL ES 2 的 API 是:

函數 描述
glViewport 調整視窗位置和尺寸
glDepthRange 調整視景體的 near 和 far 兩個面的位置 (z)
glViewport
void glViewport(GLint x, GLint y, GLsizei w, GLsizei h)
x, y 以渲染的屏幕坐標系為參考的視口原點坐標值(如:蘋果的移動設備都是是以左上角為坐標原點)
w, h 要渲染的視口尺寸,單位是像素
glDepthRange
void glDepthRange(GLclampf n, GLclampf f)
n, f n, f 分別指視景體的 near 和 far ,前者的默認值為 0 ,后者的默認值為 1.0, 它們的值范圍均為 [ 0.0, 1.0 ], 其實就是 z 值

3. 四次變換與編程應用

  • 下面這兩張圖片就是 Vertex Shader Code 中的最終代碼
#version 100

attribute vec4 v_Position;
uniform mat4 v_Projection, v_ModelView;

attribute vec4 v_Color;
varying mediump vec4 f_color;

void main(void) {
    f_color = v_Color;
    gl_Position  = v_Projection * v_ModelView * v_Position;
}
 v_Projection 表示投影變換;v_ModelView 表示模型變換和視變換;
  • 第一次變換:模型變換,模型空間到世界空間 ( 1 -> 2 )

請看《OpenGL ES 2.0 (iOS)[02]:修復三角形的顯示》 這篇文章,專門講模型變換的。

  • 余下的幾次變換,都是和攝像機模型在打交道
    攝像機里面的模型
Camera Model
要完成攝像機正確地顯示模型,要設置攝像機位置、攝像機的焦距:
  1. 設置攝像機的位置、方向 --> (視變換) gluLookAt (ES 沒有這個函數),使要渲染的模型位于攝像機可視區域中;【完成圖中 1 和 2】
  2. 選擇攝像機的焦距去適應整個可視區域 --> (投影變換) glFrustum(視景體的六個面)、gluPerspective(透視) 、glOrtho(正交)( ES 沒有這三個函數) 【完成圖中 3】
  3. 設置圖形的視圖區域,對于 3D 圖形還可以設置 depth- range --> glViewport 、glDepthRange
  • 第二次變換:視變換,世界空間到攝像機空間 ( 2 -> 3 )

上面提到, ES 版本沒有 gluLookAt 這個函數,但是我們知道,這里做的都是矩陣運算,所以可以自己寫一個功能一樣的矩陣函數即可;

// 我不想寫,所以可以用 GLKit 提供給我們的函數
/*
Equivalent to gluLookAt.
*/
GLK_INLINE GLKMatrix4 GLKMatrix4MakeLookAt(float eyeX, float eyeY, float eyeZ,
                                                 float centerX, float centerY, float centerZ,
                                                 float upX, float upY, float upZ);
Frustum

函數的 eye x、y、z 就是對應圖片中的 Eye at ,即攝像機的位置;

函數的 center x、y、z 就是對應圖片中的 z-axis 可視區域的中心點;

函數的 up x、y、z 就是對應圖片中的 up 指攝像機上下的位置(就是角度);

  • 第三次變換:投影變換,攝像機空間到裁剪空間 ( 3 -> 4 )
view frustum

當模型處于視景體外時會被剔除掉,如果模型有一部分在視景體內時,模型的點信息只會剩下在視景體內的,其它的點信息不渲染;

/*
 Equivalent to glFrustum.
 */
GLK_INLINE GLKMatrix4 GLKMatrix4MakeFrustum(float left, float right,
                                            float bottom, float top,
                                            float nearZ, float farZ);

這個是設置視景體六個面的大小的;

  • 透視投影
透視投影

對應的投影公式 :

完整的透視投影公式

使用 GLKit 提供的函數:

/*
 Equivalent to gluPerspective.
 */
GLK_INLINE GLKMatrix4 GLKMatrix4MakePerspective(float fovyRadians, // 視場角
                                                    float aspect,  // 屏幕像素縱橫比
                                                    float nearZ, // 近平面距攝像機位置的距離
                                                    float farZ); // 遠平面攝像機位的距離
  • 正交投影
Orthographic projection

對應的投影公式 :

完整的正交投影公式
/*
 Equivalent to glOrtho.
 */
GLK_INLINE GLKMatrix4 GLKMatrix4MakeOrtho(float left, float right,
                                          float bottom, float top,
                                          float nearZ, float farZ);
  • 第四次變換:視口變換,裁剪空間到屏幕空間 ( 4 -> 5 )

這里就是設置 glViewPort 和 glDepthRange 當然 2D 圖形不用設置 glDepthRange ;

  • 實際編程過程中的使用過程

  • 第一步,如果是 3D 圖形的渲染,那么要綁定深度渲染緩存(DepthRenderBuffer),若是 2D 可以跳過,因為它的頂點信息中沒有 z 信息 ( z 就是頂點坐標的深度信息 );

    1. Generate ,請求 depth buffer ,生成相應的內存標識符
    2. Bind,綁定申請的內存標識符
    3. Configure Storage,配置儲存 depth buffer 的尺寸
    4. Attach,裝載 depth buffer 到 Frame Buffer 中
      具體的程序代碼:
  • 第二步,縮寫 Vertex Shader Code
#version 100

attribute vec4 v_Position;
uniform mat4 v_Projection, v_ModelView; // 投影變換、模型視圖變換

attribute vec4 v_Color;
varying mediump vec4 f_color;

void main(void) {
     f_color = v_Color;
     gl_Position = v_Projection * v_ModelView * v_Position;
}

一般是把四次變換寫成這兩個,當然也可以寫成一個;因為它們是一矩陣,等同于一個常量,所以使用的是 uniform 變量,變量類型就是 mat4 四乘四方陣(齊次矩陣);

  • 第三步,就是外部程序賦值這兩個變量

注意,要在 glUseProgram 函數后,再使用 glUniform 函數來賦值變量,不然是無效的;*

依次完成 模型變換、視變換、投影變換,即可;它們兩兩用矩陣乘法進行連接即可;

如:modelMatrix 點乘 viewMatrix , 它們的結果再與 projectionMatrix 點乘,即為 ModelViewMatrix ;

GLKit 點乘函數,
GLK_INLINE GLKMatrix4 GLKMatrix4Multiply(GLKMatrix4 matrixLeft, GLKMatrix4 matrixRight);

  • 第四步,如果是 3D 圖形,有 depth buffer ,那么要清除深度渲染緩存

使用 glClear(GL_DEPTH_BUFFER_BIT); 進行清除,當然之后就是要使能深度測試 glEnable(GL_DEPTH_TEST); 不然圖形會變形;

最好,也使能 glEnable(GL_CULL_FACE); 這里的意思就是,把在屏幕后面的點剔除掉,就是不渲染;判斷是前還是后,是利用提供的模型頂點信息中點與點依次連接形成的基本圖元的時鐘方向進行判斷的,這個 OpenGL 會自行判斷;


ClockWise & Counterclockwise

左為順時針,右為逆時針;

  • 第五步,設置 glViewPort 和 glDepthRange

使用 OpenGL ES 提供的 glViewPort 和 glDepthRange 函數即可;


四、工程例子

Github: 《DrawSquare_3DFix》


五、參考書籍

《OpenGL ES 2.0 Programming Guide》
《OpenGL Programming Guide 8th》
《3D 數學基礎:圖形與游戲開發》
《OpenGL 超級寶典 第五版》
《Learning OpenGL ES For iOS》

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容

  • 本文首發于個人博客:Lam's Blog - 【OpenGL ES】入門及繪制一個三角形,文章由MarkDown語...
    格子林ll閱讀 7,303評論 2 18
  • 1 前言 一直想沿著圖像處理這條線建立一套完整的理論知識體系,同時積累實際應用經驗。因此有了從使用AVFounda...
    RichardJieChen閱讀 5,710評論 5 12
  • 一周緊張的工作結束了,有點疲憊,但每天都很開心,感覺團隊的每個小伙伴都特別好,從心底里喜歡每個人,雖然累,但還是要...
    L莎莎閱讀 116評論 0 0
  • 盛夏時節,安徽牛商爭霸賽的各位小伙伴又一次齊聚在徽馬科技,進行新一場的線下交流活動。這次線下交流的主題為:阿里巴巴...
    安徽飾界舞臺桁架閱讀 439評論 0 2
  • 來到寢室,打開門便看到三張帶上下鋪的床架子整整齊齊的擺在兩邊,右邊兩張,左邊一張再加一個六個人的柜子,潔白的地板上...
    右手心聲閱讀 236評論 0 0