簡述
本文記錄我記錄我學習 坐標體系和矩陣轉換的過程,加深學習便于后續(xù)查詢,可能有些描述不夠準確,或者內(nèi)容不夠充實,還請多多指正,共同學習
矩陣變換
我們將物體坐標進行一系列變換,達到自己期望的位置,需要使用到矩陣.先說一下矩陣的公式.這里我是本著了解的心態(tài)去學習的,因為已經(jīng)有趁手的數(shù)學工具了,把重要的學完~ 我會再來研究這里的.
矩陣相乘
這是一個簡單的矩陣相乘例子,
這是矩陣乘法過程~
注意:
1, 矩陣相乘不遵守交換律 即 A * B ≠ B * A
2, 只有當左側矩陣列數(shù) 等于 右側矩陣行數(shù) 兩矩陣才能相乘.
在我們對坐標進行縮放,位移,旋轉 等變換時,我們多用4x4矩陣來進行~
縮放
我們把縮放變量表示為(S1,S2,S3)我們可以為任意向量(x,y,z)定義一個縮放矩陣:
位移
如果我們把位移向量表示為(Tx,Ty,Tz),我們就能把位移矩陣定義為:
旋轉
繞X軸旋轉
繞y軸旋轉
繞z軸旋轉
將旋轉分為繞3個軸進行旋轉,以達到自己希望的位置,見下面這個公式纯续,(Rx,Ry,Rz)代表任意旋轉軸:
這種處理方式,簡單容易理解,但是 會出現(xiàn)一個問題萬向節(jié)死鎖.
舉個栗子~ 加入在三維空間中有一個平行于X中的向量,然后將它繞Y軸旋轉至它平行于Z軸,這時繞z軸的任何旋轉都不會改變 向量的方向了. 在正常情況下,關于3個軸的旋轉過程應該是可以任意組合的,最終旋轉結果都是一致的,但是當出現(xiàn)了萬向節(jié)死鎖后, 就會導致各個軸旋轉順序 組合不同,而最終旋轉結果不同~ 大家可以找根筆試一試~也可以看看這里:歐拉角與萬向節(jié)死鎖(圖文版
那么如何解決呢~ 使用四元數(shù) 旋轉矩陣與四元數(shù) 因為復變函數(shù)早已還給老師~ 后續(xù)再研究補充~~
齊次坐標
在上面關于描述3D坐標的向量 是四維向量,多出一個分量w,w分量的用處是來創(chuàng)造3D視覺效果的,根據(jù)w分量的大小對物體進行拉伸,最后將w=1的截面進行展示,從而產(chǎn)生物體遠近效果. 這篇文章我覺得介紹的比較詳細 寫給大家看的“透視除法” —— 齊次坐標和投影
組合
我將不同變換的矩陣組合起來~ 將一個放大2倍的矩陣 和位移(1,2,3)的矩陣組合起來~,得到新的變換矩陣
將得到的變換矩陣 進行驗證
因為矩陣相乘是不遵守交換律的,所以在矩陣組合時,順序就十分重要, 建議 先縮放 --> 再旋轉 --> 再位移. 并且矩陣的順序是從右到左的 所以應該是
位移矩陣 * 旋轉矩陣 * 縮放矩陣 = 所需矩陣
補充
為什么我們要用矩陣來進行著一系列的變換呢? 據(jù)我了解 是因為這樣做,將 旋轉,位移,縮放加以統(tǒng)一.簡化計算流程,提高計算機的計算效率.
最后總結:像這樣利用矩陣進行位移,縮放,旋轉 這一系列的變換叫做:仿射變換
坐標體系
OpenGL 頂點著色器 希望接受的的頂點 都是標準化設備坐標(Normalized Device Coordinate, NDC)的坐標,也就是(x,y,z)都是在 -1~1之間變換.在此之外的頂點丟棄,并且按照傳入的頂點進行繪制.
將3D的物體坐標轉換到理想的繪制效果需要進行一些列的轉換過程.
局部空間(Local Space)/物體空間(Object Space) ---> 世界空間(World Space) ---> 觀察空間(View Space)/視覺空間(Eye Space) --->裁剪空間(Clip Space) ---> 屏幕空間(Screen Space)
OpenGL是不提供數(shù)學工具的~ 我們可以使用 GLM(OpenGL Mathematics) GLM官網(wǎng) 我用的是0.9.9版本~
示意圖:
局部空間
局部空間: 就表示物體在自己本身坐標系里的坐標,比較像view的bounds屬性.可以理解為建模時模型的坐標.
世界空間
世界空間: 表示物體需要展示世界里的坐標,比較像view的frame屬性,好比我們的模型是個房子,將它放到小鎮(zhèn)(世界)中, 這時它的坐標就是在世界空間的坐標.
將局部空間坐標轉換為世界空間坐標需要進行一系列轉換,就像在象棋棋盤上放棋子,我們需要將棋子旋轉,位移...操作才能將棋子放到正確的位置上.
觀察空間
在最終展示時,我們展示的是用戶觀察的界面, 我們需要將世界空間的坐標 轉換為 以用戶坐標觀察視野產(chǎn)生的結果.
裁剪空間
在OpenGL 中所期望的坐標是 標準化設備坐標, 所以我們 需要將自己的坐標集進行轉換,將需要顯示的坐標 落在 -1.0~1.0之間.
如果只是圖元(Primitive)随珠,例如三角形,的一部分超出了裁剪體積(Clipping Volume)猬错,則OpenGL會重新構建這個三角形為一個或多個三角形讓其能夠適合這個裁剪范圍,就像三角形的角被切了一刀變成四邊形那樣~
投影
在裁剪時,我們是將3D空間的物體 轉換為 2D空間的平面圖像, 這樣的過程叫做投影, 像投影截取的顯示的3D空間 平截頭體,它就像一個容器,在這里面的所有坐標都不會被裁剪掉~
正射投影
在裁剪空間階段,獲得平截頭體的方式, 就是通過 正射投影. 使用正射投影矩陣創(chuàng)天平截頭體 需要指定 近平面寬高, 遠平面寬高.
通過正射投影矩陣,將3D空間的坐標 映射到2D平面中,但是這樣產(chǎn)生的問題是 并沒有遠近縮放的效果,這時就需要 透視投影.
透視投影
透視投影,就是利用齊次坐標w 來生成遠近效果的, 離觀察者越遠的頂點 w分量越大.在顯示時,頂點坐標的每個分量都會除以w分量,進而使 遠端的物體小,近端的物體大.
透視矩陣 會根據(jù)平截頭體的頂點的遠近,對頂點w分量進行修改
組合
若上面每一個步驟都產(chǎn)生一個變換矩陣的話,那么最終的頂點坐標應該是這個樣子的
目標頂點 = 投影矩陣 * 觀察矩陣 * 模型矩陣 *原始頂點
標準化設備坐標
//此方法創(chuàng)建的窗口就是對應的OpenGL 的標準化設備窗口
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
模型矩陣
模型矩陣 包含了 位移,縮放,旋轉操作, 它將應用到物體的所有定點上. 該矩陣的目的就是將原來位于 世界空間(0,0,0)點的物體 移動到它應該出現(xiàn)的位置.
觀察矩陣
觀察矩陣就像是 3D世界里的攝像機,最終顯示的畫面 就是攝影機的位置 和 方向 觀察的畫面~ 觀察矩陣的創(chuàng)建就需要 GLM 提供的LooK AT 函數(shù):
tmat4x4<T, P> lookAt(tvec3<T, P> const & eye, tvec3<T, P> const & center, tvec3<T, P> const & up)
該函數(shù)需要輸入 3個 vec3變量,返回觀察矩陣:
參數(shù)1: 相機在世界坐標系的位置
參數(shù)2: 相機鏡頭指向的位置
參數(shù)3: 世界的上向量,上向量的方向 在顯示時 是指向屏幕上方的向量
通過對這三個變量控制,就可以實現(xiàn)我們希望的效果,我在練習時 實現(xiàn)的是類似 游戲CS里 攝像頭移動的方式
投影矩陣
glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);
此函數(shù)其實是創(chuàng)建了一個 定義了 可視控件的 平頭截體,此空間以外的東西都將被拋棄~
參數(shù):
第一個參數(shù)定義了fov值,它表示了視野(Field of View),就相當于攝影機的攝影角度~~
第二個參數(shù) 為寬高比, 由視口的寬/高所得
第三和第四個參數(shù) 設置了平截頭體的近和遠平面窗看。我們通常設置近距離為0.1f,而遠距離設為100.0f倦炒。所有在近平面和遠平面內(nèi)且處于平截頭體內(nèi)的頂點都會被渲染.
最后經(jīng)過透視矩陣的處理,就產(chǎn)生了 物體 遠小近大的效果了~
代碼
這里就撿與本文相關的說~
頂點著色器
#version 300 es
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
layout(location = 2) in vec2 texCoord; //紋理坐標
//uniform mat4 transform;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out vec3 outColor;
out vec2 outTexCoord;
void main()
{
gl_Position = projection*view*model*vec4(position,1.0);
outColor = color;
outTexCoord = texCoord;
}
開啟深度測試
OpenGL存儲它的所有深度信息于一個Z緩沖(Z-buffer)中显沈,也被稱為深度緩沖(Depth Buffer)。GLFW會自動為你生成這樣一個緩沖(就像它也有一個顏色緩沖來存儲輸出圖像的顏色)逢唤。深度值存儲在每個片段里面(作為片段的z值)拉讯,當片段想要輸出它的顏色時,OpenGL會將它的深度值和z緩沖進行比較智玻,如果當前的片段在其它片段之后遂唧,它將會被丟棄,否則將會覆蓋吊奢。這個過程稱為深度測試(Depth Testing)盖彭,它是由OpenGL自動完成的纹烹。
深度測試還有其他關于 自定以的函數(shù),以后再說~~
int wi,he;
//檢索有關綁定緩沖區(qū)的對象的信息 ,這里獲得了layer的寬高
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &wi);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &he);
glGenRenderbuffers(1, &depthBuf);
glBindRenderbuffer(GL_RENDERBUFFER, depthBuf);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, wi, he);
//還要記得開啟深度測試
glEnable(GL_DEPTH_TEST);
模型矩陣
glm::vec3 cubePositions[] = {
glm::vec3( 0.0f, 0.0f, 0.0f),
glm::vec3( 2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3( 2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3( 1.3f, -2.0f, -2.5f),
glm::vec3( 1.5f, 2.0f, -2.5f),
glm::vec3( 1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f)
};
for(unsigned int i = 0; i < 10; i++)
{
//模型矩陣
glm::mat4 model;
model = glm::translate(model, cubePositions[i]);
float angle = 20.0f * i;
model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
glUniformMatrix4fv(glGetUniformLocation(program, "model"), 1, GL_FALSE, glm::value_ptr(model));
glDrawArrays(GL_TRIANGLES, 0, 36);
}
這里的模型矩陣 將物體 進行位移,旋轉后 放在世界坐標系中合適的位置.
觀察矩陣
定義了 相機的初始位置 與 初始方向(這里講方向保持為單位向量)
glm::vec3 cameraLo = glm::vec3(10.0f,0.0f,0.0f);
glm::vec3 cameraDir = glm::vec3(1.0f,0.0f,0.0f);
.........其他代碼.........
glm::mat4 view;
view = glm::lookAt(cameraLo,cameraLo-cameraDir, glm::vec3(0.0, 1.0, 0.0));
//將鏡頭觀察點 保持為 鏡頭方向位置 向上方向 設置為y軸.
glUniformMatrix4fv(glGetUniformLocation(program, "view"), 1, GL_FALSE, glm::value_ptr(view));
在Display計時器中持續(xù)調(diào)用該方法
-(void)move{
if (_isAdvance) {
// 使攝像機 向朝向方向移動~~
cameraLo -=(cameraDir*(speed/24));
}
if (_isback) {
cameraLo +=(cameraDir*(speed/24));
}
//改變攝像機朝向
if (_isLeft) {
cameraDir = glm::normalize(glm::vec3(cameraDir[0],cameraDir[1],cameraDir[2]-rotateSpeed));
}
if (_isRight) {
cameraDir = glm::normalize(glm::vec3(cameraDir[0],cameraDir[1],cameraDir[2]+rotateSpeed));
}
if (_isUp) {
cameraDir = glm::normalize(glm::vec3(cameraDir[0],cameraDir[1]-rotateSpeed,cameraDir[2]));
}
if (_isDown) {
cameraDir = glm::normalize(glm::vec3(cameraDir[0],cameraDir[1]+rotateSpeed,cameraDir[2]));
}
[self render];
}
最終實現(xiàn)效果:
[圖片上傳失敗...(image-2d9806-1512195914588)]