學習書籍: OpenGL 超級寶典(中文第五版) 密碼:fu4w
書籍原始碼:OpenGL 超級寶典第五版原始碼 密碼:oyb4
環境搭建:OpenGL 學習 01 - Mac 搭建 OpenGL 環境
PS:因為以後 Demo 原始碼程式碼量會越來越多,不太可能全部複製在這裡了,我就解釋下 Demo 的核心原始碼,全部原始碼請前往我的 github/OpenGLDemo 下載,原始碼裡會有詳細註釋。
基本概念
三角形批次
GLTriangleBatch
就是專門為了繪製三角形的批次類,它將以更加高效的方式(索引頂點陣列)進行組織,並實際上將多邊形儲存在圖形卡(使用定點緩衝區物件)上,這裡只需要瞭解它的用法即可。
角色幀
在場景中移動的物體通常被稱為角色,為了方便我們計算角色在 3D 場景中進行移動、旋轉等操作的座標變換,每個角色都有自己的參考幀,即本地物件座標系,更簡潔的表示角色在空間中的座標和方向。
為了在 3D 場景中表示任何物件的位置和方向,GLTools 為我們實現了該參考幀的資料結構 GLFrame
(角色幀),包含了空間中的一個位置、一個指向前方的向量和一個指向上方的向量。
class GLFrame {
M3DVector3f vOrigin; // Where am I?
M3DVector3f vForward; // Where am I going?
M3DVector3f vUp; // Which way is up?
...
}
複製程式碼
可能會有疑問說,怎麼少了一個指向左邊或右邊的向量?因為這個指向左邊或右邊的向量可通過 vForward
和 vUp
向量叉乘得到。
有了角色幀的概念後,我們就知道照相機實際上就是一個角色幀,可以想象一下,一個人抬著照相機進行拍攝,它拍攝到的東西就是我們在視窗上看到的東西。
變換光線
對於幾何圖形變換來說,典型情況下我們會設定變換矩陣,將它們傳遞到著色器,然後讓硬體完成所有頂點的變換,但對於光源來說,會有一些不同,因為光源變換是獨立於幾何變換以外。
光源位置也需要轉化到視覺座標系,但傳遞到著色器的矩陣是幾何圖形,而不是光線。想象下,光源就像是一個燈光師,它總是跟隨著照相機的一起移動的。
得到光源矩陣:
// 先獲取到照相機矩陣
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
// 根據光源位置,得到光源矩陣
M3DVector4f vLightPos = { 0.0f, 10.0f, 5.0f, 1.0f };
M3DVector4f vLightEyePos;
m3dTransformVector4(vLightEyePos, vLightPos, mCamera);
複製程式碼
使用點光源著色器,傳入光源矩陣:
shaderManager.UseStockShader(
GLT_SHADER_POINT_LIGHT_DIFF,
transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(),
vLightEyePos,
vColor
);
複製程式碼
原始碼解析
更多物件(球、花托、圓柱、圓錐、圓盤)
09-Objects 核心原始碼如下,全部原始碼下載:09-Objects
// 初始化球體的三角形批次,後面引數依次是,球半徑,片段數,堆疊數
gltMakeSphere(sphereBatch, 3.0, 10, 20);
複製程式碼
// 初始化花托的三角形批次,後面引數依次是,外半徑,內半徑,片段數,堆疊數
gltMakeTorus(torusBatch, 3.0f, 0.75f, 150, 15);
複製程式碼
// 初始化圓柱的三角形批次,後面引數依次是,底部半徑,頂部半徑,高度,片段數,堆疊數
gltMakeCylinder(cylinderBatch, 2.0f, 2.0f, 3.0f, 13, 2);
複製程式碼
// 初始化圓錐的三角形批次,後面引數依次是,底部半徑,頂部半徑,高度,片段數,堆疊數
gltMakeCylinder(coneBatch, 2.0f, 0.0f, 3.0f, 13, 2);
複製程式碼
// 初始化圓盤的三角形批次,後面引數依次是,內半徑,外半徑,片段數,堆疊數
gltMakeDisk(diskBatch, 1.5f, 3.0f, 13, 3);
複製程式碼
球體世界 Lv1(繪製地板、旋轉的花托)
13-SphereWorld-Lv1 核心原始碼如下,全部原始碼下載:13-SphereWorld-Lv1
// 程式初始化
void SetupRC() {
// 設定視窗背景顏色為黑色
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// 初始化著色器
shaderManager.InitializeStockShaders();
// 開啟深度測試
glEnable(GL_DEPTH_TEST);
// 設定多邊形模式為前後面線段模式
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// 得到花托批次資料
gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
// 得到方格地板批次資料
floorBatch.Begin(GL_LINES, 324);
for(GLfloat x = -20.0; x <= 20.0f; x+= 0.5) {
floorBatch.Vertex3f(x, -0.55f, 20.0f);
floorBatch.Vertex3f(x, -0.55f, -20.0f);
floorBatch.Vertex3f(20.0f, -0.55f, x);
floorBatch.Vertex3f(-20.0f, -0.55f, x);
}
floorBatch.End();
}
// 視窗渲染回撥
void RenderScene(void) {
// 獲取2次渲染之間的時間間隔
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
// 清空緩衝區
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 壓棧,保持原始矩陣
modelViewMatrix.PushMatrix();
// 繪製地板
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vFloorColor);
floorBatch.Draw();
// 檢視矩陣進行平移、旋轉後進行繪製花托
modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vTorusColor);
torusBatch.Draw();
// 出棧,恢復原始矩陣
modelViewMatrix.PopMatrix();
// 因為是雙緩衝區模式,後臺緩衝區替換到前臺快取區進行顯示
glutSwapBuffers();
// 自動觸發渲染,達到動畫效果
glutPostRedisplay();
}
複製程式碼
球體世界 Lv2(加入角色移動和藍色小球旋轉)
14-SphereWorld-Lv2 核心原始碼如下,全部原始碼下載:14-SphereWorld-Lv2
// 程式初始化
void SetupRC() {
// 設定視窗背景顏色為黑色
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// 初始化著色器
shaderManager.InitializeStockShaders();
// 開啟深度測試
glEnable(GL_DEPTH_TEST);
// 設定多邊形模式為前後面線段模式
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// 得到花托批次資料
gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
// 得到旋轉小球批次資料【Lv2 增加的程式碼】
gltMakeSphere(sphereBatch, 0.1f, 26, 13);
// 得到方格地板批次資料
floorBatch.Begin(GL_LINES, 324);
for(GLfloat x = -20.0; x <= 20.0f; x+= 0.5) {
floorBatch.Vertex3f(x, -0.55f, 20.0f);
floorBatch.Vertex3f(x, -0.55f, -20.0f);
floorBatch.Vertex3f(20.0f, -0.55f, x);
floorBatch.Vertex3f(-20.0f, -0.55f, x);
}
floorBatch.End();
}
// 視窗渲染回撥
void RenderScene(void) {
// 獲取2次渲染之間的時間間隔
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
// 清空緩衝區
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 原來是沒法移動所以壓入原始矩陣,但現在角色可以移動通過角色幀獲取照相機矩陣,並壓入棧中【Lv2 修改的程式碼塊】
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.PushMatrix(mCamera);
// 繪製地板
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vFloorColor);
floorBatch.Draw();
// 檢視矩陣進行平移
modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);
// 保持旋轉前的檢視矩陣,因為如果不在旋轉前儲存,會導致旋轉小球也會帶上花托的旋轉【Lv2 增加的程式碼】
modelViewMatrix.PushMatrix();
// 繼續進行旋轉並進行繪製花托
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vTorusColor);
torusBatch.Draw();
// 恢復到旋轉前的檢視矩陣【Lv2 增加的程式碼】
modelViewMatrix.PopMatrix();
// 繪製旋轉小球【Lv2 增加的程式碼】
modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vSphereColor);
sphereBatch.Draw();
// 出棧,恢復成原始矩陣
modelViewMatrix.PopMatrix();
// 因為是雙緩衝區模式,後臺緩衝區替換到前臺快取區進行顯示
glutSwapBuffers();
// 自動觸發渲染,達到動畫效果
glutPostRedisplay();
}
// 特殊按鍵點選回撥【Lv2 增加的程式碼】
void SpecialKeys(int key, int x, int y) {
float linear = 0.1f;
float angular = float(m3dDegToRad(5.0f));
// 控制角色向前後移動
if(key == GLUT_KEY_UP)
cameraFrame.MoveForward(linear);
if(key == GLUT_KEY_DOWN)
cameraFrame.MoveForward(-linear);
// 控制角色向左右旋轉
if(key == GLUT_KEY_LEFT)
cameraFrame.RotateWorld(angular, 0.0f, 1.0f, 0.0f);
if(key == GLUT_KEY_RIGHT)
cameraFrame.RotateWorld(-angular, 0.0f, 1.0f, 0.0f);
}
複製程式碼
球體世界 Lv3(加入隨機小球分佈場景)
15-SphereWorld-Lv3 核心原始碼如下,全部原始碼下載:15-SphereWorld-Lv3
// 程式初始化
void SetupRC() {
// 設定視窗背景顏色為黑色
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// 初始化著色器
shaderManager.InitializeStockShaders();
// 開啟深度測試
glEnable(GL_DEPTH_TEST);
// 設定多邊形模式為前後面線段模式
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// 得到花托批次資料
gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
// 得到旋轉小球批次資料【Lv2 增加的程式碼】
gltMakeSphere(sphereBatch, 0.1f, 26, 13);
// 得到方格地板批次資料
floorBatch.Begin(GL_LINES, 324);
for(GLfloat x = -20.0; x <= 20.0f; x+= 0.5) {
floorBatch.Vertex3f(x, -0.55f, 20.0f);
floorBatch.Vertex3f(x, -0.55f, -20.0f);
floorBatch.Vertex3f(20.0f, -0.55f, x);
floorBatch.Vertex3f(-20.0f, -0.55f, x);
}
floorBatch.End();
// 隨機小球群位置資料生成【Lv3 增加的程式碼】
for(int i = 0; i < NUM_SPHERES; i++) {
GLfloat x = ((GLfloat)((rand() % 400) - 200) * 0.1f);
GLfloat z = ((GLfloat)((rand() % 400) - 200) * 0.1f);
spheres[i].SetOrigin(x, 0.0f, z);
}
}
// 視窗渲染回撥
void RenderScene(void) {
// 獲取2次渲染之間的時間間隔
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
// 清空緩衝區
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 原來是沒法移動所以壓入原始矩陣,但現在角色可以移動通過角色幀獲取照相機矩陣,並壓入棧中【Lv2 修改的程式碼塊】
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.PushMatrix(mCamera);
// 繪製地板
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vFloorColor);
floorBatch.Draw();
// 繪製隨機小球群,這裡的做法是瞬間切換當前角色位置並開始繪製小球,然後切換回原來角色位置【Lv3 增加的程式碼】
for(int i = 0; i < NUM_SPHERES; i++) {
// 儲存當前矩陣狀態,為了能切換回原來的角色位置
modelViewMatrix.PushMatrix();
// 調整當前角色位置為隨機生成小球的位置
modelViewMatrix.MultMatrix(spheres[I]);
// 繪製藍色小球
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vSphereColor);
sphereBatch.Draw();
// 還原矩陣狀態,切換為原來角色位置
modelViewMatrix.PopMatrix();
}
// 檢視矩陣進行平移
modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);
// 保持旋轉前的檢視矩陣,因為如果不在旋轉前儲存,會導致旋轉小球也會帶上花托的旋轉【Lv2 增加的程式碼】
modelViewMatrix.PushMatrix();
// 繼續進行旋轉並進行繪製花托
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vTorusColor);
torusBatch.Draw();
// 恢復到旋轉前的檢視矩陣【Lv2 增加的程式碼】
modelViewMatrix.PopMatrix();
// 繪製旋轉小球【Lv2 增加的程式碼】
modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vSphereColor);
sphereBatch.Draw();
// 出棧,恢復成原始矩陣
modelViewMatrix.PopMatrix();
// 因為是雙緩衝區模式,後臺緩衝區替換到前臺快取區進行顯示
glutSwapBuffers();
// 自動觸發渲染,達到動畫效果
glutPostRedisplay();
}
複製程式碼
球體世界 Lv4(加入點光源著色)
16-SphereWorld-Lv4 核心原始碼如下,全部原始碼下載:16-SphereWorld-Lv4
// 程式初始化
void SetupRC() {
// 設定視窗背景顏色為黑色
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// 初始化著色器
shaderManager.InitializeStockShaders();
// 開啟深度測試
glEnable(GL_DEPTH_TEST);
// 設定多邊形模式為前後面線段模式【Lv4 刪除的程式碼】
// glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// 得到花托批次資料
gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
// 得到旋轉小球批次資料【Lv2 增加的程式碼】
gltMakeSphere(sphereBatch, 0.1f, 26, 13);
// 得到方格地板批次資料
floorBatch.Begin(GL_LINES, 324);
for(GLfloat x = -20.0; x <= 20.0f; x+= 0.5) {
floorBatch.Vertex3f(x, -0.55f, 20.0f);
floorBatch.Vertex3f(x, -0.55f, -20.0f);
floorBatch.Vertex3f(20.0f, -0.55f, x);
floorBatch.Vertex3f(-20.0f, -0.55f, x);
}
floorBatch.End();
// 隨機小球群位置資料生成【Lv3 增加的程式碼】
for(int i = 0; i < NUM_SPHERES; i++) {
GLfloat x = ((GLfloat)((rand() % 400) - 200) * 0.1f);
GLfloat z = ((GLfloat)((rand() % 400) - 200) * 0.1f);
spheres[i].SetOrigin(x, 0.0f, z);
}
}
// 視窗渲染回撥
void RenderScene(void) {
// 獲取2次渲染之間的時間間隔
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
// 清空緩衝區
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 原來是沒法移動所以壓入原始矩陣,但現在角色可以移動通過角色幀獲取照相機矩陣,並壓入棧中【Lv2 修改的程式碼塊】
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.PushMatrix(mCamera);
// 得到點光源位置 【Lv4 增加的程式碼】
M3DVector4f vLightPos = { 0.0f, 10.0f, 5.0f, 1.0f };
M3DVector4f vLightEyePos;
m3dTransformVector4(vLightEyePos, vLightPos, mCamera);
// 繪製地板
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vFloorColor);
floorBatch.Draw();
// 繪製隨機小球群,這裡的做法是瞬間切換當前角色位置並開始繪製小球,然後切換回原來角色位置【Lv3 增加的程式碼】
for(int i = 0; i < NUM_SPHERES; i++) {
// 儲存當前矩陣狀態,為了能切換回原來的角色位置
modelViewMatrix.PushMatrix();
// 調整當前角色位置為隨機生成小球的位置
modelViewMatrix.MultMatrix(spheres[I]);
// 繪製藍色小球
// 設定著色器為點光源著色器【Lv4 調整的程式碼】
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,
transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(),
vLightEyePos,
vSphereColor);
sphereBatch.Draw();
// 還原矩陣狀態,切換為原來角色位置
modelViewMatrix.PopMatrix();
}
// 檢視矩陣進行平移
modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);
// 保持旋轉前的檢視矩陣,因為如果不在旋轉前儲存,會導致旋轉小球也會帶上花托的旋轉【Lv2 增加的程式碼】
modelViewMatrix.PushMatrix();
// 繼續進行旋轉並進行繪製花托
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
// 設定著色器為點光源著色器【Lv4 調整的程式碼】
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,
transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(),
vLightEyePos,
vTorusColor);
torusBatch.Draw();
// 恢復到旋轉前的檢視矩陣【Lv2 增加的程式碼】
modelViewMatrix.PopMatrix();
// 繪製旋轉小球【Lv2 增加的程式碼】
modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
// 設定著色器為點光源著色器【Lv4 調整的程式碼】
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,
transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(),
vLightEyePos,
vSphereColor);
sphereBatch.Draw();
// 出棧,恢復成原始矩陣
modelViewMatrix.PopMatrix();
// 因為是雙緩衝區模式,後臺緩衝區替換到前臺快取區進行顯示
glutSwapBuffers();
// 自動觸發渲染,達到動畫效果
glutPostRedisplay();
}
複製程式碼
上面的 Demo 原始碼全部都放在我的 github/OpenGLDemo 上,大家可以去下載和除錯。
有什麼問題可以在下方評論區提出,寫得不好可以提出你的意見,我會合理採納的,O(∩_∩)O哈哈~,求關注求贊