OpenGL实践3之第一个着色器程序(1)

来源:互联网 发布:dg数据恢复软件破解版 编辑:程序博客网 时间:2024/05/23 10:30

 OpenGL实践3之第一个着色器程序(1)

DionysosLai(906391500@qq.com)

引言:

       本章,我们将引入着色器概念,由于内容比较多,因此分成两个部分讲解,第一个部分讲解如何创建、生成一个着色器程序。第二部分,讲解着色器基本语法并编写一个简单着色器程序。

 

准备工作

现代Opengl渲染管线严重依赖着色器处理传入的数据。如果不使用着色器,那么OpenGL很多功能都没法实现,可能只能做到最简单的清除窗口工作。如果没有定义任何一个着色器,那么OpenGL将使用默认的顶点着色器和片段着色器(我们前面介绍的两章正是如此工作的)。OpenGL在3.1之前还包含一个固定功能管线(fixed-function pipeline),可以在没有着色器情况下处理几何和像素数据,在3.1之后,则将其删除了,因此现代OpenGL必须使用着色器工作。

       着色器是现代处理3D图形方式之一。原本在固定功能管线中的大部分3D函数,开发者只能指定一些配置参数(关照属性、旋转值等)。现在通过着色器,开发者可以自己去声明这些函数,这样使得程序更加灵活,更加创新。

       OpenGL渲染管道如下图所示:

                                                                                                    

1.    顶点着色器(vertex shader)

对于绘制命令的每一个顶点数据,首先会传入顶点着色器中进行相关处理。顶点着色器可以是非常简单,例如,只是将数据复制并传递到下一个着色器阶段,叫做传递着色器(这正是默认顶点着色器)顶点着色器也可以非常复杂,例如执行大量计算确定顶点位置。对于任何一个OpenGL程序,都必须有一个顶点着色器,当然可以不止一个,但是在同一时刻只能有一个顶点着色器。

2.    细分着色器(tessellation shader)

细分着色器是一个可选着色器。细分着色器接收来自顶点着色器数据,对其进一步处理。对细分着色器会在以后章节详细接受。

3.    几何着色器(Geometry shader)

几何着色器也是一个可选阶段。在几何着色器中,可以对图元进行更多处理。这些处理包括从输入图元中生成更多图元、更改生成图元类型或者放弃图元等操作。例如,我们本来提供图元一些列点数据,通过图元着色器,我们可以生成三角形图元(本来是生成一系列点)。几何着色器使能后,其数据来源于顶点着色器或者细分着色器。

4.    剪切、光栅化处理

剪切处理是由于一些顶点数据落在了视口外面,也就是我们绘制窗口之外,因此需要将这些顶点相关图元进行处理。这一过程,OpenGL会自动执行。

剪切之后,将最新图元传递到光栅化单元,生成对应的片元,这些片元,经过片元着色器和最后一些处理,将最终绘制在窗口上。

5.    片元着色器

片元着色器是一个必选阶段。在片元着色器中,我们将计算片元的颜色和其深度值,然后传递到管线的片元测试和混合模块。

在OpenGL的着色器,我们使用GLSL(OpenGL Shading Language)编程语言编写。GLSL非常像C/C++语言。首先我们需要编写着色器,并保证程序能用。然后编译这些文本到着色器对象中。编译成功后,我们就可以链接着色器到一个程序中,比加载到GPU。

       对于着色器创建使用,可以按照下面步骤:

1. 创建着色器对象

                  创建一个着色器对象     

                  将着色器源代码编译为对象

                  验证着色器的编译是否成功。

 

     2. 创建着色器程序

                  创建一个着色器程序

                  将着色器对象关联到着色器程序

                  链接着色器程序

                  判断着色器的链接是否成功

                  使用着色器来处理顶点和片元

 

代码实现:

      创建着色器对象:我们第一步要做的,就是创建一个正确的着色器对象。

 

         GLuintvertexShaderID = glCreateShader(GL_VERTEX_SHADER);

        Gluint framentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

      首先,我们通过不同type,创建一个顶点着色器和片元着色器(如果我们要创建细分着色器,参数为GL_TESS_CONTROL_SHADER、创建几何着色器参数为GL_GEOMETRY_SHADER  )。返回值为0,说明发生了错误

          

           /// 读取vectex shader code、关联顶点着色器

       std::ifstreamvertexShaderStream(vertex_file_path, std::ios::in);

       std::stringvertexShaderCode;

       if(vertexShaderStream.is_open())

       {

              std::stringline;

              while(getline(vertexShaderStream, line))

                     vertexShaderCode+= "\n" + line;

              vertexShaderStream.close();

       }

       else

       {

              printf("Impossibleto open %s. Are you in the right directory ? Don't forget to read the FAQ!\n", vertex_file_path);

              getchar();

              return0;

       }    

       constGLchar *vertexCodePointer = vertexShaderCode.c_str();

      glShaderSource(vertexShaderID, 1, &vertexCodePointer,NULL);

           接下来,我们要做的是关联着色器代码,这里我们关联的的是顶点着色器代码。关联着色器代码,使用函数glShaderSource(),函数原型如下:

        void glShaderSource(GLuintshader, GLsizei count, const GLchar** shring, const GLuint* length)

        string 是一个由count行GLchar 类型的字符串组成的数字,用来表示着色器的源代码数据(一般,源代码已文件形式组成,那么 count = 1)。在这里,我们的顶点着色器代码,正好一文件形式组成,因此count为1。 string中字符串可以是NULL结尾,也可以不是。 length为NULL,表示string给出的每行字符串都是NULL结尾(源代码文件形式,就是NULL结尾), 否则length中必须有count个元素,它们分别表示string中对应行的长度。

 

           glCompileShader(vertexShaderID);

           编译着色器对象,只需要调用 glCompileShader() 函数即可。

 

       GLintsuccess;

       intinfoLogLength;

       glGetShaderiv(vertexShaderID,GL_COMPILE_STATUS, &success);

       glGetShaderiv(vertexShaderID,GL_INFO_LOG_LENGTH, &infoLogLength);

       if(infoLogLength > 0)

       {

              std::vector<char>vertexShaderErrorMessage(infoLogLength + 1);

              glGetShaderInfoLog(vertexShaderID,infoLogLength, NULL, &vertexShaderErrorMessage[0]);

              printf("%s\n",&vertexShaderErrorMessage[0]);

        }

        然而,就像我们编译C++代码一样,并不能保证一次编译通过。同样,我们在编译着色器代码时,也会经常遇到编译不通过问题。

        OpenGL提供了编译结果查询函数glGetShaderiv(), 当参数为GL_COMPILE_STATUS, 返回编译状态,当参数为 GL_INFO_LOG_LENGTH时,则返回编译信息。

        void glGetShader InfoLog(GLuintshader, GLsizei bufSize, GLsizei* length, char* infoLog)

        返回shader最后编译结果。如果length 设置为NULL, 将不会返回infoLog 的大小。

 

创建着色器程序:当着色器对象创建完毕后,我们就要生成一个着色器程序。

           GLuint programID = glCreateProgram();

           首先,我们需要创建一个着色器程序。当返回0 说明发生错误

 

        glAttachShader(programID, vertexShaderID);

        着色器程序正确创建后,我们将上述生成的顶点着色器关联到程序中(对于片段着色器,做法一样)。这个做法,就好比我们在一个makefile文件中指定了一些列要链接的对象。

       

       glLinkProgram(programID);

       当我们关联了相应的着色器对之后,我们就可以链接对象生成可执行程序了。

 

       glGetProgramiv(programID,GL_LINK_STATUS, &success);

       glGetProgramiv(programID,GL_INFO_LOG_LENGTH, &infoLogLength);

       if(infoLogLength > 0){

              std::vector<char>ProgramErrorMessage(infoLogLength + 1);

              glGetProgramInfoLog(programID,infoLogLength, NULL, &ProgramErrorMessage[0]);

              printf("%s\n",&ProgramErrorMessage[0]);

       }

       同样,跟之前编译着色器对象一样,在生成可执行程序过程中,我们可能遇到一些问题。因此我们要确保问题是否存在、问题的信息是什么。

        OpenGL 提供函数glGetProgramiv(),可以结果查询链接结果。当参数为GL_LINK_STATUS时,返回链接结果;当参数为GL_INFO_LOG_LENGTH时,返回链接信息。

        同时,OpenGL提供函数void glGetProgramInfoLog(GLuing program, GLsizeibufSize, GLsizer* length, char* infoLog) ,可以返回最后一个program链接的日记信息

      

       glValidateProgram(programID);

       当我们正确生成可执行程序后,我们要使用这个程序之前,必须使能它。这是由于生成可执行程序这一步,只不过是确保我们今后调用这个程序时,不会出现错误。但是,在一个复杂的应用中,存在多种多样的着色器和许多状态改变,因此在调用draw()函数之前,我们最好使能一个。由于我们本次例程非常简单,因此我们只需要使能一次就可以了。

       glUseProgram(programID);

       当一切都OK之后,我们就可以很happy的使用这个着色器程序了。函数glUseProgram(),可以帮助我们将链接好的着色器程序设置到管道状态中。一旦设置成功,那么在整个draw调用中,都会有效状态。除非我们替换了另一个着色器程序,或者传递NULL参数到glUseProgram()函数中,使之失效。

       到目前为止,我们如何调用着色器就讲解完毕了,下一章,我们将编写一个最简单的着色器程序。

       最后祝大家工作愉快。


[延伸阅读]:

[1] 《OpenGL编程指南》第三版P51~P58

 

1 0
原创粉丝点击