OpenGL學習(七)-- 基礎變化綜合練習實踐總結
我的
OpenGL
專題學習目錄,希望和大家一起學習交流進步!
- OpenGL學習(一)-- 術語瞭解
- OpenGL學習(二)-- Xcode 搭建 OpenGL 環境
- OpenGL學習(三)-- OpenGL 基礎渲染
- OpenGL學習(四)-- 正面&背面剔除和深度測試
- OpenGL學習(五)-- 裁剪與混合
- OpenGL學習(六)-- 基礎紋理
- OpenGL學習(七)-- 基礎變化綜合練習實踐總結
- OpenGL學習(八)-- OpenGL ES 初探(上)
- OpenGL學習(九)-- OpenGL ES 初探(下)GLKit
- OpenGL學習(十)-- 著色語言 GLSL 語法介紹
- OpenGL學習(十一)-- 用 GLSL 實現載入圖片
- OpenGL學習(十二)-- OpenGL ES 紋理翻轉的策略對比
一、前言
以下我總結了一些最近學習 OpenGL 中常用的一些函式,添加了比較多的註釋,既是對自己學習的一個鞏固總結,也是防止以後遺忘可以快速檢視的記錄,同時希望也能幫助到更多在學習 OpenGL 的朋友們。
Xcode 還沒有搭建OpenGL環境的朋友可以參照我這篇文章去搭建,OpenGL學習(二)-- Xcode 搭建 OpenGL 環境。 另外還有之前的我總結的一些 OpenGL 基礎文章,可以從文章上方的目錄檢視。
二、程式碼+註釋總結
1、引入的標頭檔案:
#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
複製程式碼
2、一些常用全域性變數的宣告:
GLShaderManager shaderManager;// 固定管線管理器
GLMatrixStack modelViewMatrix;// 模型檢視矩陣堆疊
GLMatrixStack projectionMatrix;// 投影檢視矩陣堆疊
GLFrame cameraFrame;// 觀察者位置
GLFrame objectFrame;// 世界座標位置
GLFrustum viewFrustum;// 投影方式,學名:視景體。用來構造投影矩陣。
GLBatch triangleBatch;// 簡單的批次容器,是GLTools的一個簡單的容器類。
GLTriangleBatch CC_Triangle;// 三角形批次類
GLTriangleBatch sphereBatch;// 球
GLTriangleBatch torusBatch;// 環
GLTriangleBatch cylinderBatch;// 圓柱
GLTriangleBatch coneBatch;// 錐
GLTriangleBatch diskBatch;// 磁碟
GLGeometryTransform transformPipeline; // 變換管道,專門用來管理投影和模型矩陣的
GLfloat vGreen[] = {0.0f,1.0f,0.0f,1.0f };// 定義一個顏色值,綠色
複製程式碼
3、main 函式:程式入口中的設定
int main(int argc,char *argv[]) {
//設定當前工作目錄,針對MAC OS X
/*
`GLTools`函式`glSetWorkingDrectory`用來設定當前工作目錄。實際上在Windows中是不必要的,因為工作目錄預設就是與程式可執行執行程式相同的目錄。但是在Mac OS X中,這個程式將當前工作資料夾改為應用程式捆綁包中的`/Resource`資料夾。`GLUT`的優先設定自動進行了這個中設定,但是這樣中方法更加安全。
*/
gltSetWorkingDirectory(argv[0]);
//初始化GLUT庫,這個函式只是傳輸命令引數並且初始化glut庫
glutInit(&argc,argv);
/*
初始化雙緩衝視窗,其中標誌GLUT_DOUBLE、GLUT_RGBA、GLUT_DEPTH、GLUT_STENCIL分別指
雙緩衝視窗、RGBA顏色模式、深度測試、模板緩衝區
--GLUT_DOUBLE`:雙快取視窗,是指繪圖命令實際上是離屏快取區執行的,然後迅速轉換成視窗檢視,這種方式,經常用來生成動畫效果;
--GLUT_DEPTH`:標誌將一個深度快取區分配為顯示的一部分,因此我們能夠執行深度測試;
--GLUT_STENCIL`:確保我們也會有一個可用的模板快取區。
深度、模板測試後面會細緻講到
*/
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);
//GLUT視窗大小、視窗標題
glutInitWindowSize(800,600);
glutCreateWindow("Triangle");
/*
GLUT 內部執行一個本地訊息迴圈,攔截適當的訊息。然後呼叫我們不同時間註冊的回撥函式。我們一共註冊2個回撥函式:
1)為視窗改變大小而設定的一個回撥函式
2)包含OpenGL 渲染的回撥函式
*/
//註冊重塑函式
glutReshapeFunc(changeSize);
//註冊顯示函式
glutDisplayFunc(RenderScene);
glutSpecialFunc(SpeacialKeys);
/*
初始化一個GLEW庫,確保OpenGL API對程式完全可用。
在試圖做任何渲染之前,要檢查確定驅動程式的初始化過程中沒有任何問題
*/
GLenum status = glewInit();
if (GLEW_OK != status) {
printf("GLEW Error:%s\n",glewGetErrorString(status));
return 1;
}
//設定我們的渲染環境
setupRC();
glutMainLoop();
return 0;
}
複製程式碼
4、changeSize (int w,int h) 重塑函式:
重塑函式,為視窗改變大小而設定的一個回撥函式,視窗大小改變時,接收新的寬度&高度。 在 main 函式中使用
glutReshapeFunc
函式進行了註冊:glutReshapeFunc(changeSize);//註冊重塑函式
/*
在視窗大小改變時,接收新的寬度&高度。
*/
void changeSize(int w,int h) {
/*
1、設定視窗座標
x,y 引數代表視窗中檢視的左下角座標,而寬度、高度是畫素為表示,通常x,y 都是為0
*/
// 防止h變為0
if(h == 0){
h = 1;
}
glViewport(0,0,w,h);
// 2、
// 如果繪製的是立體圖形,還需要設定透視投影
// 透視投影
// 引數1:從頂點方向看去的視場角度(用角度值表示)
// 引數2:寬和高的縱橫比
// 引數3:fNear
// 引數4:fFar
viewFrustum.SetPerspective(35.0f,float(w) / float(h),500.0f);
// 3、
// 上面只是設定了但沒有用,我們需要把設定的轉化為矩陣,把結果帶回去。
// 往投影矩陣堆疊projectionMatrix里加載一個矩陣,從我們的視景體viewFrustum裡獲取投影矩陣GetProjectionMatrix()
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
// 4、
// 模型檢視矩陣是用來旋轉、平移等操作的,這裡並沒有變化,所以只是載入一個單元矩陣。
// 往模型檢視矩陣堆疊modelViewMatrix里加載一個單元矩陣
modelViewMatrix.LoadIdentity();
}
複製程式碼
5、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();
}
複製程式碼
6、SpecialKeys 函式
點選鍵盤的上下左右,讓世界座標系發生變化(並不是移動物體本身)
//上下左右,移動世界座標系
void SpecialKeys(int key,int y) {
if(key == GLUT_KEY_UP)
// 移動世界座標系,而不是去移動物體。
// 將世界座標系在X方向移動-5.0
objectFrame.RotateWorld(m3dDegToRad(-5.0f),0.0f);
if (key == GLUT_KEY_DOWN)
objectFrame.RotateWorld(m3dDegToRad(5.0f),0.0f);
if (key == GLUT_KEY_LEFT)
objectFrame.RotateWorld(m3dDegToRad(-5.0f),0.0f);
if (key == GLUT_KEY_RIGHT)
objectFrame.RotateWorld(m3dDegToRad(5.0f),0.0f);
glutPostRedisplay();
}
複製程式碼
7、SetupRC() 函式:
進行必要的初始化,設定我們的渲染環境,在 main
函式中呼叫。一般在這裡是去描述我們要渲染的圖形長什麼樣子。真正去繪製的時候不在這裡,而是在 RenderScene()
函式 中,後面會講解。
// 將上下文中,進行必要的初始化
void SetupRC() {
// 1、設定背景顏色
glClearColor(0.7f,0.7f,1.0f );
// 2、初始化固定著色器管理器
shaderManager.InitializeStockShaders();
// 3、繪製立體圖形的時候,需要開啟深度測試
glEnable(GL_DEPTH_TEST);
// 4、通過GLGeometryTransform管理矩陣堆疊
// 使用transformPipeline 管道管理模型檢視矩陣堆疊 和 投影矩陣堆疊(把兩個矩陣作為兩個引數放進去)
transformPipeline.SetMatrixStacks(modelViewMatrix,projectionMatrix);
// 5、為了讓效果明顯,將觀察者座標位置Z移動往螢幕裡移動15個單位位置
// 引數:表示離螢幕之間的距離。 負數,是往屏幕後面移動;正數,往螢幕前面移動
cameraFrame.MoveForward(-15.0f);
// ----------------------GLTriangleBatch型別--------------------------
// 利用“GLTriangleBatch”型別的三角形批次類構造圖形物件,GLTriangleBatch型別封裝了很多常用立體圖形。
// 1、球
/*
gltMakeSphere(GLTriangleBatch& sphereBatch,GLfloat fRadius,GLint iSlices,GLint iStacks);
引數1:sphereBatch,三角形批次類物件
引數2:fRadius,球體半徑
引數3:iSlices,從球體底部堆疊到頂部的三角形帶的數量;其實球體是一圈一圈三角形帶組成
引數4:iStacks,圍繞球體一圈排列的三角形對數
建議:一個對稱性較好的球體的片段數量是堆疊數量的2倍,就是iStacks = 2 * iSlices,引數4是引數3的2倍。
繪製球體都是圍繞Z軸,這樣+z就是球體的頂點,-z就是球體的底部。
*/
gltMakeSphere(sphereBatch,3.0,10,20);
// 2、環面
/*
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);
// 3、圓柱
/*
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);
// 4、錐
/*
void gltMakeCylinder(GLTriangleBatch& cylinderBatch,圓柱底部堆疊到頂部圓環的三角形數量
*/
//圓柱體,從0開始向Z軸正方向延伸。
//圓錐體,是一端的半徑為0,另一端半徑可指定。
gltMakeCylinder(coneBatch,13,2);
// 5、磁碟
/*
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);
// ------------------------GLBatch型別--------------------------
// 用上面GLTriangleBatch的程式碼的時候,把下面這段程式碼先註釋掉再執行。兩塊兒程式碼本來是分開的,為了方便寫在了一起。
//上面的是用的“GLTriangleBatch”型別的三角形批次類,下面我們使用最基礎的“GLBatch”型別的三角形批次類繪製金字塔。
// 首先建立三角形批次類,告訴它我們需要多少個頂點,
// 1.函式BeginMesh( GLuint nMaxVerts )// 指定有多少個頂點
CC_Triangle.BeginMesh(300);// 指定有300個頂點
// 2.建立一個頂點
M3DVector3f m[] = {
0.5,0.0,-0.5,0.5,0.0
};
// 3.將頂點複製進去,可以使用copy函式,也可以使用add函式新增
// 引數1:表示頂點座標值
// 引數2:表示法線座標值,沒有就寫NULL
// 引數3:表示紋理座標值,沒有就寫NULL
CC_Triangle.AddTriangle(m,NULL,NULL);
//CC_Triangle.CopyVertexData3f
CC_Triangle.End();
CC_Triangle.Draw();
// --------------------------------------------------------------
}
複製程式碼
8、RenderScene()顯示函式:
在發生任何變化,呼叫 glutPostRedisplay()
方法時,會呼叫 RenderScene()
這個函式。
在 main
函式中註冊了 RenderScene
顯示函式
glutDisplayFunc(RenderScene);
下面我先著重解釋一下這幾段程式碼:
- (1)壓棧 PushMatrix();
modelViewMatrix.PushMatrix();
這句程式碼的意思是壓棧,如果 PushMatix() 括號裡是空的,就代表是把棧頂的矩陣複製一份,再壓棧到它的頂部。如果不是空的,比如是括號裡是單元矩陣,那麼就代表壓入一個單元矩陣到棧頂了。
- (2)矩陣相乘 MultMatrix(mObjectFrame)
將 modelViewMatrix 的堆疊中的矩陣 與 mOjbectFrame 矩陣相乘,儲存到 modelViewMatrix 矩陣堆疊中。
modelViewMatrix.MultMatrix(mObjectFrame);
這句程式碼的意思是把 模型檢視矩陣堆疊 的 棧頂 的矩陣 copy 出一份來和新矩陣進行矩陣相乘,然後再將相乘的結果賦值給棧頂的矩陣。
- (3)出棧PopMatrix();
modelViewMatrix.PopMatrix();
把棧頂的矩陣出棧,恢復為原始的矩陣堆疊,這樣就不會影響後續的操作了。下面是我自己總結的一個《矩陣入棧、相乘、出棧》的流程圖:
- (4)cameraFrame.GetCameraMatrix(mCamera) :
cameraFrame.GetCameraMatrix(mCamera);
這裡和 OC 的語法有些不一樣,它的意思是從 cameraFrame 這個 觀察者座標系 中獲取矩陣,然後賦值給 mCamera。同理的還有獲取 世界座標位置 的矩陣objectFrame.GetMatrix(mObjectFrame);
。 cameraFrame 和 mCamera 都是GLFrame
型別的結構體。
接下來看一下 RenderScene() 程式碼:
//召喚場景
void RenderScene(void) {
//1.清除一個或者一組特定的快取區
/*
緩衝區是一塊存在影象資訊的儲存空間,紅色、綠色、藍色和alpha分量通常一起分量通常一起作為顏色快取區或畫素快取區引用。
OpenGL 中不止一種緩衝區(顏色快取區、深度快取區和模板快取區)
清除快取區對數值進行預置
引數:指定將要清除的快取的
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);
//模型檢視矩陣棧堆,壓棧
modelViewMatrix.PushMatrix();
//獲取攝像頭矩陣
M3DMatrix44f mCamera;
//從camereaFrame中獲取矩陣到mCamera
cameraFrame.GetCameraMatrix(mCamera);
//模型檢視堆疊的 矩陣與mCamera矩陣 相乘之後,儲存到modelViewMatrix矩陣堆疊中
modelViewMatrix.MultMatrix(mCamera);
//建立矩陣mObjectFrame
M3DMatrix44f mObjectFrame;
//從ObjectFrame 獲取矩陣到mOjectFrame中
objectFrame.GetMatrix(mObjectFrame);
//將modelViewMatrix 的堆疊中的矩陣 與 mOjbectFrame 矩陣相乘,儲存到modelViewMatrix矩陣堆疊中
modelViewMatrix.MultMatrix(mObjectFrame);
//使用平面著色器
//引數1:型別
//引數2:通過transformPipeline獲取模型檢視矩陣
//引數3:顏色
shaderManager.UseStockShader(GLT_SHADER_FLAT,transformPipeline.GetModelViewProjectionMatrix(),vBlack);
//判斷你目前是繪製第幾個圖形
// 注意這裡傳遞的是地址 &sphereBatch
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;
}
// 繪製完畢了,需要把棧頂的矩陣pop出去,不要影響我下一次繪圖
modelViewMatrix.PopMatrix();
// Flush drawing commands
glutSwapBuffers();
}
複製程式碼
9、DrawWireFramedBatch 函式:
void DrawWireFramedBatch(GLTriangleBatch* pBatch) {
// 平面著色器,繪製三角形
shaderManager.UseStockShader(GLT_SHADER_FLAT,vGreen);
// 傳過來的引數,對應不同的圖形Batch
pBatch->Draw();
// 畫出黑色輪廓
glPolygonOffset(-1.0f,-1.0f);
// 開啟線段圓滑處理
glEnable(GL_LINE_SMOOTH);
// 開啟混合功能
glEnable(GL_BLEND);
// 顏色混合
// 表示源顏色乘以自身的alpha 值,目標顏色乘以1.0減去源顏色的alpha值,這樣一來,源顏色的alpha值越大,則產生的新顏色中源顏色所佔比例就越大,而目標顏色所佔比例則減 小。這種情況下,我們可以簡單的將源顏色的alpha值理解為“不透明度”。這也是混合時最常用的方式。
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
// 通過程式點大小模式來設定點的大小
glEnable(GL_POLYGON_OFFSET_LINE);
// 多邊形模型(背面、線) 將多邊形背面設為線框模式
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
// 線條寬度
glLineWidth(2.5f);
// 平面著色器繪製線條
shaderManager.UseStockShader(GLT_SHADER_FLAT,vBlack);
pBatch->Draw();
// 恢復多邊形模式和深度測試
glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
glDisable(GL_POLYGON_OFFSET_LINE);
glLineWidth(1.0f);
glDisable(GL_BLEND);
glDisable(GL_LINE_SMOOTH);
}
複製程式碼
以上的總結參考了並部分摘抄了以下文章,非常感謝以下作者的分享!:
1、《OpenGL超級寶典 第5版》
2、《OpenGL程式設計指南(第八版)》
轉載請備註原文出處,不得用於商業傳播——凡幾多