1 引子
這些天公司一次次的軟件發布節點忙的博主不可開交,另外還有其它的一些事也占用了很多時間。現在坐在電腦前,在很安靜的環境下,與大家分享自己的OpenGL學習筆記和理解心得,感到格外舒服。這讓我回憶起了童年時期的一些情景,在群山環繞的農村,方圓不足一兩公里,當時感覺自己面對的世界好小,很想到去看看外面世界的精彩;上大學后就來到大城市了,至今算來也近十年了。現在看來外面的世界不見得比家鄉好:地鐵站、大街上,人們步履匆匆,爭分按秒;食品安全問題、生活買房壓力……讓我再次思考——大城市一定適合我嗎?現在越來越覺得安靜的小城市小鄉村的生活是多么的愜意。呵呵,發發感慨!也許這世界本不存在完美,大城市有大城市的好處和缺點,小城鎮有小城鎮的特色和不足;也許人生之路就是每走一步都要嘗試着去用心發現生活中的美好。
讀書筆記(二)中我們整理了坐標系的定義,然后着重推導了一下平移、旋轉、縮放三種基本的變換矩陣,所講內容比較抽象,但確實非常重要。這一次讀書筆記我們就應用這些矩陣來做幾個簡單的例子。整裝待發,讓我們再次踏上OpenGL的學習之旅吧!

2 着色器的編譯鏈接
着色器語言是一門高級語言,語法上和C系列語言很類似。既然是高級語言,那么用它寫的程序代碼就不能被機器直接執行,需要編譯、連接,最后生成可執行文件才能被CPU/GPU調度執行。着色器語言的編譯、連接過程與C語言十分類似,所以在介紹GLSL程序的鏈接接之前來看看C語言程序是怎么鏈接成一個可執行程序的。C語言的編譯與鏈接過程如下圖所示:

類似的,GLSL編譯過程如下:

咋看之下,似乎兩者相差很大,GLSL的編譯鏈接過程更為復雜。但其實兩者本質是一樣的,只不過C語言的編譯器如gcc為我們默默地做了很多幕后工作了——如讀入源程序、編譯、鏈接——一氣呵成。而GLSL語言的編譯則需要我們自己將這些步驟拼起來,而且這其中還要創建着色器對象和着色器程序對象,以便於OpenGL的管理。下面,我們就GLSL的編譯過程具體展開看看——一個着色器程序是怎么變成可以讓GPU調度執行的程序。
C語言中,會先把各個源文件編譯為目標文件,然后將各個目標文件鏈接為可執行文件。GLSL中則通過兩種對象——着色器對象和着色器程序對象——來分別處理編譯過程和連接過程。這里,我使用了一個類GPUProgram來對GLSL着色器程序的編譯、鏈接過程進行了封裝:類的使用者只需要創建對象,通過AddShader添加着色器類型和着色器源代碼文件名,然后執行CreateGPUProgram函數即可得到編譯鏈接之后的可執行程序。我們先看看這個對象的代碼,然后再深入分析涉及到的API。
GPUProgram.h文件聲明了類:
1 #pragma once
2
3 #include <map>
4 #include <string>
5
6 #ifndef GLEW_STATIC
7 #define GLEW_STATIC
8 #endif
9
10 #include "GL/glew.h"
11 #include "ResourceCommon.h"
12
13 #ifdef __cplusplus
14 extern "C" {
15 #endif // __cplusplus
16
17 /// \brief
18 class GPUProgram
19 {
20 public:
21 /// \brief constructor & destructor
22 RESOURCE_EXPORT GPUProgram();
23 RESOURCE_EXPORT ~GPUProgram();
24
25 void RESOURCE_EXPORT AddShader(GLenum SHADER_TYPE, const std::string& sFileName);
26
27 GLuint RESOURCE_EXPORT CreateGPUProgram();
28
29 private:
30 std::string _ReadShaderSourceCode(const std::string& sFileName) const;
31
32 void _RecordShaderLog(GLuint shader) const;
33
34 void _CompileShader(GLuint shader) const;
35
36 void _LineGPUProgram() const;
37
38 void _RecordProgramLog() const;
39
40 private:
41 std::map<GLenum, std::string> m_mapShaderFileName;
42
43 GLuint m_uiProgramID;
44 };
45
46 #ifdef __cplusplus
47 }
48 #endif // __cplusplus
GPUProgram.cpp是上述的實現:
1 #include "GPUProgram.h"
2
3 #include <fstream>
4 #include <iostream>
5 #include <sstream>
6 #include <memory>
7 #include "GameFramework/StdAfx.h"
8
9 #ifdef __cplusplus
10 extern "C" {
11 #endif // __cplusplus
12
13 GPUProgram::GPUProgram()
14 {
15
16 }
17
18 GPUProgram::~GPUProgram()
19 {
20 // -----------------刪除着色器程序對象-----------------
21 glDeleteProgram(m_uiProgramID);
22 }
23
24 void GPUProgram::AddShader(
25 GLenum SHADER_TYPE, const std::string& sFileName )
26 {
27 m_mapShaderFileName.insert(std::make_pair(SHADER_TYPE, sFileName));
28 }
29
30 GLuint GPUProgram::CreateGPUProgram()
31 {
32 if (GLEW_OK != glewInit())
33 {
34 std::cerr << "Unable to initialize GLEW... Exiting..." << std::endl;
35 std::exit(EXIT_FAILURE);
36 }
37
38 // -----------------創建着色器程序對象-----------------
39 m_uiProgramID = glCreateProgram();
40
41 for (auto aPair : m_mapShaderFileName)
42 {
43 // -----------------創建着色器對象-----------------
44 GLuint shader = glCreateShader(aPair.first);
45
46 // -----------------讀取着色器程序文件內容-----------------
47 std::string sShaderSrc = _ReadShaderSourceCode(aPair.second);
48
49 // -----------------關聯着色器對象和着色器源代碼-----------------
50 const GLchar *src = const_cast<GLchar *>(sShaderSrc.c_str());
51 glShaderSource(shader, 1, &src, NULL);
52
53 // -----------------編譯着色器源代碼-----------------
54 _CompileShader(shader);
55
56 // -----------------關聯着色器對象到着色器程序對象-----------------
57 glAttachShader(m_uiProgramID, shader);
58 }
59
60 // ----------------連接着色器程序對象-----------------
61 _LineGPUProgram();
62
63 return m_uiProgramID;
64 }
65
66 std::string GPUProgram::_ReadShaderSourceCode( const std::string& sFileName ) const
67 {
68 std::ifstream is;
69 is.open(sFileName);
70
71 std::stringstream ss;
72 ss << is.rdbuf();
73
74 return ss.str();
75 }
76
77 void GPUProgram::_RecordShaderLog(GLuint shader) const
78 {
79 // -----------------獲取Log長度----------------
80 GLsizei len;
81 glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
82
83 // -----------------獲取Log並打印----------------
84 GLchar* LOG = new GLchar[len+1];
85 glGetShaderInfoLog(shader, len, &len, LOG);
86 std::cerr << "Program link failed: " << LOG << std::endl;
87 delete [] LOG;
88 }
89
90 void GPUProgram::_CompileShader( GLuint shader ) const
91 {
92 glCompileShader(shader);
93
94 GLint compileSucceed;
95 glGetShaderiv(shader, GL_COMPILE_STATUS, &compileSucceed);
96 if (!compileSucceed)
97 {
98 _RecordShaderLog(shader);
99 throw std::exception("Failed to compile the shader!");
100 }
101 }
102
103 void GPUProgram::_LineGPUProgram() const
104 {
105 glLinkProgram(m_uiProgramID);
106
107 GLint linkSucceed;
108 glGetProgramiv(m_uiProgramID, GL_LINK_STATUS, &linkSucceed);
109 if (!linkSucceed)
110 {
111 _RecordProgramLog();
112 throw std::exception("Failed to link the GPUProgram!");
113 }
114 }
115
116 void GPUProgram::_RecordProgramLog() const
117 {
118 // -----------------獲取Log長度----------------
119 GLsizei len;
120 glGetProgramiv(m_uiProgramID, GL_INFO_LOG_LENGTH, &len);
121
122 // -----------------獲取Log並打印----------------
123 std::shared_ptr<GLchar> LOG(
124 new GLchar[len + 1], std::default_delete<GLchar>());
125 glGetProgramInfoLog(m_uiProgramID, len, &len, LOG.get());
126 std::cerr << "Program link failed: " << *LOG << std::endl;
127 }
128
129 #ifdef __cplusplus
130 }
131 #endif // __cplusplus
2.1 着色器對象的管理
OpenGL管理着一系列的對象,如之前我們接觸到的緩存對象、頂點數組對象,以及以后要接觸的紋理對象、采樣器對象、幀緩存對象等等,使用這些對象來管理我們的數據。我們在客戶端通過OpenGL的API創建這些對象,並設置其中的數據,然后在GPU端的着色器程序中實現對這些對象的訪問。對於每一個對象,都有與之對於的一套API,如創建glCreateXXX,銷毀glDeleteXXX,有一些對象還有綁定glBindXXX,以及其他一些和具體對象相關的API。着色器對象和這些對象一樣,不過它管理的數據是用GLSL語言寫的着色器源代碼。
首先看CreateGPUProgram函數,這個函數主要封裝了GLSL編譯鏈接的過程。第一個調用的OpenGL API便是glCreateProgram,顧名思義,這個借口就是創建一個着色器程序對象。創建完着色器程序對象后,遍歷一個map容器,在這個map容器中存放着着色器類型到着色器文件名的映射。
- 對每一個着色器類型,通過glCreateShader創建一個管理着色器源代碼的着色器對象,該函數的函數簽名為:
GLuint glCreateShader(GLenum type); type ——待創建的着色器對象類型 返回值 ——着色器對象的ID
- 對每一個着色器文件名,通過調用_ReadShaderSourceCode讀入文件中的着色器程序源代碼。
創建完對應類型的着色器對象並從文件讀取着色器的源代碼(注:着色器代碼也可以通過定義字符串的方式給出,見紅寶書<中文版>p50-51),接下來就是建立着色器對象和着色器源代碼之間的關系,通過glShaderSource實現,其函數簽名為:
void glShaderSource(GLuint shader, GLsizei count, const GLchar **string, const GLchar* length); shader ——標識着色器對象的ID(可理解為Handler) count ——着色器源代碼的數量 string ——着色器源代碼(可能由多個,根據count而定) length ——每個着色器源代碼的長度
一切准備工作都已經完畢——着色器對象已經有了,着色器源代碼也已經有了,並和着色器對象做了關聯,接下來就是把編譯着色器源代碼了,這就是glCompileShader的功能,其函數簽名為:
void glCompileShader(GLuint shader) shader ——標識着色器對象的ID
最后一步,就是將編譯好的着色器對象關聯到着色器程序對象,所使用 的OpenGL API函數簽名如下:
void glAttachShader(GLuint program, GLuint shader) program ——標識着色器程序對象的ID shader ——標識着色器對象的ID
2.2 着色器程序對象的管理
着色器程序對象相當於着色器對象的容器,和任何OpenGL管理的對象一樣,在使用着色器程序對象之前,需要通過glCreateProgram接口來創建出一個着色器程序對象。一般,在一次OpenGL繪制中,只能使用一個着色器程序對象。創建着色器程序對象的函數簽名為:
GLuint glCreateProgram() 返回值 ——標識着色器程序對象的ID
創建完着色器程序對象之后,可以通過剛才介紹的glAttachShader將編譯完的着色器對象與着色器程序對象關聯起來。關聯完成后,就可以將這些着色器對象鏈接成為一個可被GPU調度執行的二進制程序了。鏈接命令的函數全面如下:
void glLinkProgram(GLuint program) program ——標識着色器程序對象的ID
執行完上述命令后,我們得到了一個可執行的着色器程序,但一個OpenGL程序中可能有多個很多個可執行的着色器程序,那么這時我們就要告訴OpenGL,使用哪一個可執行的着色器程序,這就引出了下面的API:
void GLUseProgram(GLuint program) program ——標識着色器程序對象的ID
至此,OpenGL在執行渲染管線時就會調用相應的着色器程序了。寫到這里,我們可以知道,編譯好的着色器程序就像dll一樣,不能單獨運行,只能嵌入到OpenGL的渲染管線中管線中才能被執行。上面介紹的是在編譯鏈接過程中最重要的一些API,還有其它API都是比較簡單的,在此就不作詳細介紹了。
看到這里,大家也許有點疲憊了,休息一會,來看看靜謐的海灘、蔚藍的天空和美麗的椰子樹~~

3 着色器輸入之變換矩陣
3.1 存儲限制符與uniform變量的引入
課堂筆記(一)講述了OpenGL程序的基本結構——和大多數程序一樣,OpenGL程序一般由三部分組成(如下圖):輸入層、處理層和輸出層。其中輸入部分主要是將頂點數據、坐標變換矩陣(本節將要講述)以及以后要講述的紋理數據、光照數據輸入給OpenGL——巧婦難為無米之催,OpenGL也同樣需要數據或資源才能繪制圖像;處理層則主要是運行OpenGL運行渲染管線——這部分以前是固定的、不可編程的;現在引入了着色器程序之后就可編程了,也就是着色器程序的作用主要是對這些輸入進行處理,比如頂點變換、光照明暗處理、紋理讀取balabala……,也就是對輸入層進行加工;最后從幀緩存中取出的就是OpenGL給我們繪制出來的圖像了。這就是計算機繪圖,曾經覺得很神秘,現在也慢慢地懂了其中的一些流程,慢慢覺得這些也不過如此!^_^。

從上圖中可以看出:筆記(二)得到的變換矩陣是作為輸入層的一部分。在筆記(一)中,我們知道OpenGL為頂點數據提供了緩沖區對象(Buffer Object)和頂點數組屬性分別用來上傳頂點數據和描述頂點的數據(元數據)給GPU。那OpenGL為變換矩陣的輸入提供了什么工具呢?——Uniform變量。在講述uniform變量前,讓我們先來回顧一下變量的概念。
從大一接觸C語言開始,我們就學習了程序設計中變量的概念。所謂變量,其實是一段內存區域的刻畫——變量名、變量類型、變量初始值(數據)。變量在編程中的重要性,不言而喻:變量里保存的是數據,我們編程的目的不就是對這些數據進行處理嗎?所以,在任何一門編程語言中,變量(包括聲明、賦值、類型等)相關的知識點是學習編程語言的一個重點。
在C語言中變量定義時,碰到第一個單詞描述的就是變量的類型,如int a = 1。我們從變量類型開始,GLSL着色語言的變量類型和普通的C系列語言沒有太大差別,主要是多了兩個在計算機圖形學中使用十分廣泛的類型——向量vec和矩陣mat,下圖給出了GLSL定義的基本數據類型:
變量名和變量初始值這些和C系列語言是完全相同的,在這里就不再贅述了。
定義變量語句給出的是變量類型、變量名和變量值,這在GLSL中還不夠!變量在內存/顯存中是存儲在哪塊區域中的?從物理存儲器的角度看,內存只是一系列連續的存儲單元,通過地址對其訪問;從編程邏輯的角度看,操作系統或編譯系統對內存的管理其實是分了不同的邏輯區域的——棧區、堆區、全局數據區、常量區和代碼區。例如:全局變量存儲在全局數據區;局部變量及函數輸入輸出變量存儲在棧區;常量存儲在常量區。在C語言中,可以將全局變量定義在任何函數之外來表示這是一個全局變量;局部變量則定義在函數中;函數的輸入變量定義在函數的輸入參數列表中等等。那GLSL中,怎么描述一個變量是局部變量還是全局變量,怎么描述着色器的輸入變量還是輸出變量。這就引入了存儲限制符的概念——用來描述變量存儲區域和作用,存儲限制符主要用下面這些關鍵字描述:
const ——這應該是最簡單的存儲限制符了,它出現在變量聲明表達式中,意味着該變量一旦初始化之后,就不能改變了,如:const float PI = 3.1415926;
in ——在着色器程序中的主函數(main)中,是沒有輸入參數的,如何向着色器傳入外部數據呢?這時就要用到in存儲修飾符來表示那些變量是來自外部程序或其他着色器的輸入;
out ——和in存儲修飾符相反,out存儲修飾符用於標識着色器的輸出變量,或傳給下一階段的着色器,或傳給着色器外部程序;
uniform ——總算出來了,紅寶書上是這么寫的:uniform修飾符可以指定一個在應用中設置好的變量,它不會在圖元處理的過程中發生變化,且在所有的着色階段之間都是共享的——着色器中的全局變量。
buffer ——和uniform變量類似,不過它可以被修改。

所以,如果希望應用程序客戶端向GPU的着色器程序傳遞數據,可以使用uniform變量。關於uniform變量,這涉及到兩個知識點:應用程序端調用什么借口上傳數據?着色器中應該怎么定義uniform變量?下面通過一段程序來看看uniform變量的使用。
3.2 Uniform變量的使用
下面的程序繪制的是一個正方形輪廓,代碼和讀書筆記(一)中的程序差不多,主要是增加了一個變換矩陣,並通過Uniform變量上傳至GPU中,使着色器程序能訪問到該矩陣變量;另外,在讀書筆記(一)中我們使用的是紅寶書給出的方法來加載着色器,這次讀書筆記中使用的是剛才在第二小節中給出的GPUProgram對象對着色器程序的加載、編譯與鏈接。程序代碼如下:
1 #include <iostream>
2
3 #include "AlgebraicEntity/Matrix.h"
4
5 #include "GameFramework/StdAfx.h"
6 #include "Resource/GPUProgram.h"
7
8 GLint location;
9 GPUProgram gpuProgram;
10
11 void initialize_04()
12 {
13 // ----------------准備頂點數據----------------
14 GLfloat vertices_array[4][4] =
15 {
16 { -0.5, 0.5, 0.0, 1.0 },
17 { 0.5, 0.5, 0.0, 1.0 },
18 { 0.5, -0.5, 0.0, 1.0 },
19 { -0.5, -0.5, 0.0, 1.0 },
20 };
21
22 // ----------------建立緩存對象加載數據----------------
23 GLuint Buffer_ID;
24 glGenBuffers(1, &Buffer_ID);
25 glBindBuffer(GL_ARRAY_BUFFER, Buffer_ID);
26 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_array), vertices_array, GL_STATIC_DRAW);
27
28 // ----------------建立頂點數組對象----------------
29 GLuint VAO_ID;
30 glGenVertexArrays(1, &VAO_ID);
31 glBindVertexArray(VAO_ID);
32 glVertexAttribPointer(0, 4, GL_FLOAT, false, 0, BUFFER_OFFSET(0));
33 glEnableVertexAttribArray(0);
34
35 // ----------------創建着色器程序對象----------------
36 gpuProgram.AddShader(GL_VERTEX_SHADER, "F:/VC++游戲編程/OpenGLGuide/OpenGL04/square.vert");
37 gpuProgram.AddShader(GL_FRAGMENT_SHADER, "F:/VC++游戲編程/OpenGLGuide/OpenGL04/square.frag");
38 GLuint program = gpuProgram.CreateGPUProgram();
39 glUseProgram(program);
40
41 location = glGetUniformLocation(program, "mat_simple_transform");
42 }
43
44 void display_04()
45 {
46 // ----------------創建縮放矩陣----------------
47 Matrix4X4 mat = Matrix4X4::CreateRotateMatrix(PI / 4, Vector3D(0.0, 0.0, 1.0));
48
49 // ----------------通過uniform上傳至GPU----------------
50 glUniformMatrix4fv(location, 1, GL_FALSE, mat._m);
51
52 // ----------------清空顏色緩存----------------
53 glClear(GL_COLOR_BUFFER_BIT);
54
55 // ----------------繪制正方形圖像----------------
56 glDrawArrays(GL_LINE_LOOP, 0, 4);
57
58 // ----------------同步執行結束----------------
59 glFlush();
60 }
61
62 int main(int argc, char **argv)
63 {
64 try
65 {
66 glutInit(&argc, argv);
67 glutInitDisplayMode(GLUT_RGBA);
68 glutInitWindowSize(512, 512);
69 glutInitContextVersion(3, 3);
70 glutInitContextProfile(GLUT_CORE_PROFILE);
71 glutCreateWindow(argv[0]);
72
73 glewExperimental = GL_TRUE;
74 if (GLEW_OK != glewInit())
75 {
76 std::cerr << "Unable to initialize GLEW... Exiting..." << std::endl;
77 std::exit(EXIT_FAILURE);
78 }
79
80 initialize_04();
81 glutDisplayFunc(display_04);
82 glutMainLoop();
83
84 return 0;
85 }
86 catch (const std::exception& ke)
87 {
88 std::cerr << "Error happened because of " << ke.what();
89 return -1;
90 }
91 }
運行效果就是繪制了一個正方形的輪廓,如下圖所示:

效果確實不怎么樣^_^,但是可以闡述知識點和OpenGL API。可以看出,圖中的正方形是旋轉得到的。旋轉矩陣通過下面這個我們自己寫的接口CreateRotateMatrix獲得(該矩陣的定義已在筆記(二)中做了介紹,在此不再贅述了),該函數的函數簽名如下:
static Matrix4X4 CreateRotateMatrix(double dAngle, const Vector3D& rotateAxis); dAngle ——旋轉的角度 rotateAxis ——旋轉軸向量
緊接着就是通過glUniformMatrix4fv的方式將上述旋轉矩陣上傳至服務(GPU)端。上傳之前,就像我們上傳文件到服務器一樣,需要知道上傳的位置,所以在調用此函數前,會先調用glGetUniformLocation函數獲得Uniform變量的索引值(見程序:41行)。事實上,GLSL編譯器在鏈接着色器程序時,會創建一個uniform變量列表,在設置unifrom前所獲取的索引便是在該列表中的索引。glGetUniformLocation函數簽名如下:
GLint glGetUniformLocation(GLuint program, const char* name); program ——鏈接后的GLSL程序標識ID name ——uniform在着色器程序代碼中的變量名 返回值 ——uniform變量在uniform變量列表中的索引值
上述函數的返回值和輸入參數program都比較簡單,但name(變量名)的設置可以比較靈活——可以是單一的變量名稱,也可以是結構體中成員變量(域)的名稱(通過.操作符訪問),或者是數組中的某一條目(通過[]索引訪問),下面是幾個較為復雜的實例:
着色器中的聲明如下:
1 uniform struct
2 {
3 struct
4 {
5 float a;
6 float b[10];
7 } c[2];
8 vec2 d;
9 } e;
應用程序端獲取索引值的函數調用為:
1 GLint loc1 = glGetUniformLoaction(program, "e.d"); // ok 2 GLint loc1 = glGetUniformLoaction(program, "e.c[0]"); // error 3 GLint loc1 = glGetUniformLoaction(program, "e.c[0].b"); // ok, 數組第一個元素的索引 4 GLint loc1 = glGetUniformLoaction(program, "e.c[0].b[2]"); // ok
確定好存放位置之后,就可以通過glUniformMatrix4fv接口向GPU端上傳數據了,其函數簽名為:
void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* values) location ——上傳數據在uniform列表中索引值 count ——上傳的矩陣個數 transpose ——行主序還是列主序 value ——指向矩陣數據的指針
上傳了數據之后,就可以在着色器程序中對unifrom變量進行訪問了,例如,頂點着色器中訪問旋轉矩陣的代碼如下:
#version 330 core
uniform mat4 mat_simple_transform;
layout(location = 0) in vec4 vPosition;
void main()
{
gl_Position = mat_simple_transform * vPosition;
}
實踐提示:① 一定要保證着色器中的uniform變量類型一定要和應用程序上傳的數據類型一致。例如在我們的着色器程序中,變換矩陣元素的類型是單精度浮點型,那上傳的數據也必須是單精度浮點型的。我在寫這段程序的時候,就遇到了一個錯誤:着色器中使用的是mat4,也就是矩陣元素單精度浮點型,但上傳的數據時使用了double類型(雙精度浮點型),而且這樣的錯誤還很難發現——編譯器並沒有提示,只是圖都不見了;② 另外GLSL支持的隱式類型轉換更少,更要保持類型的一致性。例如:如果矩陣是mat4類型的,vPosition必須是vec4類型的,那么它們之間是不能運算的,會有編譯錯誤。
4 變換矩陣的綜合使用
這一節主要對筆記(二)定義的坐標變換矩陣的綜合運用,給出幾個基於目前所了解的知識點就能實現的繪制實例,也算是對前兩篇讀書筆記的溫習吧!這里的例子,頂點數據就是上述例子中的數據——繪制的都是正方形輪廓,只是通過不同的display函數實現不同的展示效果。
4.1 縮放
將繪制函數修改為如下函數:
1 void display_04_scale()
2 {
3 // ----------------清空顏色緩存----------------
4 glClear(GL_COLOR_BUFFER_BIT);
5
6 for (int i = 1; i <= 10; i++)
7 {
8 // ----------------創建縮放矩陣----------------
9 Matrix4X4 mat = Matrix4X4::CreateScaleMatrix(i * 0.1);
10
11 // ----------------通過uniform上傳至GPU----------------
12 glUniformMatrix4fv(location, 1, GL_FALSE, mat._m);
13
14 // ----------------繪制正方形圖像----------------
15 glDrawArrays(GL_LINE_LOOP, 0, 4);
16 }
17 }
可以得到下面的顯示效果:

4.2 平移
將繪制函數改為如下函數:
1 void display_04_translate()
2 {
3 // ----------------清空顏色緩存----------------
4 glClear(GL_COLOR_BUFFER_BIT);
5
6 for (int i =-4; i <= 4; i++)
7 {
8 // ----------------創建縮放矩陣----------------
9 Matrix4X4 mat = Matrix4X4::CreateTranslateMatrix(
10 Vector3D(i * 0.1, i * 0.1, 0.0));
11
12 // ----------------通過uniform上傳至GPU----------------
13 glUniformMatrix4fv(location, 1, GL_TRUE, mat._m);
14
15 // ----------------繪制正方形圖像----------------
16 glDrawArrays(GL_LINE_LOOP, 0, 4);
17 }
18 }
可以得到下述效果:

4.3 綜合效果
下面代碼演示的是各種不同坐標變換矩陣相乘得到的變換矩陣,對圖形進行變換,然后進行繪制:
1 void display_04_combine()
2 {
3 // ----------------清空顏色緩存----------------
4 glClear(GL_COLOR_BUFFER_BIT);
5
6 // 上
7 {
8 Matrix4X4 mat = Matrix4X4::CreateTranslateMatrix(Vector3D(0.0, 0.5, 0.0));
9 mat = mat * Matrix4X4::CreateScaleMatrix(0.3);
10 mat = mat * Matrix4X4::CreateRotateMatrix(0, Vector3D(0.0, 0.0, 1.0));
11 glUniformMatrix4fv(location, 1, GL_TRUE, mat._m);
12 glDrawArrays(GL_LINE_LOOP, 0, 4);
13 }
14
15 // 右
16 {
17 Matrix4X4 mat = Matrix4X4::CreateTranslateMatrix(Vector3D(0.5, 0.0, 0.0));
18 mat = mat * Matrix4X4::CreateScaleMatrix(0.4);
19 mat = mat * Matrix4X4::CreateRotateMatrix(PI / 12, Vector3D(0.0, 0.0, 1.0));
20 glUniformMatrix4fv(location, 1, GL_TRUE, mat._m);
21 glDrawArrays(GL_LINE_LOOP, 0, 4);
22 }
23
24 // 下
25 {
26 Matrix4X4 mat = Matrix4X4::CreateTranslateMatrix(Vector3D(0.0, -0.5, 0.0));
27 mat = mat * Matrix4X4::CreateScaleMatrix(0.5);
28 mat = mat * Matrix4X4::CreateRotateMatrix(PI / 6, Vector3D(0.0, 0.0, 1.0));
29 glUniformMatrix4fv(location, 1, GL_TRUE, mat._m);
30 glDrawArrays(GL_LINE_LOOP, 0, 4);
31 }
32
33 // 左
34 {
35 Matrix4X4 mat = Matrix4X4::CreateTranslateMatrix(Vector3D(-0.5, 0, 0.0));
36 mat = mat * Matrix4X4::CreateScaleMatrix(0.6);
37 mat = mat * Matrix4X4::CreateRotateMatrix(PI / 4, Vector3D(0.0, 0.0, 1.0));
38 glUniformMatrix4fv(location, 1, GL_TRUE, mat._m);
39 glDrawArrays(GL_LINE_LOOP, 0, 4);
40 }
41 }
效果圖:

5 總結
這一次讀書筆記主要是對上次讀書筆記的回顧與應用,同時講述了GLSL着色器程序的編譯以及如何通過uniform變量向着色器程序傳遞數據。這次讀書筆記內容雖然較多,但相對前一次讀書筆記來說,要簡單一些。到目前為止,我們還是在二維領域摸索,但OpenGL主要是用於三維圖形的繪制,接下來我們會將這些基礎知識應用到三維圖形的繪制。哎,明天又要上班了,又不能睡懶覺了,~~~~(>_<)~~~~
