OpenGL學習之路(三)


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

標簽:  OpenGL


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM