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

posted @ 2016-04-24 18:17  lijihong0723  阅读(1288)  评论(2编辑  收藏  举报
页脚测试