zoukankan      html  css  js  c++  java
  • 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 /// rief    
    18 class GPUProgram
    19 {
    20 public:
    21     /// rief    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主要是用于三维图形的绘制,接下来我们会将这些基础知识应用到三维图形的绘制。哎,明天又要上班了,又不能睡懒觉了,~~~~(>_<)~~~~

  • 相关阅读:
    onpropertychange与onchange事件应用
    HttpWorkerRequest实现大文件上传asp.net
    JQuery中对option的添加、删除、取值
    "分析 EntityName 时出错"的解决方法
    asp.net断点续传
    直接在ASP.net中上传大文件的方法
    ASP.NET中文件上传下载方法集合
    asp.net .ashx文件使用Server.MapPath解决方法
    FF与IE下javascript计算屏幕尺寸
    处理顶点——为赛道创建顶点
  • 原文地址:https://www.cnblogs.com/lijihong/p/5410010.html
Copyright © 2011-2022 走看看