學習書籍: OpenGL 超級寶典(中文第五版) 密碼:fu4w
書籍原始碼:OpenGL 超級寶典第五版原始碼 密碼:oyb4
環境搭建:OpenGL 學習 01 - Mac 搭建 OpenGL 環境
之前我們都只是畫平面圖形,這節我們來畫一個 3D 圖形,對比下平面圖形和 3D 圖形繪製的一些區別和注意點。
基本概念
花托
一種環狀的像麵包圈一樣的物體,如果要我們自己去一個個去計算花托的每個點,是要花很多時間,GLTools 裡面已經為我們整合好了得到花托頂點資料的函式
//得到花托的頂點資料
void gltMakeTorus(
GLTriangleBatch& torusBatch, // 返回的三角形批次,和普通批次功能類似,是專門畫三角形的批次
GLfloat majorRadius, // 中心到外邊緣的半徑
GLfloat minorRadius, // 外邊緣到內邊緣的距離
GLint numMajor, // 片段數,即花托由多少個片段面組成
GLint numMinor // 堆疊數,即每個片段面由多少個頂點組成
);
複製程式碼
油畫法渲染
在預設情況下,我們所渲染的點、線或三角形都會在螢幕上進行光柵化,並且會按照在組合批次時的指定順序進行排列,這樣就會出現一些問題,比如一些三角形在物體背面,我們應該是看不到的,或者前面繪製的三角形可能被後面繪製的三角形所覆蓋。
對於這些問題,可能的解決方法之一是,對這些三角形進行排序,先渲染較遠的三角形,再渲染較近的三角形,這種方式稱為“油畫法”。【油畫法渲染是非常低效的,重疊部分進行了多次寫操作,三角形排序開銷大】
正面和背面剔除
對正面和背面三角形進行區分的原因之一就是為了進行剔除。背面剔除能極大提高效能,且能修正一些上面提出的問題。
剔除是在渲染的圖元裝配階段就整體拋棄掉一些三角形,並且沒有執行任何不恰當的光柵化操作。
深度測試
深度測試是另外一種高效消除隱藏表面的技術。深度測試是在繪製每個畫素時,分配一個深度值z
,表示該點與觀察者的距離**【注意,這裡是深度值越大,表示離觀察者越近】**,當另外一個畫素需要繪製在同一個位置,會先比較深度值,深度值大的說明在前面,深度值小的說明在後面。
深度測試是通過深度緩衝區實現的,深度快取區儲存了螢幕上每個畫素的深度值。要使用深度測試,一般需要配置渲染模式:
/*初始化渲染模式*/
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
複製程式碼
深度測試還有一個很強大的作用,就是處理重疊的獨立物件,這是剔除技術無法處理的一種情況。深度測試是先渲染離觀察者較近的物件,再渲染較遠的物件,和“油畫法”剛好相反。
多邊形模式
預設情況下,多邊形是作為實心圖形進行繪製,我們可以通過調整多邊形模式,讓多邊形繪製為只有點或輪廓。此外,我們也可以在多邊形的兩面分別應用這個多邊形模式
多邊形模式:
- 實體(GL_FULL)
- 輪廓(GL_LINE)
- 點(GL_POINT)
多邊形模式應用面:
- 正面(GL_FRONT)
- 背面(GL_BACK)
- 雙面(GL_FRONT_AND_BACK)
原始碼解析
建立右鍵選單,便於切換各種模式
//建立選單,繫結一個響應方法
glutCreateMenu(ProcessMenu);
//為選單新增選項
glutAddMenuEntry("Toggle depth test", 1);
glutAddMenuEntry("Toggle cull backface", 2);
glutAddMenuEntry("Set Fill Mode", 3);
glutAddMenuEntry("Set Line Mode", 4);
glutAddMenuEntry("Set Point Mode", 5);
//右鍵彈出選單
glutAttachMenu(GLUT_RIGHT_BUTTON);
複製程式碼
物體 3D 旋轉,便於檢視各種模式優劣
//上、下、左、右按鍵,3D 旋轉
/*
* RotateWorld(float fAngle, float x, float y, float z)
* fAngle: 旋轉弧度, x/y/z:以哪個座標軸旋轉
* m3dDegToRad:角度 -> 弧度
* viewFrame:物體世界座標
*/
switch (key) {
case GLUT_KEY_UP:
viewFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
break;
case GLUT_KEY_DOWN:
viewFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
break;
case GLUT_KEY_LEFT:
viewFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
break;
case GLUT_KEY_RIGHT:
viewFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
break;
default:
break;
}
複製程式碼
開啟/關閉剔除
// 開啟剔除
glEnable(GL_CULL_FACE);
// 關閉剔除
glDisable(GL_CULL_FACE);
// 預設我們是剔除背面,也可以剔除正面,調以下函式進行控制
glCullFace(GL_FRONT); // 控制剔除的面為正面
glCullFace(GL_BACK); // 控制剔除的面為背面
glCullFace(GL_FRONT_AND_BACK); // 控制剔除的面為雙面
複製程式碼
開啟/關閉深度測試
// 開啟深度測試
glEnable(GL_DEPTH_TEST);
// 關閉深度測試
glDisable(GL_DEPTH_TEST);
複製程式碼
配置多邊形模式
//多邊形面模式為雙面實體
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
//多邊形面模式為雙面輪廓
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
//多邊形面模式為雙面點
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
複製程式碼
全部原始碼
#include <GLTools.h> // OpenGL toolkit
#include <GLMatrixStack.h>
#include <GLFrame.h>
#include <GLFrustum.h>
#include <GLGeometryTransform.h>
#include <math.h>
#include <glut/glut.h>
GLFrame viewFrame;
GLFrustum viewFrustum;
GLTriangleBatch torusBatch;
GLMatrixStack modelViewMatrix;
GLMatrixStack projectionMatrix;
GLGeometryTransform transformPipeline;
GLShaderManager shaderManager;
//是否開啟正反面剔除
int iCull = 0;
//是否開始深度測試
int iDepth = 0;
//點選選單選項
void ProcessMenu(int value) {
switch(value) {
case 1:
//開關深度測試
iDepth = !iDepth;
break;
case 2:
//開關正反面剔除
iCull = !iCull;
break;
case 3:
//開關多邊形面模式
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
break;
case 4:
//開關多邊形線模式
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
break;
case 5:
//開關多邊形點模式
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
break;
}
//觸發渲染
glutPostRedisplay();
}
//渲染畫面
void RenderScene(void) {
//清除一個或一組特定的緩衝區
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//判斷是否開啟正反面剔除
if(iCull)
glEnable(GL_CULL_FACE);
else
glDisable(GL_CULL_FACE);
//判斷是否開啟深度測試
if(iDepth)
glEnable(GL_DEPTH_TEST);
else
glDisable(GL_DEPTH_TEST);
//儲存當前的模型檢視矩陣
modelViewMatrix.PushMatrix(viewFrame);
//這裡使用預設光源進行著色,可以看到陰影
GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
//畫花托
torusBatch.Draw();
// 還原以前的模型檢視矩陣
modelViewMatrix.PopMatrix();
//將在後臺緩衝區進行渲染,然後在結束時交換到前臺
glutSwapBuffers();
}
//為程式作一次性的設定
void SetupRC() {
//設定視窗背景顏色
glClearColor(0.3f, 0.3f, 0.3f, 1.0f );
//初始化著色器管理器
shaderManager.InitializeStockShaders();
//設定變換管線以使用兩個矩陣堆疊
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
//移動物體的位置,值越大物體越遠,值越小物體越近
viewFrame.MoveForward(6.0f);
//建立一個花托批次,引數依次是:批次,外半徑,內半徑,片段數,堆疊數(在計算機中圓是正多邊形,頂點數越多越像真正的圓)
gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);
//設定預設點大小
glPointSize(4.0f);
}
//特殊按鍵(功能鍵或者方向鍵)監聽
void SpecialKeys(int key, int x, int y) {
//上、下、左、右按鍵,3D 旋轉
/*
* RotateWorld(float fAngle, float x, float y, float z)
* fAngle: 旋轉弧度, x/y/z:以哪個座標軸旋轉
* m3dDegToRad:角度 -> 弧度
*/
switch (key) {
case GLUT_KEY_UP:
viewFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
break;
case GLUT_KEY_DOWN:
viewFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
break;
case GLUT_KEY_LEFT:
viewFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
break;
case GLUT_KEY_RIGHT:
viewFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
break;
default:
break;
}
//觸發渲染
glutPostRedisplay();
}
//視窗大小改變時接受新的寬度和高度,其中0,0代表視窗中視口的左下角座標,w,h代表畫素
void ChangeSize(int width, int height) {
// 防止下面除法的除數為0導致的閃退
if(height == 0) height = 1;
//設定檢視視窗位置
glViewport(0, 0, width, height);
// 建立投影矩陣,並將它載入到投影矩陣堆疊中
viewFrustum.SetPerspective(35.0f, float(width) / float(height), 1.0f, 500.0f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
}
//程式入口
int main(int argc, char* argv[]) {
//設定當前工作目錄,針對MAC OS X
gltSetWorkingDirectory(argv[0]);
//初始化GLUT庫
glutInit(&argc, argv);
/*初始化渲染模式,其中標誌GLUT_DOUBLE、GLUT_RGBA、GLUT_DEPTH、GLUT_STENCIL分別指
雙緩衝視窗、RGBA顏色模式、深度測試、模板緩衝區*/
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
//初始化視窗大小
glutInitWindowSize(800, 600);
//建立視窗
glutCreateWindow("Geometry Test Program");
//註冊回撥函式
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
glutSpecialFunc(SpecialKeys);
//建立選單,繫結一個響應方法
glutCreateMenu(ProcessMenu);
//為選單新增選項
glutAddMenuEntry("Toggle depth test",1);
glutAddMenuEntry("Toggle cull backface",2);
glutAddMenuEntry("Set Fill Mode", 3);
glutAddMenuEntry("Set Line Mode", 4);
glutAddMenuEntry("Set Point Mode", 5);
//右鍵彈出選單
glutAttachMenu(GLUT_RIGHT_BUTTON);
//確保驅動程式的初始化中沒有出現任何問題。
GLenum err = glewInit();
if(GLEW_OK != err) {
fprintf(stderr, "glew error:%s\n", glewGetErrorString(err));
return 1;
}
//初始化設定
SetupRC();
//進入呼叫迴圈
glutMainLoop();
return 0;
}
複製程式碼
Demo 原始碼在這裡:github->openGLDemo->04-GeoTest
有什麼問題可以在下方評論區提出,寫得不好可以提出你的意見,我會合理採納的,O(∩_∩)O哈哈~,求關注求贊