本文僅僅是對我的在學習OpenGL的過程當中,用到的3D數學,向量和矩陣,在此作一個記錄。數組
在OpenGL的三維座標中,3個值(x,y,z)組合起來就表示2個重要的值:方向和數量-->向量。 緩存
M3DVector3f
能夠表示⼀個三 維向量(x,y,z),而
M3DVector4f
則能夠表示一個四維向量(x,y,z,w)。在典型狀況下,w 座標設爲1.0。x,y,z值經過除以w,來進行縮放。而除以1.0則本質上不改變x,y,z值。
typedef float M3DVector3f[3];
typedef float M3DVector4f[4];
聲明⼀個三份量向量操做:
M3DVector3f vVector;
相似,聲明⼀個四份量的操做:
M3DVector4f vVectro= {0.0f,0.0f,1.0f,1.0f};
聲明一個三份量頂點數組,例如⽣成⼀個三⻆形
M3DVector3f vVerts[] = {
-1.0f,0.0f,0.0f,
0.5f,0.0f,0.0f,
0.0f,1.0f,0.0f
};
複製代碼
點乘運算返回2個向量之間的夾角bash
//實現點乘方法:
//⽅法1: 返回的是-1,1之間的值。它表明這個2個向量量的餘弦值。
float m3dDotProduct3(const M3DVector3f u,const
M3DVector3f v);
//方法2:返回2個向量之間的弧度值。
float m3dGetAngleBetweenVector3(const M3DVector3f u,const M3DVector3f v);
複製代碼
叉乘運算結果返回一個新的向量,這個新的向量與原來的2個向量垂直函數
void m3dCrossProduct3(M3DVector3f result,const M3DVector3f u ,const M3DVector3f v);
複製代碼
先來看一下OpenGL中的變換:工具
變換 | 應用 | 描述說明 |
---|---|---|
視圖 | 制定觀察者或攝像機的位置 | 視圖變換容許咱們把觀察點放在所但願的任何位置,並容許在任何方向上觀察場景。肯定視圖變換就像在場景中放置照相機並讓它指向某個方向。 |
模型 | 在場景中移動物體 | 模型變換用於操縱模型和其中的特定對象。這些變換將對象移動到須要的位置,而後再對它們進行旋轉和縮放。 |
視圖模型 | 描述視圖和模型變換的二元性 | 其實沒必要經過移動攝像機(視圖變換)來觀察物體,而是移動這個物體(模型變換),這兩個方式都能實現一樣地效果,有時使用其中一種變換比使用另外一種變換要方便得多。這也是把視圖變換和模型變換都統一爲模型視圖矩陣的緣由。 |
投影 | 改變視景體的大小或從新設置它的形狀,將3維座標投影爲2維屏幕座標,位於視景體以外的東西將被裁剪掉 | 投影變換將在模型視圖變換以後應用到頂點上,它將指定一個完成的場景(全部模型變換都已完成)是如何投影到屏幕上的最終圖像。 正投影:全部多邊形都是精確地按照指定的相對大小來在屏幕上繪製的。 透視投影:透視投影的特色是透視縮短,這種特性使得遠處的物體看起來比進出一樣大小的物體更小一些。 |
視口 | 這是一種僞變換,只是對窗口上的最終輸出進行縮放 | 當全部變換完成後,就獲得了一個場景的二維投影,它將被映射到屏幕上某處的窗口上。這種到物理創口標的映射是咱們最後要作的變換,稱爲視口變換。 |
聲明矩陣oop
聲明一個三維矩陣:
typedef float M3DMatrix33f[9];
聲明一個四維矩陣:
typedef float M3DMatrix44f[16];
複製代碼
OpenGL並非將一個4X4矩陣表示爲一個浮點值的二維數組,而是將它表示爲一個由16個浮點值組成的單個數組。學習
模型視圖矩陣 模型視圖矩陣是一個4X4矩陣,它表示一個變換後的座標系,咱們能夠用來放置對象和肯定對象的方向。一個包含單個頂點數據的矩陣乘以模型視圖矩陣後會獲得新的視覺座標。咱們調用math3d庫中的m3dTranslationMatrix44函數來使用模型視圖變換矩陣。測試
1.平移
void m3dTranslationMatrix44(M3DMatrix44f m,float x,float y,float z);
2.旋轉
m3dRotationMatrix44(M3DMatrix44f m,float angle,float x,float y,float z);
3.縮放
void m3dScaleMatrix44(M3DMatrix44f m,float xScale,float yScale,float zScale);
4.綜合(math3d庫函數m3dMatrixMultiply44用來將兩個矩陣相乘並返回運算結果)
void m3dMatrixMultiply44(M3DMatrix44f product,const M3DMatrix44f a,const M3DMatrix44f b);
eg:
M3DMatrix44f mFinalTransform, mTranslationMatrix, mRotationMatrix;
//平移 xPos,yPos
m3dTranslationMatrix44(mTranslationMatrix, xPos, yPos, 0.0f);
// 每次重繪時,旋轉5度
static float yRot = 0.0f;
yRot += 5.0f;
m3dRotationMatrix44(mRotationMatrix, m3dDegToRad(yRot), 0.0f, 0.0f, 1.0f);
//將旋轉和移動的結果合併到mFinalTransform 中
m3dMatrixMultiply44(mFinalTransform, mTranslationMatrix, mRotationMatrix);
複製代碼
投影視圖矩陣ui
1.正投影
GLFrustum::SetOrthographic(GLfloat xMin,GLfloat xMax,GLfloat yMin,GLfloat yMax,GLfloat zMin,GLfloat zMax);
2.透視投影
GLFrustum::SetPerspective(float fFov,float fAspect,float fNear,float fFar);
// 參數1:從頂點方向看去的視場角度值
// 參數2:寬和高的縱橫比
// 參數3:近裁剪面的距離
// 參數4:遠裁剪面的距離
複製代碼
矩陣堆棧 GLMatrixStack類的構造函數容許指定堆棧的最大深度,默認的堆棧深度爲64.這個矩陣堆棧在初始化時已經在堆棧中包含了單位矩陣。(矩陣堆棧先進後出)spa
GLMatrixStack::GLMatrixStack(int iStackDepth=64);
複製代碼
矩陣堆棧的使用:
1.矩陣堆棧頂部載入單元矩陣
void GLMatrixStack::LoadIdentity(void);
2.矩陣堆棧頂部載入任何矩陣
void GLMatrixStack::LoadMatrix(const M3DMatrix44f m);
3.用一個矩陣乘以矩陣堆棧的頂部矩陣,相乘獲得的結果隨後將存儲在堆棧的頂部
void GLMatrixStack::MultMatrix(const M3DMatrix44f);
4.獲取矩陣堆棧頂部的值
const M3DMatrix44f& GLMatrixStack::GetMatrix(void);
void GLMatrixStack::GetMatrix(M3DMatrix44f mMatrix);
5.壓棧和出棧
void GLMatrixStack::PushMatrix(void);
void PushMatrix(const M3DMatrix44f mMatrix);
void PushMatrix(GLFrame& frame);
void GLMatrixStack::PopMatrix(void);
6.仿射變換
GLMatrixStack類也有對建立旋轉、平移和縮放矩陣的支持。
void MatrixStack::Rotate(GLfloat angle,GLfloat x,GLfloat y,GLfloat z);
void MatrixStack::Translate(GLfloat x,GLfloat y,GLfloat z);
void MatrixStack::Scale(GLfloat x,GLfloat y,GLfloat z);
複製代碼
咱們經過一份繪製多個圖形的代碼來看一下矩陣和矩陣堆棧的使用: 首先導入頭文件:
#include "GLTools.h" // OpenGL toolkit
#include "GLMatrixStack.h" //矩陣堆棧工具類
#include "GLFrame.h" //矩陣位置工具類
#include "GLFrustum.h" // 透視投影矩陣類
#include "GLBatch.h" //三角形批次類
#include "GLGeometryTransform.h" //變換管道
#include "StopWatch.h"
#include <math.h> // 數學類
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif
複製代碼
聲明全局變量:
GLShaderManager shaderManager; //存儲着色器管理
GLMatrixStack modelViewMatrix;//模型視圖矩陣堆棧
GLMatrixStack projectionMatrix;// 投影視圖矩陣堆棧
GLFrame cameraFrame;//觀察者位置
GLFrame objectFrame;//世界座標位置
GLFrustum viewFrustum;//視景體,用來構造投影矩陣
GLTriangleBatch CC_Triangle;//三角形批次類
//每一種圖形都須要一個單獨的三角形批次類:
GLTriangleBatch sphereBatch;//球
GLTriangleBatch torusBatch;//環
GLTriangleBatch cylinderBatch;//圓柱
GLTriangleBatch coneBatch;//錐
GLTriangleBatch diskBatch;//磁盤
GLGeometryTransform transformPipeline;//變換管道,用來管理模型,投影矩陣
M3DMatrix44f shadowMatrix;
GLfloat vGreen[] = { 0.0f, 1.0f, 0.0f, 1.0f }; //定義顏色:綠色
GLfloat vBlack[] = { 0.0f, 0.0f, 0.0f, 1.0f };//定義顏色:黑色
int nStep = 0; //定義第幾個圖形
複製代碼
程序入口main()函數:
int main(int argc, char* argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
//設置GLUT窗口大小、窗口標題
glutInitWindowSize(800, 600);
glutCreateWindow("Sphere");
//註冊重塑函數---設置視口
glutReshapeFunc(ChangeSize);
//註冊空格函數---切換圖形
glutKeyboardFunc(KeyPressFunc);
//註冊移動函數---上下左右移動圖形
glutSpecialFunc(SpecialKeys);
//註冊顯示函數
glutDisplayFunc(RenderScene);
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
//設置渲染環境
SetupRC();
glutMainLoop();
return 0;
}
複製代碼
重塑函數ChangeSize(): 當窗口第一次顯示或者在改變窗口的大小的時候會從新調用重塑函數,接收新的w,h值。
void ChangeSize(int w, int h)
{
//1.視口
if(h == 0){ // 防止h變爲0
h = 1;
}
glViewport(0, 0, w, h); //設置視口座標,大小,通常狀況下視口的座標都是窗口的左下角,故設置爲(0,0);
//2.設置透視投影
//建立透視投影矩陣
viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);
//projectionMatrix 投影視圖矩陣堆棧---加載透視投影矩陣
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//3.modelViewMatrix 模型視圖矩陣堆棧 ----加載一個單元矩陣
modelViewMatrix.LoadIdentity();
//4.經過GLGeometryTransform管理矩陣堆棧
//使用transformPipeline 管道管理模型視圖矩陣堆棧 和 投影視圖矩陣堆棧
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}
複製代碼
設置渲染環境SetupRC()函數:
// 將上下文中,進行必要的初始化
void SetupRC()
{
//1.設置背景顏色值
glClearColor(0.7f, 0.7f, 0.7f, 1.0f );
//2.初始化存儲着色器管理
shaderManager.InitializeStockShaders();
//3.開啓深度測試(不管繪製什麼圖形,都開啓深度測試)
glEnable(GL_DEPTH_TEST);
//4.將觀察者座標位置Z移動往屏幕裏移動15個單位位置
//表示離屏幕之間的距離。
//負數,是往屏幕後面移動
//正數,是往屏幕前面移動
cameraFrame.MoveForward(-15.0f);
//5.利用三角形批次類構造圖形對象
// 球
/*
gltMakeSphere(GLTriangleBatch& sphereBatch, GLfloat fRadius, GLint iSlices, GLint iStacks);
參數1:sphereBatch,三角形批次類對象
參數2:fRadius,球體半徑
參數3:iSlices,從球體底部堆疊到頂部的三角形帶的數量;其實球體是一圈一圈三角形帶組成
參數4:iStacks,圍繞球體一圈排列的三角形對數
建議:一個對稱較好的球體的片斷數量是堆疊數量的2倍,就是iStacks = 2 * iSlices;
繪製球體都是圍繞Z軸,這樣+z就是球體的頂點,-z就是球體的底部。
*/
gltMakeSphere(sphereBatch, 3.0, 10, 20);
// 環面
/*
gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
參數1:torusBatch,三角形批次類對象
參數2:majorRadius,甜甜圈中心到外邊緣的半徑
參數3:minorRadius,甜甜圈中心到內邊緣的半徑
參數4:numMajor,沿着主半徑的三角形數量
參數5:numMinor,沿着內部較小半徑的三角形數量
*/
gltMakeTorus(torusBatch, 3.0f, 0.75f, 15, 15);
// 圓柱
/*
void gltMakeCylinder(GLTriangleBatch& cylinderBatch, GLfloat baseRadius, GLfloat topRadius, GLfloat fLength, GLint numSlices, GLint numStacks);
參數1:cylinderBatch,三角形批次類對象
參數2:baseRadius,底部半徑
參數3:topRadius,頭部半徑
參數4:fLength,圓形長度
參數5:numSlices,圍繞Z軸的三角形對的數量
參數6:numStacks,圓柱底部堆疊到頂部圓環的三角形數量
*/
gltMakeCylinder(cylinderBatch, 2.0f, 2.0f, 3.0f, 15, 2);
//錐
/*
void gltMakeCylinder(GLTriangleBatch& cylinderBatch, GLfloat baseRadius, GLfloat topRadius, GLfloat fLength, GLint numSlices, GLint numStacks);
參數1:cylinderBatch,三角形批次類對象
參數2:baseRadius,底部半徑
參數3:topRadius,頭部半徑
參數4:fLength,圓形長度
參數5:numSlices,圍繞Z軸的三角形對的數量
參數6:numStacks,圓柱底部堆疊到頂部圓環的三角形數量
*/
//圓柱體,從0開始向Z軸正方向延伸。
//圓錐體,是一端的半徑爲0,另外一端半徑可指定。
gltMakeCylinder(coneBatch, 2.0f, 0.0f, 3.0f, 13, 2);
// 磁盤
/*
void gltMakeDisk(GLTriangleBatch& diskBatch, GLfloat innerRadius, GLfloat outerRadius, GLint nSlices, GLint nStacks);
參數1:diskBatch,三角形批次類對象
參數2:innerRadius,內圓半徑
參數3:outerRadius,外圓半徑
參數4:nSlices,圓盤圍繞Z軸的三角形對的數量
參數5:nStacks,圓盤外網到內圍的三角形數量
*/
gltMakeDisk(diskBatch, 1.5f, 3.0f, 13, 3);
}
複製代碼
顯示場景RenderScene()函數:
//召喚場景
void RenderScene(void)
{
//1.清除當前緩存,防止在渲染過程當中出現錯誤
//GL_COLOR_BUFFER_BIT :指當前激活的用來進行顏色寫入緩衝區
//GL_DEPTH_BUFFER_BIT :指深度緩存區
//GL_STENCIL_BUFFER_BIT:指模板緩衝區
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
//2.模型視圖矩陣棧堆,壓棧
modelViewMatrix.PushMatrix();
//3.建立攝像頭矩陣
M3DMatrix44f mCamera;
//從camereaFrame中獲取矩陣到mCamera
cameraFrame.GetCameraMatrix(mCamera);
//模型視圖堆棧的 矩陣與mCamera矩陣 相乘以後,存儲到modelViewMatrix矩陣堆棧中
modelViewMatrix.MultMatrix(mCamera);
//4.建立矩陣mObjectFrame
M3DMatrix44f mObjectFrame;
//從ObjectFrame 獲取矩陣到mOjectFrame中
objectFrame.GetMatrix(mObjectFrame);
//將modelViewMatrix 的堆棧中的矩陣 與 mOjbectFrame 矩陣相乘,存儲到modelViewMatrix矩陣堆棧中
modelViewMatrix.MultMatrix(mObjectFrame);
//5.判斷你目前是繪製第幾個圖形
switch(nStep) {
case 0:
DrawWireFramedBatch(&sphereBatch);
break;
case 1:
DrawWireFramedBatch(&torusBatch);
break;
case 2:
DrawWireFramedBatch(&cylinderBatch);
break;
case 3:
DrawWireFramedBatch(&coneBatch);
break;
case 4:
DrawWireFramedBatch(&diskBatch);
break;
}
//6. pop繪製完成以後,把棧頂矩陣pop出去,不要影響下一次的繪製。(還原到之前的模型視圖矩陣 (單位矩陣))
modelViewMatrix.PopMatrix();
//7. 進行緩衝區交換
glutSwapBuffers();
}
複製代碼
下面重點標記一下這段代碼:
- modelViewMatrix.PushMatrix(); 表示讓模型視圖壓棧,這裏的PushMatrix()括號中是空的,並非表示壓棧的內容是空的,而是把矩陣堆棧的棧頂元素複製一份進行壓棧,壓到棧頂。若是括號不是空的,就表明壓入一個矩陣到矩陣堆棧的棧頂。
![]()
- M3DMatrix44f mCamera; 聲明一個觀察者矩陣; cameraFrame.GetCameraMatrix(mCamera);意思是從 cameraFrame 這個觀察者座標系中獲取矩陣,而後賦值給 mCamera。
- modelViewMatrix.MultMatrix(mCamera);意思是把矩陣堆棧的棧頂矩陣和觀察者矩陣(mCamera)相乘,獲得的結果矩陣,從新存入到矩陣堆棧的棧頂(也就是替換掉剛剛矩陣堆棧的棧頂矩陣)。
- M3DMatrix44f mObjectFrame; 聲明一個物體世界座標位置的矩陣。 objectFrame.GetMatrix(mObjectFrame);意思是從 objectFrame 這個世界座標系中獲取矩陣,而後賦值給 mObjectFrame。
- modelViewMatrix.MultMatrix(mObjectFrame); 將modelViewMatrix 的堆棧中的棧頂矩陣複製一份與 mOjbectFrame矩陣相乘,獲得的結果矩陣存儲到modelViewMatrix矩陣堆棧中的棧頂。(此處注意:矩陣相乘並不知足交換律,因此在棧頂矩陣相乘的時候,並不能先是mObjectFrame,再是mCamera)
- modelViewMatrix.PopMatrix(); 意思是把棧頂的矩陣出棧,恢復爲原始的矩陣堆棧,纔不會影響後續繪製。
在這裏記錄一下: 在SetupRC()這個方法中,咱們將觀察者座標位置Z移動往屏幕裏移動15個單位位置,cameraFrame.MoveForward(-15.0f); 可是其實咱們也能夠不移動觀察者,讓物體向觀察者移動 objectFrame.MoveForward(15.0f); 此時在RenderScene()這個方法中,
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.MultMatrix(mCamera);
M3DMatrix44f mObjectFrame;
objectFrame.GetMatrix(mObjectFrame);
modelViewMatrix.MultMatrix(mObjectFrame);
複製代碼
這幾句代碼就能夠不須要添加,而後模型視圖矩陣堆棧的壓棧操做,modelViewMatrix.PushMatrix(); 壓入的就是objectFrame modelViewMatrix.PushMatrix(objectFrame); 爲何這樣作呢?是由於咱們在SetupRC()方法中,objectFrame記錄了物體向觀察者移動的狀態,同時在SpecialKeys()方法中,(objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);)objectFrame一樣記錄了隨着世界座標系移動的狀態,最後在渲染的時候(RenderScene()),modelViewMatrix.PushMatrix(objectFrame); 就是把移動的變化和旋轉的變化結合起來放在矩陣堆棧中。
下圖是,繪製了一個關於堆棧棧頂矩陣的理解流程圖。
繪製圖形函數DrawWireFramedBatch():
void DrawWireFramedBatch(GLTriangleBatch* pBatch)
{
//----繪製圖形----
//1.平面着色器,繪製三角形
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vGreen);
//參數1:着色器類型
//參數2:經過transformPipeline獲取模型視圖投影矩陣
//參數3:顏色
//傳過來的參數,對應不一樣的圖形Batch
pBatch->Draw();
//2.開啓多邊形偏移
glEnable(GL_POLYGON_OFFSET_LINE);
//多邊形模型(背面、線) 將多邊形背面設爲線框模式
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
//開啓多邊形偏移(設置偏移數量)
glPolygonOffset(-1.0f, -1.0f);
//線條寬度
glLineWidth(2.5f);
//3.開啓混合功能(顏色混合&抗鋸齒功能)
glEnable(GL_BLEND);
//開啓處理線段抗鋸齒功能
glEnable(GL_LINE_SMOOTH);
//設置顏色混合因子
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//4.平面着色器繪製線條
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
pBatch->Draw();
//5.恢復多邊形模式和深度測試
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDisable(GL_POLYGON_OFFSET_LINE);
glLineWidth(1.0f);
glDisable(GL_BLEND);
glDisable(GL_LINE_SMOOTH);
}
複製代碼
移動圖形函數SpecialKeys():
//點擊鍵盤的上下左右,來移動世界座標系從而移動圖形
void SpecialKeys(int key, int x, int y)
{
if(key == GLUT_KEY_UP)
//移動世界座標系,而不是去移動物體。
//將世界座標系在X方向移動-5.0
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
if(key == GLUT_KEY_DOWN)
objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
if(key == GLUT_KEY_LEFT)
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
if(key == GLUT_KEY_RIGHT)
objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
// 通知GLUT在進行一次一樣操做
glutPostRedisplay();
}
複製代碼
切換圖形函數KeyPressFunc():
//點擊鍵盤空格,切換渲染的圖形
void KeyPressFunc(unsigned char key, int x, int y)
{
if(key == 32)
{
nStep++;
if(nStep > 4)
nStep = 0;
}
switch(nStep)
{
case 0:
glutSetWindowTitle("Sphere");
break;
case 1:
glutSetWindowTitle("Torus");
break;
case 2:
glutSetWindowTitle("Cylinder");
break;
case 3:
glutSetWindowTitle("Cone");
break;
case 4:
glutSetWindowTitle("Disk");
break;
}
glutPostRedisplay();
}
複製代碼
而後來看一下代碼執行的圖形結果: