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主要是用于三维图形的绘制,接下来我们会将这些基础知识应用到三维图形的绘制。哎,明天又要上班了,又不能睡懒觉了,~~~~(>_<)~~~~