【OpenGL】GLSL学习笔记(持续更新)

由于这里的知识点很细碎又不是很多,所以我边学OpenGl一边把需要用到的GLSL知识写上去。

0.概念和初始化:

着色器分为顶点着色器(Vertex Shader)和片元着色器(Fragment Shader),语法类似C++,OpenGL对每一个顶点都执行一次顶点着色器,对所有片元执行片元着色器(片元可以狭隘的理解为像素)。

初始化:

In C++:

GLuint programID = LoadShaders( "TransformVertexShader.vertexshader", "ColorFragmentShader.fragmentshader" );

获得到的是链接的着色器句柄。但是LoadShaders却需要我们自己编写。

GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path){

    // Create the shaders
    GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
    GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

    // Read the Vertex Shader code from the file
    std::string VertexShaderCode;
    std::ifstream VertexShaderStream(vertex_file_path, std::ios::in);
    if(VertexShaderStream.is_open()){
        std::stringstream sstr;
        sstr << VertexShaderStream.rdbuf();
        VertexShaderCode = sstr.str();
        VertexShaderStream.close();
    }else{
        printf("Impossible to open %s. Are you in the right directory ? Don't forget to read the FAQ !
", vertex_file_path);
        getchar();
        return 0;
    }

    // Read the Fragment Shader code from the file
    std::string FragmentShaderCode;
    std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in);
    if(FragmentShaderStream.is_open()){
        std::stringstream sstr;
        sstr << FragmentShaderStream.rdbuf();
        FragmentShaderCode = sstr.str();
        FragmentShaderStream.close();
    }

    GLint Result = GL_FALSE;
    int InfoLogLength;


    // Compile Vertex Shader
    printf("Compiling shader : %s
", vertex_file_path);
    char const * VertexSourcePointer = VertexShaderCode.c_str();
    glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
    glCompileShader(VertexShaderID);

    // Check Vertex Shader
    glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    if ( InfoLogLength > 0 ){
        std::vector<char> VertexShaderErrorMessage(InfoLogLength+1);
        glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
        printf("%s
", &VertexShaderErrorMessage[0]);
    }



    // Compile Fragment Shader
    printf("Compiling shader : %s
", fragment_file_path);
    char const * FragmentSourcePointer = FragmentShaderCode.c_str();
    glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
    glCompileShader(FragmentShaderID);

    // Check Fragment Shader
    glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    if ( InfoLogLength > 0 ){
        std::vector<char> FragmentShaderErrorMessage(InfoLogLength+1);
        glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
        printf("%s
", &FragmentShaderErrorMessage[0]);
    }



    // Link the program
    printf("Linking program
");
    GLuint ProgramID = glCreateProgram();
    glAttachShader(ProgramID, VertexShaderID);
    glAttachShader(ProgramID, FragmentShaderID);
    glLinkProgram(ProgramID);

    // Check the program
    glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
    glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    if ( InfoLogLength > 0 ){
        std::vector<char> ProgramErrorMessage(InfoLogLength+1);
        glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
        printf("%s
", &ProgramErrorMessage[0]);
    }

    
    glDetachShader(ProgramID, VertexShaderID);
    glDetachShader(ProgramID, FragmentShaderID);
    
    glDeleteShader(VertexShaderID);
    glDeleteShader(FragmentShaderID);

    return ProgramID;
}
LoadShaders

大意是通过文件流获取两个着色器代码,然后分别编译这两个代码得到两个着色器句柄,最后把创建一个进程把这两个句柄合一块,最后把这个进程链接到主程序中。

由于此代码是如此的枯燥,所以最好封装成一个库。

1.传入属性(Attribute):

In GLSL,Vertex Shader:

layout(location = 0) in vec3 vertexPosition_modelspace;

我们要知道,我们传进去的数据是顶点属性,0代表一个位置,OpenGL确保至少有16个包含4分量的顶点属性可用,但是有些硬件或许允许更多的顶点属性,你可以查询GL_MAX_VERTEX_ATTRIBS来获取具体的上限。

in vec3表示输入,使用vec3这种类型,后面的是名字。

In C++:

第一步创建一个VBO把顶点数据拷进去:

GLuint vertexbuffer;
    glGenBuffers(1, &vertexbuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);

参数应该很好懂,最后一个函数glBufferData的第四个参数是GL_STATIC_DRAW,代表静态数据,不能修改,可以提高性能。

第二步:

glEnableVertexAttribArray(0);
        glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
        glVertexAttribPointer(
            0,                  // attribute. No particular reason for 0, but must match the layout in the shader.
            3,                  // size
            GL_FLOAT,           // type
            GL_FALSE,           // normalized?
            0,                  // stride
            (void*)0            // array buffer offset
        );

一步步解释。首先得知道,我们向着色器里传进去的是顶点属性(Vertex Attribute),之前说过它的数量是有限制的。

第一行glEnableVertexAttribArray(0)指定的是启用location=0的顶点属性,此时在GLSL中可以用layout(location = 0)接收到。

第二行把vertexbuffer——我们定义的顶点缓冲,之前我把顶点类型为GLfloat的一维数组,其中每连续三个元素代表一个顶点——绑定到当前上下文的GL_ARRAY_BUFFER中,在使用前要绑定,这时我们就可以使用——

第三行glVertexAttribPointer,它用于指定顶点数组的数据格式和位置。首先是位置,第一个参数,这里填的0,对应layout的0;3代表三个元素一个顶点;GL_FLOAT代表我们的类型;GL_FALSE代表不进行标准化(向量标准化就是把(1,10,1,0)变成(0.1,1,0.1,0));0代表步长(此处等效于4),最后一个是数组偏移量,因为我们从数组下标为0开始定义顶点,所以填0。

为什么要指定这些呢?因为OpenGL只知道array buffer里的二进制数据,并不知道这些数据的意义,所以当我们要把这些数据传给着色器的时候,必须要事先指定类型和位置。

最后,随手关门好习惯,记得关闭顶点属性和销毁顶点数组缓冲区。

glDisableVertexAttribArray(0);//关闭位置为0的顶点属性
glDeleteBuffers(1, &vertexbuffer);//销毁顶点数组缓冲

2.传入变量(Variable)

使用uniform关键字来实现,适用于所有着色器统一传入的常量。

In GLSL,Vertex Shader

使用uniform关键字传入一个矩阵:

uniform mat4 MVP;

注意,虽然说是变量,但uniform传入的变量实际上不可修改。

In C++:

GLuint MatrixID = glGetUniformLocation(programID, "MVP");

通过glGetUniformLocation获取指定名字的uniform的句柄,programID是我们之前提到的着色器句柄。

glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);

通过glUniformMatrix4fv传入数据,MatrixID是之前获取到的句柄,1代表数组数量或者矩阵的数量,GL_FALSE指定矩阵是列优先矩阵,最后传入数据开头的指针。

除了传入矩阵,还有以下函数来对uniform进行装载(没有重载太蛋疼了):

这是获取uniform地址,返回一个句柄,接下来的装载将使用这个句柄。

原文地址:https://www.cnblogs.com/dudujerry/p/13542466.html