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主要是用於三維圖形的繪制,接下來我們會將這些基礎知識應用到三維圖形的繪制。哎,明天又要上班了,又不能睡懶覺了,~~~~(>_<)~~~~