1. 程式人生 > >OpenGL 座標變換詳解

OpenGL 座標變換詳解

模型(Model)、檢視(View)和投影(Projection)矩陣

1。模型矩陣(在Object Coordinates內)

這個三維模型,和我們心愛的紅色三角形一樣,是由一組頂點定義的。頂點的XYZ座標是相對於物體中心定義的:也就是說,若某頂點位於(0, 0, 0),它就在物體的中心。
model
也許玩家需要用鍵鼠控制這個模型,所以我們希望能夠移動它。這簡單,只需學會:縮放*旋轉*平移就行了。在每一幀中,用算出的這個矩陣,去乘(在GLSL中乘,不是C++中!)所有的頂點,物體就動了。唯一不動的就是世界座標系(World Space)的中心
world
現在,物體所有頂點都位於世界座標系。下圖中黑色箭頭的意思是:從模型座標系(Model Space)(頂點都相對於模型的中心定義)變換到世界座標系(頂點都相對於世界座標系中心定義)。
下圖概括了這一過程:

M
2。檢視矩陣(World Coordinates)
camera
起初,相機位於世界座標系的原點。移動世界只需乘上一個矩陣。假如你想把相機向右(X軸正方向)移動3個單位,這和把整個世界(包括網格)向左(X軸負方向)移3個單位是等效的!
下圖展示了:從世界座標系(頂點都相對於世界座標系中心定義)到觀察座標系(Camera Space,頂點都相對於相機定義)的變換。
model_to_world_to_camera

glm::mat4 CameraMatrix = glm::LookAt(
cameraPosition, // the position of your camera, in world space
cameraTarget, // where you want to look at, in world space
upVector // probably glm::vec3(0,1,0), but (0,-1,0) would make you looking upside-down, which can be great too
);
下圖解釋了上述變換過程:

MV

3。投影矩陣(Eye Coordinates)
現在,我們處於觀察座標系中。這意味著,經歷了這麼多變換後,現在一個座標為(0,0)的頂點,應該被畫在螢幕的中心。但僅有x、y座標還不足以確定物體是否應該畫在螢幕上:它到相機的距離(z)也很重要!兩個x、y座標相同的頂點,z值較大的一個將會最終顯示在螢幕上。

這就是所謂的透視投影(perspective projection):
model_to_world_to_camera_to_homogeneous
好在用一個4×4矩陣就能表示這個投影¹ :

// Generates a really hard-to-read matrix, but a normal, standard 4×4 matrix nonetheless
glm::mat4 projectionMatrix = glm::perspective(
FoV, // The horizontal Field of View, in degrees : the amount of “zoom”. Think “camera lens”. Usually between 90° (extra wide) and 30° (quite zoomed in)
4.0f / 3.0f, // Aspect Ratio. Depends on the size of your window. Notice that 4/3 == 800/600 == 1280/960, sounds familiar ?
0.1f, // Near clipping plane. Keep as big as possible, or you’ll get precision issues.
100.0f // Far clipping plane. Keep as little as possible.
);
最後一個變換:

從觀察座標系(頂點都相對於相機定義)到齊次座標系(Homogeneous Space)(頂點都在一個小立方體中定義。立方體內的物體都會在螢幕上顯示)的變換。

最後一幅圖示:
MVP-235x300
再添幾張圖,以便大家更好地理解投影變換。投影前,藍色物體都位於觀察座標系中,紅色的東西是相機的視域四稜錐(frustum):這是相機實際能看見的區域。nondeforme
用投影矩陣去乘前面的結果,得到如下效果:
homogeneous
此圖中,視域四稜錐變成了一個正方體(每條稜的範圍都是-1到1,圖上不太明顯),所有的藍色物體都經過了相同的形變。因此,離相機近的物體就顯得大一些,遠的顯得小一些。和真實生活中一樣!

讓我們從視域四稜錐的“後面”看看它們的模樣:
projected1
這就是你得出的影象了!看上去太方方正正了,因此,還需要做一次數學變換使之適合實際的視窗大小:
final1
這就是實際渲染的影象啦!

4。複合變換:模型檢視投影矩陣(MVP)
… 再來一串親愛的矩陣乘法:

// C++ : compute the matrix
glm::mat3 MVPmatrix = projection * view * model; // Remember : inverted !
// GLSL : apply it
transformed_vertex = MVP * in_vertex;
總結

第一步:建立模型檢視投影(MVP)矩陣。任何要渲染的模型都要做這一步。
// Projection matrix : 45° Field of View, 4:3 ratio, display range : 0.1 unit 100 units
glm::mat4 Projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);
// Camera matrix
glm::mat4 View = glm::lookAt(
glm::vec3(4,3,3), // Camera is at (4,3,3), in World Space
glm::vec3(0,0,0), // and looks at the origin
glm::vec3(0,1,0) // Head is up (set to 0,-1,0 to look upside-down)
);
// Model matrix : an identity matrix (model will be at the origin)
glm::mat4 Model = glm::mat4(1.0f); // Changes for each model !
// Our ModelViewProjection : multiplication of our 3 matrices
glm::mat4 MVP = Projection * View * Model; // Remember, matrix multiplication is the other way around
第二步:把MVP傳給GLSL
// Get a handle for our “MVP” uniform.
// Only at initialisation time.
GLuint MatrixID = glGetUniformLocation(programID, “MVP”);

// Send our transformation to the currently bound shader,
// in the “MVP” uniform
// For each model you render, since the MVP will be different (at least the M part)
glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);
第三步:在GLSL中用MVP變換頂點
in vec3 vertexPosition_modelspace;
uniform mat4 MVP;

void main(){

// Output position of the vertex, in clip space : MVP * position
vec4 v = vec4(vertexPosition_modelspace,1); // Transform an homogeneous 4D vector, remember ?
gl_Position = MVP * v;
}

opengl座標系的變換
gl_transform02

Object Coordinates
物件的本地座標系——任何變換之前的最初位置.為了變換(transformation)這些物件,在opengl es1.0,1.1中可以呼叫glRotate(),glTranslatef(),glScalef()這些方法。

Eye Coordinates
在opengl es1.0,1.1中:
使用GL_MODELVIEW矩陣和Object 座標相乘所得。在OpenGL中用GL_MODELVIEW將物件物件空間(Object Space)變換到視覺空間(eye space)。GL_MODELVIEW矩陣是模型矩陣(Model Matrix)和視覺矩陣(View Matrix)的組合 (Mview * Mmodel)。其中,Model 變換將Object Space轉換到World Space,View 變換是將World space變換到eye space
gl_transform07
法線向量(Normal vectors)——從物件座標系(Object coordinates)變換到視覺座標系(eye coordinates),它是用來計算光照(lighting calculation)的.注意法線向量(Normal vectors)的變換和頂點的不同。其中視覺矩陣(view matrix)是GL_MODELVIEW逆矩陣的轉置矩陣和法線向量(Normal vector)相乘所得,即:
gl_normaltransform01
剪切面座標系(Clip Coordinates)

視覺座標系和GL_PROJECTION矩陣相乘,得到剪切面座標系。GL_PROJECTION矩陣定義了可視的空間(截頭錐體)以及頂點資料如何投影到螢幕上(透視(perspective)或者正交化(orthogonal)),它被稱為剪切面座標系的原因是(x,y,z)變換之後要和±w比較

gl_transform08

標準化裝置座標系(NDC)
將剪切面座標系除以w所得,它被稱為透視除法(perspective division)
.它更像是視窗座標系,只是還沒有轉換或者縮小到螢幕畫素。其中它取值範圍在3個軸向從-1到1標準化了。
gl_transform12

視窗座標系(Window Coordinates)/螢幕座標系(Screen Coordinates)

將標準化裝置座標系(NDC)應用於視口轉換。NDC將縮小和平移以便適應螢幕的透視。視窗座標系最終傳遞給OpenGL的管道處理變成了fragment。glViewPort()函式
用來定義最終圖片對映的投影區域。同樣,glDepthRange()用來決定視窗座標系的z座標。視窗座標系由下面兩個方法給出的引數計算出來
glViewPort(x,y,w,h);
glDepthRange(n,f);
gl_transform13
視口轉換公式很簡單,通過NDC和視窗座標系的線性關係得到:
gl_transform14

transformsummary

transformtotal

附註
1 : […]好在用一個4×4矩陣就能表示這個投影:實際上,這句話並不對。透視變換不是仿射(affine)的,因此,透視投影無法完全由一個矩陣表示。向量與投影矩陣相乘之後,它齊次座標的每個分量都要除以自身的W(透視除法)。W分量恰好是-Z(投影矩陣會保證這一點)。這樣,離原點更遠的點,被除了較大的Z值;其X、Y座標變小,點與點之間變緊,物體看起來就小了,這才產生了透視效果。

【參考】:http://www.opengl-tutorial.org/zh-hans/beginners-tutorials-zh/tutorial-3-matrices-zh/