录十六

持之以恒

创建和编译着色器

着色器源代码需要经过编译,链接,才能生成可执行的着色器程序,但是着色器的编译器和链接器都是内置在OpenGL库中的。因此我们需要调用OpenGL函数,才能创建出可执行的着色器程序。

一、定义着色器类

定义一个着色器的c++类,将所有着色器相关的操作都封装在一起,方便我们后续的代码管理和维护。MCShader类的定义如下:

class MCShader
{
public:
	MCShader();
	virtual ~MCShader();

private:
	//shader对象句柄
	unsigned int m_Handle;
};

目前这个类只有一个私有的成员变量m_Handle,用来保存创建成功之后的着色器对象句柄。此外,为了访问m_Handle成员变量,可以再增加一个成员函数:

//返回shader对象   
unsigned int  GetHandle()
{
    return m_Handle;
}

二、创建和编译着色器

(1)创建着色器对象

首先需要使用glCreateShader函数,创建一个着色器对象,它的函数原型如下:

GLuint glCreateShader(GLenum type)

type — 需要创建的着色器对象类型,可选的值有GL_FRAGMENT_SHADER和GL_VERTEX_SHADER

目前OpenGL3.0只支持顶点着色器和片段着色器,因此着色器类型的参数只有两种:GL_VERTEX_SHADER(顶点着色器)和GL_FRAGMENT_SHADER(片段着色器)。 调用glCreateShader函数创建着色器对象,如果创建成功,会返回一个非零的,空着色器对象的句柄。

(2)附加着色器源码

着色器对象创建成功之后,下一步还要使用glShaderSource函数,为之前创建的空着色器对象绑定着色器的源代码。

void glShaderSource( GLuint shader, GLsizei count, 
                      const GLchar** string, 
                      const GLchar* length )
  • shader — 着色器对象的句柄

  • count — 着色器源代码的数量

  • string — 每个着色器源代码的地址指针(数量由count决定)

  • length — 每个着色器源代码的长度(数量由count决定)

这个函数为绑定着色器源码,提供了一种很灵活的方式。着色器源码可以分布在多个字符串数组中,我们只需要提供这些数组的指针数组,以及一个用于存放对应源代码段长度的整型数组。但为了方便,一般整个着色器源代码仅使用一个字符串数组,源程序指针数组和和长度数组都只有一个元素。

其中参数length可以为NULL,那么着色器源代码的字符串必须是以null结尾的。

(3)编译着色器

着色器源码被绑定后,下一步就是调用glCompiler函数,编译之前绑定的着色器源代码,函数原型:

void glCompileShader(GLuint shader)
  • shader — 着色器对象的句柄

该函数比较简单,它没有返回值,着色器对象编译的成功与否,需要后续做进一步的编译状态查询。

下面是着色器源代码编译的整个过程:

bool MCShader::LoadShaderSource(int type, const char* shaderSource)
{  
    //第一步,创建shader对象
    unsigned int shader = GL30::glCreateShader(type);   
    if (shader == 0)  
    {  
        return false;  
    }  
  
    // 第二步,绑定shader源码  
    GL30::glShaderSource(shader, 1, &shaderSource, NULL);  
  
    // 第三步,编译shader源码
    GL30::glCompileShader(shader);  
  
    //其它代码段…
}

三、检查编译状态

(1)编译状态查询

在完成着色器编译后,首先需要检查编译是否成功。为了查询编译状态,需要借助于glGetShaderiv函数,它的原型如下:

 void glGetShaderiv(GLuint shader, GLenum pname, GLint *params)
  • shader — 着色器对象的句柄

  • pname — 需要查询的信息名称, 可选的参数有:

    • GL_COMPILE_STATUS:编译状态

    • GL_DELETE_STATUS:删除状态

    • GL_INFO_LOG_LENGTH:Log信息长度

    • GL_SHADER_SOURCE_LENGTH:源代码长度

    • GL_SHADER_TYPE:着色器类型

  • params — 查询结果返回的指针地址

使用glGetShaderiv查询着色器的编译状态,pname参数需要选择GL_COMPILE_STATUS,如果编译成功,params中返回的值为GL_TURE,否则为GL_FALSE;pname参数选择GL_INFO_LOG_LENGTH,可以查询编译着色器时,生成的错误信息或者警告信息的长度。

当然,glGetShaderiv函数还可以查询着色器其它的一些信息,比如着色器对象的类型,着色器源码的长度和着色器对象是否处于删除状态。

(2)编译错误输出

如果着色器源码编译失败了,还需调用glGetShaderInfoLog函数获取错误信息。因为我们需要根据错误信息,修改着色器源代码,直至编译通过。它的函数原型如下:

void glGetShaderInfoLog(GLuint shader,
                        GLsizei maxLength,
                        GLsizei *length,
                        GLchar *infoLog)
  • shader — 着色器对象的句柄

  • maxLength — 获取错误信息buffer的长度

  • length — 错误信息的长度(不包含null终止符),如果不需要获取长度,参数可以传NULL

  • infoLog — 获取错误信息的buffer指针

调用glGetShaderInfoLog函数之后,返回的错误信息是一个以null结尾的字符串,它保存在infoLog缓存中,长度为length个字符。错误信息可以返回的最大长度是通过maxLength定义的,如果maxLength定义的长度过小,则返回的错误信息将被截断。

将检查着色器编译状态的整个过程,可以封装成一个函数。下面是它的代码实现:

bool MCShader::CheckCompileStatus(unsigned int shader)
{
    //获取编译状态
    GLint compiled;  
    GL30::glGetShaderiv(shader, GL30::GL_COMPILE_STATUS, &compiled);

    //编译失败时,获取Log信息
    if (!compiled)  
    {   
        //获取Log长度
        GLint infoLen;  
        GL30::glGetShaderiv(shader, GL30::GL_INFO_LOG_LENGTH, &infoLen);

        if (infoLen > 0)  
        {  
            char* pLogMsg = new char[infoLen];  
             
            //获取编译失败时的Log信息
            GL30::glGetShaderInfoLog(shader, infoLen, NULL, pLogMsg);
            //打印失败的Log信息
            printf("Error compiling shader: \n%s\n", pLogMsg);

            delete[] pLogMsg;
        }
        return false;
    }
    return true;
}

三、删除着色器对象

当着色器对象使用完成后,最后还需要删除着色器对象,释放系统资源。

 void glDeleteShader(GLuint shader)
  • shader — 着色器对象的句柄

如果一个着色器对象正在使用中,那么当执行glDeleteShader函数时不会立即被删除,而是将该着色器对象标记为删除状态。当它不再被其它任何程序对象使用时,将会被自动删除。

void MCShader::DeleteShader()
{
    if(m_Handle != 0)
    {
        GL30::glDeleteShader(m_Handle);
        m_Handle = 0;
    }
}

四、函数调用

首先在OpenGL窗口类中,定义两个着色器类的对象,分别是顶点和片段着色器类对象:

private:
    MCShader  m_vsShader; //顶点shader
    MCShader  m_psShader; //片段shader

为了创建着色器对象,还需要提供着色器的源代码。这里我们先提供一个顶点shader源码和一个片段shader源码。至于如何编写着色器源代码,这里不做过多说明。

//顶点Shader源码
GLbyte vsShaderStr[] =
        "attribute vec4 vPosition;                  \n"
        "void main()                                \n"
        "{                                          \n"
        "    gl_Position = vPosition;               \n"
        "}                                          \n";

//片段Shader源码
GLbyte psShaderStr[] =
        "void main()                               \n"
        "{                                         \n"
        "  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
        "}                                         \n";

这两个着色器的源码字符串,定义在OpenGL窗口类的初始化函数中。下一步,就可以调用LoadShaderSource函数,编译这两段着色器源代码。如果没发生任何错误,应该都会返回ture,否则返回false。

//编译顶点shader
if(!m_vsShader.LoadShaderSource(GL30::GL_VERTEX_SHADER, vsShaderStr))
{
        printf("the vertex shader compiled failed.\n");
}

//编译片段shader
if(!m_psShader.LoadShaderSource(GL30::GL_FRAGMENT_SHADER, psShaderStr))
{
        printf("the fragment shader compiled failed.\n");
}

五、配套源码

源码下载:CompileShader

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

Copyright © 1999-2019, lu16.com, All Rights Reserved