View Transform(檢視變換)詳解

小 樓 一 夜 聽 春 雨發表於2014-01-20

http://www.cnblogs.com/graphics/archive/2012/07/12/2476413.html

 

什麼是View Transform

我們可以用照相機的原理來闡釋3D圖形的繪製過程,想象一下,我們在攝影的時候都需要做哪些工作,大致可分為如下幾個步驟

  1. 擺放好待拍攝的物品,或者人物。
  2. 調整好拍攝角度。
  3. 調整焦距。
  4. 拍攝。

好了,來分析一下,上面的第一步就相當於世界變換了,將一個模型置於一個公認的座標系中,這裡所謂的公認,也就是大家都遵守的,目的是保證待拍攝的物體和照相機在同一個座標系。第二步相當於檢視變換,這個過程是調整Camera到合適的位置以便拍攝,在3D程式中,也就是設定View Matrix了。第三步調整焦距,這就相當於3D程式設計中的投影變換。

View Transform的過程就是在世界座標系中擺放Camera的過程,並將頂點由世界座標系轉換到Camera Space,在Camera Space中,觀察者(Camera)位於座標原點,觀察方向指向Z軸正方向。

為什麼要進行View Transform?

在world space中,camera並不一定位於座標原點,並且觀察方向不一定指向Z軸正方向,對於投影變換及其他的一些操作來說,如果不滿足這兩個條件,後續的的操作就會變得非常低效,所以為了提高效率,我們需要進行view transform。

View Transform的作用

View Transform主要有下面兩個作用。

  • 移動camera,使其位於world space的座標原點
  • 旋轉camera,使其朝向z軸正方向,也就是視線由原點指向z軸正方向。

這兩個過程,前一個實際上是平移,後一個實際上是旋轉。你可以想象成Camera也有三個座標軸x,y,z,檢視變換的過程就是將Camera的座標軸與世界座標系的座標軸對齊的過程。

注意:view transform中,所有位於world space中的models都隨著camera一起變換,所以視野並未發生變化,具體過程見下圖

如何求解View Matrix?

使用D3D函式

使用下面的D3D函式可以計算view matrix,第一個引數是輸出引數,返回求得的檢視矩陣,第二個引數是眼睛的位置,第三個引數是觀察點中心,最後一個引數是向上向量。該函式的最後兩個字母表示左手系(Left Hand)。

D3DXMatrixLookAtLH(&M, &eyePt, &lookCenter, &upVec) ;

手動求解

手動求解View matrix並不是難事,一個camera一般有如下四個屬性,

  • 向前向量(direction),相當於Z軸
  • 向上向量(up vector),相當於Y軸
  • 向右向量(right vector),相當於X軸
  • 位置(position)

其中前三個向量要求是相互垂直的。假設我們分別用d, u, v和p來表示這四個變數。並假設待求的檢視矩陣為V,根據前面的介紹我們知道,V的作用就是將攝像機移動到原點,並將攝像機的三個向量分別與座標軸對齊,d與z軸正方向對齊,u與y軸正方向對齊,r與x軸正方向對齊。假設將攝像機與座標軸對齊的矩陣為V,那麼V的推導過程如下。

通常在實際的程式設計中,只會給出如下三個量。至於d和r這兩個量,只能通過計算求得。

  • 攝像機(眼睛)的位置(eye point),相當於p
  • 觀察點中心(look at),假設為向量c
  • 向上向量(up vector),相當於u

可以通過下面方法求得d, r和u。

  • d = c - p,下面第一幅圖
  • r = d x u,下面第二幅圖
  • u = r x d,下面第三幅圖

注意上面的x是叉積運算(cross product),現在p,d,r,u四個量都已經知道,於是就可以根據上面的矩陣M求得檢視變換矩陣了,對應的程式碼如下。該函式有三個引數,分別是攝像機位置p,向上向量u和視點中心lookAt。

複製程式碼
D3DXMATRIX buildViewMatrix(D3DXVECTOR3& p, D3DXVECTOR3& u, D3DXVECTOR3& lookAt)
{
    // Calculate d
    D3DXVECTOR3 d = lookAt - p;
    D3DXVec3Normalize(&d, &d);

    // Calculate r
    D3DXVECTOR3 r;
    D3DXVec3Cross(&r, &u, &d);
    D3DXVec3Normalize(&r, &r);

    // Calculate up
    D3DXVec3Cross(&u, &r, &d);
    D3DXVec3Normalize(&u, &u);

    // Fill in the view matrix entries.
    float x = -D3DXVec3Dot(&p, &r);
    float y = -D3DXVec3Dot(&p, &u);
    float z = -D3DXVec3Dot(&p, &d);

    D3DXMATRIX M;
    M(0,0) = r.x; 
    M(1,0) = r.y; 
    M(2,0) = r.z; 
    M(3,0) = x;   

    M(0,1) = u.x;
    M(1,1) = u.y;
    M(2,1) = u.z;
    M(3,1) = y;  

    M(0,2) = d.x; 
    M(1,2) = d.y; 
    M(2,2) = d.z; 
    M(3,2) = z;   

    M(0,3) = 0.0f;
    M(1,3) = 0.0f;
    M(2,3) = 0.0f;
    M(3,3) = 1.0f;

    return M;
}
複製程式碼

注意事項,在求取View Matrix的時候有幾點是需要注意的:

  • 叉積滿足右手法則。
  • 叉積不滿足交換律。 a x b = - b x a
  • DirectX使用左手系,滿足左手法則。

只要最終求得的三個向量,up, right和d滿足左手法則即可。如果應用了View Matrix之後發現模型左右或者上下顛倒了,那麼就說明求取的時候沒有滿足左手系。

好了,矩陣求解完畢,趕快使用SetTransform(D3DTS_VIEW, &M) ;來試試吧,效果和使用函式D3DXMatrixLookAtLH是一樣的!

Happy Coding!!!

 

 

我們看DX9幫助文件中,D3DXMatrixLookAtLH函式的計算公式也是這樣的:
zaxis = normal(At - Eye)
xaxis = normal(cross(Up, zaxis))
yaxis = cross(zaxis, xaxis)

 xaxis.x           yaxis.x           zaxis.x          0
 xaxis.y           yaxis.y           zaxis.y          0
 xaxis.z           yaxis.z           zaxis.z          0
-dot(xaxis, eye)  -dot(yaxis, eye)  -dot(zaxis, eye)  1

唯一的不同就是D3DXMatrixLookAtLH的函式引數只有四個,即攝像機位置Eye、攝像機朝向At、攝像機上向量Up,通過簡單的運算就可以轉換成我們上面所說的那四個描述量了,在這裡就不贅言了。

相關文章