目錄
一立莉、多坐標系
1. 世界坐標系
2. 物體(模型)坐標系
3. 攝像機坐標系
4. 慣性坐標系
二绢彤、坐標空間
1. 世界空間
2. 模型空間
3. 攝像機空間
4. 裁剪空間
5. 屏幕空間
三、OpenGL ES 2 3D 空間
1. 變換發(fā)生的過程
2. 各個變換流程分解簡述
3. 四次變換與編程應(yīng)用
四蜓耻、工程例子
五茫舶、參考書籍
一、多坐標系
1. 世界坐標系
即物體存在的空間刹淌,以此空間某點為原點饶氏,建立的坐標系
世界坐標系是最大的坐標系,世界坐標系不一定是指“世界”芦鳍,準確來說是一個空間或者區(qū)域嚷往,就是足以描述區(qū)域內(nèi)所有物體的最大空間坐標,是我們關(guān)心的最大坐標空間柠衅;
-
例子
-
ep1:
比如我現(xiàn)在身處廣州皮仁,要描述我現(xiàn)在所在的空間,對我而言最有意義就是菲宴,我身處廣州的那里贷祈,而此時的廣州就是我關(guān)心的“世界坐標系”,而不用描述我現(xiàn)在的經(jīng)緯坐標是多少喝峦,不需要知道我身處地球的那個經(jīng)緯位置势誊。
這個例子是以物體的方向思考的最合適世界坐標系;(當然是排除我要與廣州以外的區(qū)域進行行為交互的情況咯Rゴ馈)
-
ep1:
ep2:
如果現(xiàn)在要描述廣州城的全貌粟耻,那么對于我們而言查近,最大的坐標系是不是就是廣州這個世界坐標系,也就是所謂的我們最關(guān)心的坐標系挤忙;
這個例子是以全局的方向思考的最合適世界坐標系霜威;世界坐標系主要研究的問題:
- 每個物體的位置和方向
- 攝像機的位置和方向
- 世界的環(huán)境(如:地形)
- 物體的運動(從哪到哪)
2. 物體(模型)坐標系
模型自身的坐標系,坐標原點在模型的某一點上册烈,一般是幾何中心位置為原點
模型坐標系是會跟隨模型的運動而運動戈泼,因為它是模型本身的 “一部份” ;
模型內(nèi)部的構(gòu)件都是以模型坐標系為參考進而描述的赏僧;
ep:
比如有一架飛機大猛,機翼位于飛機的兩側(cè),那么描述機翼最合適的坐標系淀零,當然是相對于飛機本身挽绩,機翼位于那里;飛機在飛行的時候窑滞,飛機本身的坐標系是不是在跟隨運動琼牧,機翼是不是在飛機的坐標中同時運動著。
3. 攝像機坐標系
攝像機坐標系就是以攝像機本身為原點建立的坐標系哀卫,攝像機本身并不可見巨坊,它表示的是有多少區(qū)域可以被顯示(渲染)
白色線所圍成的空間,就是攝像機所能捕捉到的最大空間此改,而物體則位于空間內(nèi)部趾撵;
位于攝像機捕捉空間外的圖形會直接被剔除掉;
4. 慣性坐標系
它的 X 軸與世界坐標系的 X 軸平行且方向相同共啃,Y 軸亦然占调,它的原點與模型坐標系相同
它的存在的核心價值是,簡化坐標系的轉(zhuǎn)換移剪,即簡化模型坐標系到世界坐標系的轉(zhuǎn)換究珊;
二、坐標空間
坐標空間就是坐標系形成的空間
1. 世界空間
世界坐標系形成的空間纵苛,光線計算一般是在此空間統(tǒng)一進行剿涮;
2. 模型空間
模型坐標系形成的空間,這里主要包含模型頂點坐標和表面法向量的信息攻人;
第一次變換
模型變換(Model Transforms):就是指從模型空間轉(zhuǎn)換到世界空間的過程
3. 攝像機空間
攝像機空間取试,就是黃色區(qū)域所包圍的空間;
攝像機空間在這里就是透視投影怀吻,透視投影用于 3D 圖形顯示瞬浓,反映真實世界的物體狀態(tài);
透視知識擴展 《透視》
第二次變換
視變換(View Transforms):就是指從世界空間轉(zhuǎn)換到攝像機空間的過程
- 攝像機空間蓬坡,也被稱為眼睛空間猿棉,即可視區(qū)域磅叛;
- 其中,LookAt(攝像機的位置) 和 Perspective(攝像機的空間) 都是在調(diào)整攝像空間铺根;
4. 裁剪空間
圖形屬于裁剪空間則保留宪躯,圖形在裁剪空間外,則剔除(Culled)
標號(3)[視景體] 位迂,所指的空間即為裁剪空間,這個空間就由 Left详瑞、Right掂林、Top、Bottom坝橡、Near泻帮、Far 六個面組成的四棱臺,即視景體计寇。
圖中紫色區(qū)域為視場角
從而引出锣杂,視場縮放為:
- 其次,頂點是用齊次坐標表示{x, y, z, w}, 3D 坐標則為{x/w, y/w, z/w}而 w 就是判斷圖形是否屬于裁剪空間的關(guān)鍵:
錐面 | 關(guān)系 |
---|---|
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. 屏幕空間
它就是顯示設(shè)備的物理屏幕所在的坐標系形成的空間踱蠢,它是 2D 的且以像素為單位,原點在屏幕的幾何中心點
第四次變換(最后一次)
視口變換(ViewPort Transforms): 指從裁剪空間到屏幕空間的過程棋电,即從 3D 到 2D
這里主要是關(guān)注像素的分布茎截,即像素縱橫比;因為圖形要從裁剪空間投影映射到屏幕空間中赶盔,需要知道真實的環(huán)境的像素分布情況企锌,不然圖形就會出現(xiàn)變形;
《OpenGL ES 2.0 (iOS)[02]:修復三角形的顯示》這篇文章就是為了修復屏幕像素比例不是 1 : 1 引起的拉伸問題于未,而它也就是視中變換中的一個組成部分撕攒。
- 像素縱橫比計算公式
三、OpenGL ES 2 3D 空間
1. 變換發(fā)生的過程
這個過程表明的是 GPU 處理過程(渲染管線)沉眶;
變換過程發(fā)生在打却,頂點著色與光柵化之間,即圖元裝配階段谎倔;
編寫程序的時候柳击,變換的操作是放在頂點著色器中進行處理;
右下角寫明了片习,總共就是四個變換過程:模型變換捌肴、視變換蹬叭、投影變換、視口變換状知,經(jīng)過這四個變換后秽五,圖形的點就可以正確并如愿地顯示在用戶屏幕上了;
側(cè)面反應(yīng)饥悴,要正確地渲染圖形坦喘,就要掌握這四種變換;
2. 各個變換流程分解簡述
-
階段一:追加 w 分量為 1.0 (第一個藍框)
這個階段不需要程序員操作
這里的原因是西设,OpenGL 需要利用齊次坐標去進行矩陣的運算瓣铣,核心原因當然就是方便矩陣做乘法咯(R(4x4) 點乘 R(4x1) 嘛)!
-
階段二:用戶變換 (第二個藍框)
這個階段需要程序員操作贷揽,在 Vertex Shader Code 中進行操作
這個階段主要是把模型正確地通過 3D 變換(旋轉(zhuǎn)棠笑、縮放、平移)放置于攝像機的可視區(qū)域(視景體)中禽绪,包括處理攝像機的位置蓖救、攝像機的可視區(qū)域占整個攝像機空間的大小。
這個階段過后印屁,w 就不在是 1.0 了
-
階段三:重新把齊次坐標轉(zhuǎn)換成 3D 坐標 (第三個藍框)
這個階段不需要程序員操作
要重新轉(zhuǎn)換回來的原因循捺,也很簡單 ---- 齊次坐標只是為了方便做矩陣運算而引入的,而 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 是指無限近履怯,攝像機可視區(qū)的最遠處就是 z = 1回还, 所以 1 是指無限遠;
-
階段四:重新把齊次坐標轉(zhuǎn)換成 3D 坐標 (第四個藍框)
*這個階段需要程序員操作叹洲,在圖形渲染前要進行操作柠硕,即在 gldraw 前 **
這個階段核心的就是 ViewPort 和 DepthRange 兩個,前者是指視口,后者是深度蝗柔,分別對應(yīng)的 OpenGL ES 2 的 API 是:
函數(shù) | 描述 |
---|---|
glViewport | 調(diào)整視窗位置和尺寸 |
glDepthRange | 調(diào)整視景體的 near 和 far 兩個面的位置 (z) |
glViewport | |
---|---|
void glViewport(GLint x, GLint y, GLsizei w, GLsizei h) | |
x, y 以渲染的屏幕坐標系為參考的視口原點坐標值(如:蘋果的移動設(shè)備都是是以左上角為坐標原點) | |
w, h 要渲染的視口尺寸闻葵,單位是像素 |
glDepthRange | |
---|---|
void glDepthRange(GLclampf n, GLclampf f) | |
n, f n, f 分別指視景體的 near 和 far ,前者的默認值為 0 癣丧,后者的默認值為 1.0槽畔, 它們的值范圍均為 [ 0.0, 1.0 ], 其實就是 z 值 |
3. 四次變換與編程應(yīng)用
- 下面這兩張圖片就是 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]:修復三角形的顯示》 這篇文章,專門講模型變換的掏呼。
- 余下的幾次變換坏快,都是和攝像機模型在打交道
攝像機里面的模型
要完成攝像機正確地顯示模型,要設(shè)置攝像機位置憎夷、攝像機的焦距:
- 設(shè)置攝像機的位置、方向 --> (視變換) gluLookAt (ES 沒有這個函數(shù))昧旨,使要渲染的模型位于攝像機可視區(qū)域中拾给;【完成圖中 1 和 2】
- 選擇攝像機的焦距去適應(yīng)整個可視區(qū)域 --> (投影變換) glFrustum(視景體的六個面)、gluPerspective(透視) 兔沃、glOrtho(正交)( ES 沒有這三個函數(shù)) 【完成圖中 3】
- 設(shè)置圖形的視圖區(qū)域蒋得,對于 3D 圖形還可以設(shè)置 depth- range --> glViewport 、glDepthRange
- 第二次變換:視變換乒疏,世界空間到攝像機空間 ( 2 -> 3 )
上面提到额衙, ES 版本沒有 gluLookAt 這個函數(shù),但是我們知道怕吴,這里做的都是矩陣運算窍侧,所以可以自己寫一個功能一樣的矩陣函數(shù)即可;
// 我不想寫转绷,所以可以用 GLKit 提供給我們的函數(shù)
/*
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);
函數(shù)的 eye x伟件、y、z 就是對應(yīng)圖片中的 Eye at 议经,即攝像機的位置斧账;
函數(shù)的 center x、y煞肾、z 就是對應(yīng)圖片中的 z-axis 可視區(qū)域的中心點咧织;
函數(shù)的 up x、y籍救、z 就是對應(yīng)圖片中的 up 指攝像機上下的位置(就是角度)习绢;
- 第三次變換:投影變換,攝像機空間到裁剪空間 ( 3 -> 4 )
當模型處于視景體外時會被剔除掉钧忽,如果模型有一部分在視景體內(nèi)時毯炮,模型的點信息只會剩下在視景體內(nèi)的逼肯,其它的點信息不渲染;
/*
Equivalent to glFrustum.
*/
GLK_INLINE GLKMatrix4 GLKMatrix4MakeFrustum(float left, float right,
float bottom, float top,
float nearZ, float farZ);
這個是設(shè)置視景體六個面的大小的桃煎;
- 透視投影
對應(yīng)的投影公式 :
使用 GLKit 提供的函數(shù):
/*
Equivalent to gluPerspective.
*/
GLK_INLINE GLKMatrix4 GLKMatrix4MakePerspective(float fovyRadians, // 視場角
float aspect, // 屏幕像素縱橫比
float nearZ, // 近平面距攝像機位置的距離
float farZ); // 遠平面攝像機位的距離
- 正交投影
對應(yīng)的投影公式 :
/*
Equivalent to glOrtho.
*/
GLK_INLINE GLKMatrix4 GLKMatrix4MakeOrtho(float left, float right,
float bottom, float top,
float nearZ, float farZ);
- 第四次變換:視口變換篮幢,裁剪空間到屏幕空間 ( 4 -> 5 )
這里就是設(shè)置 glViewPort 和 glDepthRange 當然 2D 圖形不用設(shè)置 glDepthRange ;
實際編程過程中的使用過程
-
第一步为迈,如果是 3D 圖形的渲染三椿,那么要綁定深度渲染緩存(DepthRenderBuffer),若是 2D 可以跳過葫辐,因為它的頂點信息中沒有 z 信息 ( z 就是頂點坐標的深度信息 )搜锰;
- Generate ,請求 depth buffer 耿战,生成相應(yīng)的內(nèi)存標識符
- Bind蛋叼,綁定申請的內(nèi)存標識符
- Configure Storage,配置儲存 depth buffer 的尺寸
- 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 函數(shù)后,再使用 glUniform 函數(shù)來賦值變量公荧,不然是無效的带射;*
依次完成 模型變換、視變換循狰、投影變換窟社,即可;它們兩兩用矩陣乘法進行連接即可绪钥;
如:modelMatrix 點乘 viewMatrix , 它們的結(jié)果再與 projectionMatrix 點乘灿里,即為 ModelViewMatrix ;
GLKit 點乘函數(shù)程腹,
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 會自行判斷;
左為順時針狐血,右為逆時針淀歇;
- 第五步,設(shè)置 glViewPort 和 glDepthRange
使用 OpenGL ES 提供的 glViewPort 和 glDepthRange 函數(shù)即可匈织;
四浪默、工程例子
Github: 《DrawSquare_3DFix》
五、參考書籍
《OpenGL ES 2.0 Programming Guide》
《OpenGL Programming Guide 8th》
《3D 數(shù)學基礎(chǔ):圖形與游戲開發(fā)》
《OpenGL 超級寶典 第五版》
《Learning OpenGL ES For iOS》