OpenGL中矩陣的相乘順序探討

一、從問題切入

先看代碼(省略無用部分):

//模型視圖棧
GLMatrixStack       modelViewMatrix;
//投影棧
GLMatrixStack       projectionMatrix;
//觀察者矩陣
GLFrame             cameraFrame;
//模型矩陣
GLFrame             objectFrame;
//平截頭體
GLFrustum           viewFrustum;
//幾何變換的管道
GLGeometryTransform transformPipeline;

void SetupRC(){
  //變換管道綁定模型視圖棧與投影棧
  transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}

void ChangeSize(int w, int h)
{
  ...
    //創(chuàng)建投影矩陣,并將它載入投影矩陣堆棧中
    viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);
    //獲取投影矩陣,放入投影棧
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    //調(diào)用頂部載入單元矩陣
    modelViewMatrix.LoadIdentity();
    ...
}

// 召喚場景
void RenderScene(void)
{
    ...
   
    //壓棧
    modelViewMatrix.PushMatrix();
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    
    //矩陣乘以矩陣堆棧的頂部矩陣,相乘的結(jié)果隨后簡存儲在堆棧的頂部
    modelViewMatrix.MultMatrix(mCamera);
    
    M3DMatrix44f mObjectFrame;
    //只要使用 GetMatrix 函數(shù)就可以獲取矩陣堆棧頂部的值,這個函數(shù)可以進(jìn)行2次重載。用來使用GLShaderManager 的使用。或者是獲取頂部矩陣的頂點(diǎn)副本數(shù)據(jù)
    objectFrame.GetMatrix(mObjectFrame);
    
    //矩陣乘以矩陣堆棧的頂部矩陣,相乘的結(jié)果隨后簡存儲在堆棧的頂部
    modelViewMatrix.MultMatrix(mObjectFrame);
    
    /* GLShaderManager 中的Uniform 值——平面著色器
     參數(shù)1:平面著色器
     參數(shù)2:運(yùn)行為幾何圖形變換指定一個 4 * 4變換矩陣
     --transformPipeline.GetModelViewProjectionMatrix() 獲取的
     GetMatrix函數(shù)就可以獲得矩陣堆棧頂部的值
     參數(shù)3:顏色值(黑色)
     */
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
    ...
    
    //還原到以前的模型視圖矩陣(單位矩陣)
    modelViewMatrix.PopMatrix();
   ...
}

...

從上面的代碼中,我們看到,在RenderScene函數(shù)中,模型視圖棧首先乘以mCamera(觀察者矩陣),然后,我們再是乘以mObjectFrame(模型矩陣),最后,使用transformPipeline.GetModelViewProjectionMatrix()方法,我們用投影矩陣乘以模型視圖矩陣,將最后結(jié)果返回。

也就是如下順序:

projection * view * model

那么,我們?yōu)槭裁匆凑者@個順序來添加矩陣呢?

這要從兩個方面講起,一個是看這幾個方法的實(shí)現(xiàn),還有一個就是坐標(biāo)變換。

二、實(shí)現(xiàn)

首先是矩陣相乘的實(shí)現(xiàn)

inline void MultMatrix(const M3DMatrix44f mMatrix) {
    M3DMatrix44f mTemp;
    m3dCopyMatrix44(mTemp, pStack[stackPointer]);
    m3dMatrixMultiply44(pStack[stackPointer], mTemp, mMatrix);
}

從實(shí)現(xiàn)可以看出,調(diào)用這個方法就是將棧頂?shù)木仃嚾〕?,乘以參?shù)的矩陣,再將相乘結(jié)果存放在原來的棧頂。

我們在兩個地方依次調(diào)用了這個方法

//1.矩陣乘以模型視圖棧的頂部矩陣,相乘的結(jié)果隨后簡存儲在堆棧的頂部
modelViewMatrix.MultMatrix(mCamera);
//2.矩陣乘以模型視圖棧的頂部矩陣,相乘的結(jié)果隨后簡存儲在堆棧的頂部
modelViewMatrix.MultMatrix(mObjectFrame);

按照實(shí)現(xiàn),這兩部合并就是mCamera * mObjectFrame的結(jié)果存放在模型視圖矩陣的棧頂。

再看transformPipeline.GetModelViewProjectionMatrix()的實(shí)現(xiàn):

const M3DMatrix44f& GetModelViewProjectionMatrix(void)
{
    m3dMatrixMultiply44(_mModelViewProjection, _mProjection->GetMatrix(), _mModelView->GetMatrix());
    return _mModelViewProjection;
}

這個的實(shí)現(xiàn)也很簡單,就是返回投影矩陣 * 模型視圖矩陣的結(jié)果,綜合上面的分析,也就是:

projection * view * model

順序是如此,下面我們從坐標(biāo)變換來講解為什么是這個順序

三、坐標(biāo)變換

坐標(biāo)系變化

如圖所示,為了將坐標(biāo)從一個坐標(biāo)系變換到另一個坐標(biāo)系,我們需要用到幾個變換矩陣,最重要的幾個分別是模型(Model)、觀察(View)、投影(Projection)三個矩陣。我們的頂點(diǎn)坐標(biāo)起始于局部空間(Local Space),在這里它稱為局部坐標(biāo)(Local Coordinate),它在之后會變?yōu)槭澜缱鴺?biāo)(World Coordinate),觀察坐標(biāo)(View Coordinate),裁剪坐標(biāo)(Clip Coordinate),并最后以屏幕坐標(biāo)(Screen Coordinate)的形式結(jié)束。它的流程如下:

  1. 局部坐標(biāo)是對象相對于局部原點(diǎn)的坐標(biāo),也是物體起始的坐標(biāo)。
  2. 下一步是將局部坐標(biāo)變換為世界空間坐標(biāo),世界空間坐標(biāo)是處于一個更大的空間范圍的。這些坐標(biāo)相對于世界的全局原點(diǎn),它們會和其它物體一起相對于世界的原點(diǎn)進(jìn)行擺放。
  3. 接下來我們將世界坐標(biāo)變換為觀察空間坐標(biāo),使得每個坐標(biāo)都是從攝像機(jī)或者說觀察者的角度進(jìn)行觀察的。
  4. 坐標(biāo)到達(dá)觀察空間之后,我們需要將其投影到裁剪坐標(biāo)。裁剪坐標(biāo)會被處理至-1.0到1.0的范圍內(nèi),并判斷哪些頂點(diǎn)將會出現(xiàn)在屏幕上。
  5. 最后,我們將裁剪坐標(biāo)變換為屏幕坐標(biāo),我們將使用一個叫做視口變換(Viewport Transform)的過程。視口變換將位于-1.0到1.0范圍的坐標(biāo)變換到由glViewport函數(shù)所定義的坐標(biāo)范圍內(nèi)。最后變換出來的坐標(biāo)將會送到光柵器,將其轉(zhuǎn)化為片段。

我們?yōu)樯厦娴牟襟E創(chuàng)建了三個矩陣:模型矩陣、觀察矩陣和投影矩陣。

一個頂點(diǎn)坐標(biāo)將會根據(jù)以下過程被變換到剪裁坐標(biāo):

Vclip=Mprojection?Mview?Mmodel?Vlocal

注意矩陣運(yùn)算的順序是相反的(我們需要從右往左閱讀矩陣的乘法)。最后的頂點(diǎn)應(yīng)該被復(fù)制到頂點(diǎn)著色器的內(nèi)建變量gl_position,OpenGL將會自動進(jìn)行透視除法和裁剪。

根據(jù)這個過程,我們就可以回答一開始的問題,為什么OpenGL中需要先加入camera(也就是view),再加入model,最后是projection

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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