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

OpenGL学习之路(三)的更多相关文章

  1. OpenGL学习之路(四)

    1 引子 上次读书笔记主要是学习了应用三维坐标变换矩阵对二维的图形进行变换,并附带介绍了GLSL语言的编译.链接相关的知识,之后介绍了GLSL中变量的修饰符,着重介绍了uniform修饰符,来向着色器 ...

  2. OpenGL学习之路(五)

    1 引子 不知不觉我们已经进入到读书笔记(五)了,我们先对前四次读书笔记做一个总结.前四次读书笔记主要是学习了如何使用OpenGL来绘制几何图形(包括二维几何体和三维几何体),并学习了平移.旋转.缩放 ...

  3. 学习之路三十九:新手学习 - Windows API

    来到了新公司,一开始就要做个程序去获取另外一个程序里的数据,哇,挑战性很大. 经过两周的学习,终于搞定,主要还是对Windows API有了更多的了解. 文中所有的消息常量,API,结构体都整理出来了 ...

  4. OpenGL学习之路(一)

    1 引子 虽然是计算机科班出身,但从小对几何方面的东西就不太感冒,空间想象能力也较差,所以从本科到研究生,基本没接触过<计算机图形学>.为什么说基本没学过呢?因为好奇(尤其是惊叹于三维游戏 ...

  5. Redis——学习之路三(初识redis config配置)

    我们先看看config 默认情况下系统是怎么配置的.在命令行中输入 config get *(如图) 默认情况下有61配置信息,每一个命令占两行,第一行为配置名称信息,第二行为配置的具体信息.     ...

  6. OpenGL学习之路(二)

    1 引子 在上一篇读书笔记中,我们对书本中给出的例子进行详细的分析.首先是搭出一个框架:然后填充初始化函数,在初始化函数中向OpenGL提供顶点信息(缓冲区对象)和顶点属性信息(顶点数组对象),并启用 ...

  7. zigbee学习之路(三):按键的控制

    一.前言 通过前一次的实验,相信大家都已经对cc2530程序的编写有了一定的认识,这次我们来操作和实验的是cc2530上的按键模块. 二.原理分析 我们先来看一下按键的原理图: 根据原理图我们可以得出 ...

  8. OPENGL学习之路(0)--安装

    此次实验目的: 安装并且配置环境. 1 下载 https://www.opengl.org/ https://www.opengl.org/wiki/Getting_Started#Downloadi ...

  9. 学习之路三十二:VS调试的简单技巧

    这段时间园子里讲了一些关于VS的快捷键以及一些配置技巧,挺好的,大家一起学习,一起进步. 这段时间重点看了一下关于VS调试技巧方面的书,在此记录一下学习的内容吧,主要还是一些比较浅显的知识. 1. 调 ...

随机推荐

  1. SIOCADDRT: No such process

    配置Ubuntu靶机遇到的问题 如果你添加/修改默认网关时遇到这个问题. 原因:你要添加的网关不在你主机所在的网段. 解决方法: 比如你要添加的网关是10.57.50.1 sudo route add ...

  2. Log4net学习

    转自:http://www.cnblogs.com/sirkevin/archive/2012/06/13/2548449.html Log4net简介根据日志类别保存到不同的文件,并按照日期生成不同 ...

  3. 【PSR规范专题(2)】PSR-1 基本代码规范

    转载自: https://github.com/PizzaLiu/PHP-FIG/blob/master/PSR-1-basic-coding-standard-cn.md 基本代码规范 本篇规范制定 ...

  4. 10 signs you’re dating the wrong person

    10 signs you’re dating the wrong person10个迹象表明TA不是你的真心人       Do you have any exes who were so awful ...

  5. Spring框架学习之第4节

    从ApplicaionContext应用上下文容器中获取bean和从bean工厂容器中有什么区别: 具体案例如下 结论: 1.如果使用上下文ApplicationContext,则配置的bean如果是 ...

  6. QTableWidget使用简单,因为不再存在父节点的关系

    虽然使用比较简单,但亲自过一遍还是有必要的,权当一个学习笔记吧,记录在此. #include "tablewidgetxxx.h" #include <QtGui/QAppl ...

  7. web appbuilder 改变样式和添加自定义widget

    一.改变样式 要实现的效果是添加cyan样式 1.将FoldableTheme/style下的cyan copy到TabTheme下的同一目录下: 2.打开TabTheme下的manifest,cop ...

  8. [iOS]把16进制(#871f78)颜色转换UIColor

    // // ViewController.m // text // // Created by 李东旭 on 16/1/22. // Copyright © 2016年 李东旭. All rights ...

  9. Linux远程文件传输

    linux系统中,难免会遇到一些要将某文件通过网络传送给其他主机的情况,而恰好两台主机 都是linux系统的时候,我们就可以直接使用scp命令来传输文件到另一台主机了. scp命令用于在网络中安全的传 ...

  10. Windows下搭建Android开发环境

    1.下载eclipse google eclipse,到官网去下载最新版的,推荐java ee版本的,当然你要用android版本或者standard也无妨 2.下载android sdk,安装相关的 ...