OpenGL的透視投影

坐標(biāo)系統(tǒng)

在所有頂點著色器運行后,所有我們可見的頂點都變?yōu)闃?biāo)準(zhǔn)化設(shè)備坐標(biāo)(Normalized Device Coordinate, NDC)。每個頂點的x,y,z坐標(biāo)都應(yīng)該在-1.0到1.0之間,超出這個坐標(biāo)范圍的頂點都將不可見。投影是一個將坐標(biāo)轉(zhuǎn)換為標(biāo)準(zhǔn)化設(shè)備坐標(biāo)的過程。在對象坐標(biāo)轉(zhuǎn)換到屏幕空間之前會先將其轉(zhuǎn)換到多個坐標(biāo)系統(tǒng)。將對象的坐標(biāo)轉(zhuǎn)換到幾個過渡坐標(biāo)系(Intermediate Coordinate System)的優(yōu)點在于,在這些特定的坐標(biāo)系統(tǒng)中進行一些操作或運算更加方便和容易。
對我們來說比較重要的總共有5個不同的坐標(biāo)系統(tǒng):

  1. 物體空間(Object Space), 或稱為局部空間(Local Space)
  2. 世界空間(World Space)
  3. 觀察空間(View Space,或者稱為視覺空間(Eye Space))
  4. 裁剪空間(Clip Space)
  5. 屏幕空間(Screen Space)

為了將坐標(biāo)從一個坐標(biāo)系轉(zhuǎn)換到另一個坐標(biāo)系,我們需要用到幾個轉(zhuǎn)換矩陣,最重要的幾個分別是模型(Model)、視圖(View)、投影(Projection)三個矩陣。首先,頂點坐標(biāo)開始于局部空間(Local Space),稱為局部坐標(biāo)(Local Coordinate),然后經(jīng)過世界坐標(biāo)(World Coordinate),觀察坐標(biāo)(View Coordinate),裁剪坐標(biāo)(Clip Coordinate),并最后以屏幕坐標(biāo)(Screen Coordinate)結(jié)束。

坐標(biāo)系統(tǒng)的轉(zhuǎn)換
  1. 局部坐標(biāo)是對象相對于局部原點的坐標(biāo);也是對象開始的坐標(biāo)。
  2. 將局部坐標(biāo)轉(zhuǎn)換為世界坐標(biāo),世界坐標(biāo)是作為一個更大空間范圍的坐標(biāo)系統(tǒng)。這些坐標(biāo)是相對于世界的原點的。
  3. 接下來我們將世界坐標(biāo)轉(zhuǎn)換為觀察坐標(biāo),觀察坐標(biāo)是指以攝像機或觀察者的角度觀察的坐標(biāo)。
  4. 在將坐標(biāo)處理到觀察空間之后,我們需要將其投影到裁剪坐標(biāo)。裁剪坐標(biāo)是處理-1.0到1.0范圍內(nèi)并判斷哪些頂點將會出現(xiàn)在屏幕上。
  5. 最后,我們需要將裁剪坐標(biāo)轉(zhuǎn)換為屏幕坐標(biāo),我們將這一過程成為視口變換(Viewport Transform)。視口變換將位于-1.0到1.0范圍的坐標(biāo)轉(zhuǎn)換到由glViewport函數(shù)所定義的坐標(biāo)范圍內(nèi)。最后轉(zhuǎn)換的坐標(biāo)將會送到光柵器,由光柵器將其轉(zhuǎn)化為片段。
    物體變換到的最終空間就是世界坐標(biāo)系,并且你會想讓這些物體分散開來擺放(從而顯得更真實)。對象的坐標(biāo)將會從局部坐標(biāo)轉(zhuǎn)換到世界坐標(biāo);該轉(zhuǎn)換是由模型矩陣(Model Matrix)實現(xiàn)的。
    觀察空間(View Space)經(jīng)常被人們稱之OpenGL的攝像機(Camera)(所以有時也稱為攝像機空間(Camera Space)或視覺空間(Eye Space))。觀察空間就是將對象的世界空間的坐標(biāo)轉(zhuǎn)換為觀察者視野前面的坐標(biāo)。因此觀察空間就是從攝像機的角度觀察到的空間。觀察(視圖)矩陣(View Matrix)里,用來將世界坐標(biāo)轉(zhuǎn)換到觀察空間。
    在一個頂點著色器運行的最后,OpenGL期望所有的坐標(biāo)都能落在一個給定的范圍內(nèi),且任何在這個范圍之外的點都應(yīng)該被裁剪掉(Clipped)。被裁剪掉的坐標(biāo)就被忽略了,所以剩下的坐標(biāo)就將變?yōu)槠聊簧峡梢姷钠?。這也就是裁剪空間(Clip Space)名字的由來。
    為了將頂點坐標(biāo)從觀察空間轉(zhuǎn)換到裁剪空間,我們需要定義一個投影矩陣(Projection Matrix),它指定了坐標(biāo)的范圍,例如,每個維度都是從-1000到1000。投影矩陣接著會將在它指定的范圍內(nèi)的坐標(biāo)轉(zhuǎn)換到標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系中(-1.0,1.0)。
    投影矩陣將觀察坐標(biāo)轉(zhuǎn)換為裁剪坐標(biāo)的過程采用兩種不同的方式,每種方式分別定義自己的平截頭體。我們可以創(chuàng)建一個正射投影矩陣(Orthographic Projection Matrix)或一個透視投影矩陣(Perspective Projection Matrix)。
投影方式
  • 正射投影,即正投影,多用于三維健模
  • 透視投影,則由于和人的視覺系統(tǒng)相似,多用于在二維平面中對三維世界的呈現(xiàn)。透視投影具有消失感、距離感、相同大小的形體呈現(xiàn)出有規(guī)律的變化等一系列的透視特性,能逼真地反映形體的空間形象。透視投影通常用于動畫、視覺仿真以及其它許多具有真實性反映的方面。
    可以將透視投影變換看作是調(diào)整照相機的焦距,它模擬了為照相機選擇鏡頭的過程。投影變換是所有變換中最復(fù)雜的一個。

    視錐體是一個三維體,他的位置和攝像機相關(guān),視錐體的形狀決定了模型如何從camera space投影到屏幕上。最常見的投影類型-透視投影,使得離攝像機近的物體投影后較大,而離攝像機較遠的物體投影后較小。透視投影使用棱錐作為視錐體,攝像機位于棱錐的椎頂。該棱錐被前后兩個平面截斷,形成一個棱臺,叫做View Frustum,只有位于Frustum內(nèi)部的模型才是可見的。
    object-projection.png
透視投影

我們以O(shè)penGL繪制3D圖形為例來了解透視投影。

  1. 定義物體、camera的GLframe:cameraFrame、objectFrame
    GLFrame叫參考幀,其中存儲了1個世界坐標(biāo)點和2個世界坐標(biāo)下的方向向量,也就是9個GLfloat值,分別用來表示:當(dāng)前位置點,向前方向向量,向上方向向量。我們分別創(chuàng)建物體和camera的GLFrame的表示。
    一般來說,物體坐標(biāo)系X軸永遠平行于視口的水平方向,+X的方向根據(jù)右手定則由+Y與+Z得出;Y軸永遠平行于視口的豎直方向,豎直向上為+Y;Z軸永遠平行于視口的垂直紙面向里方向,正前方為+Z。也就是說,在世界坐標(biāo)系中,物體坐標(biāo)系的Y軸看上去就是GLFrame的向上方向向量,Z軸看上去就是GLFrame的向前方向向量,而X軸由Y軸方向向量與Z軸方向向量根據(jù)右手定則可得出。
    舉例來說,比如相機,默認(rèn)狀態(tài)下,其物體坐標(biāo)系為:+Y豎直向上;相機的視野中心軸即Z軸,+Z指向相機看到的方向,也就是垂直紙面向里;根據(jù)+Y與+Z可得出+X是水平向左。所以對于默認(rèn)的相機,修改其vOrigin.y,若增大,則相機上移,看到的景物下移,減小同理;修改其vOrigin.z,若增大,則相機向前移動,看到的景物更加靠近,減小同理;修改其vOrigin.x,若增大,則相機向左移動,看到的景物右移,減小同理。

  2. 在改變的窗口的時候定義一個View Frustum,創(chuàng)建View Frustum完成可以通過GetProjectionMatrix()得到一個投影矩陣
    SetPerspective(float fFov, float fAspect, float fNear, float fFar)
    fFov,這個可以理解為視角的大小
    fAspect,是實際窗口的縱橫比,即x/y
    fNear,這個呢,表示你近處,的裁面,必須為正數(shù)
    fFar表示遠處的裁面,必須為正數(shù)
    創(chuàng)建GLMatrixStack projectionMatrix;投影矩陣堆棧,把投影矩陣壓入投影矩陣堆棧,然后加載模型堆棧的單位矩陣

void changeSize(int w, int h) {
    glViewport(0, 0, w, h);
    // 創(chuàng)建投影矩陣 并將它載入投影矩陣堆棧中
    viewFrustum.SetPerspective(50.f, float(w)/float(h), 1.f, 500.f);
    // projectionMatrix 、modelViewMatrix 投影、模型堆棧為全局變量
    // 把投影矩陣壓入投影矩陣堆棧
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    // 調(diào)用模型堆棧,載入單元矩陣 ,這一步可以省略,因為modelViewMatrix堆棧底部默認(rèn)就是一個單元矩陣
    modelViewMatrix.LoadIdentity();
}
projection.jpg
  1. setupRC函數(shù)的操作
  • 定義全局變量GLGeometryTransform transformPipeline; GLGeometryTransform類型的變換管道是專門用來管理投影和模型矩陣的 。其實就是把兩個矩陣堆棧都存到他這個管道里,方便我們用的時候拿出來使用。調(diào)用SetMatrixStacks(GLMatrixStack& mModelView, GLMatrixStack& mProjection) 給transformPipeline設(shè)置堆棧transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
  • 定義頂點坐標(biāo),設(shè)置圖元類型,并提交批次類
void setupRC(){
    /// 設(shè)置背景顏色,顏色緩存區(qū)
    glClearColor(1.f, 1.f, 1.f, 1.f);
    /// 初始化著色管理器
    shaderManager.InitializeStockShaders();
    // 設(shè)置變換管線
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
    // 為了讓效果明顯,將觀察者坐標(biāo)位置Z移動往屏幕里移動15個單位位置
    // 參數(shù):表示離屏幕之間的距離。 負數(shù),是往屏幕后面移動;正數(shù),往屏幕前面移動
    cameraFrame.MoveForward(-15.f);
    
    GLfloat vCoast[9] = {
        3,3,0,
        0,3,0,
        3,0,0
    };
    // 用點的方式
    pointBatch.Begin(GL_POINTS, 3);
    pointBatch.CopyVertexData3f(vCoast);
    pointBatch.End();
}
  1. 渲染
  • 根據(jù)cameraFrame、objectFrame創(chuàng)建觀察者矩陣和object矩陣
    // 觀測者矩陣
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    // 矩陣相乘,觀察者矩陣乘模型視圖矩陣頂部的矩陣,結(jié)果存儲在模型視圖矩陣堆棧的頂部
    modelViewMatrix.MultMatrix(mCamera);
    // object矩陣
    M3DMatrix44f mObjectFrame;
    objectFrame.GetMatrix(mObjectFrame);
    modelViewMatrix.MultMatrix(mObjectFrame);
void renderScene(void) {
    /// 清除一個或一組特定的緩沖區(qū)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
    /// 設(shè)置一組浮點數(shù)表示紅色
    GLfloat vRed[] = {1.f, 0.f, 0.f, 1.f};
    // 如果 PushMatix() 括號里是空的,就代表是把棧頂?shù)木仃噺?fù)制一份,再壓棧到它的頂部。如果不是空的,比如是括號里是單元矩陣,那么就代表壓入一個單元矩陣到棧頂了。
    modelViewMatrix.PushMatrix();
    // 觀測者矩陣
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    // 矩陣相乘,觀察者矩陣乘模型視圖矩陣頂部的矩陣,結(jié)果存儲在模型視圖矩陣堆棧的頂部
    modelViewMatrix.MultMatrix(mCamera);
    // 獲取object矩陣
    M3DMatrix44f mObjectFrame;
    objectFrame.GetMatrix(mObjectFrame);
    // object矩陣乘模型視圖矩陣頂部的矩陣,結(jié)果存儲在模型視圖矩陣矩陣的頂部
    modelViewMatrix.MultMatrix(mObjectFrame);
    // 調(diào)用平面著色器,使用transformPipeline傳入模型矩陣堆棧和投影矩陣堆棧頂部矩陣
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vRed);
    
    // 設(shè)置渲染時點的大小
    glPointSize(8.f);
    pointBatch.Draw();
    glPointSize(1.f);
        
    // 還原單元矩陣
    modelViewMatrix.PopMatrix();
    
    // 交換緩沖區(qū)
    glutSwapBuffers();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容