OpenGL 笔记 <2> Compiling and Linking a shader program

 

Preface

这一节所有的主要内容都在一个OpenGL库文件中<LoadShaders.h> ,只需要用LoadShader()函数进行加载即可。但是由于老是出错,所以自己实现了一下,也用以提供给有兴趣了解着色器的编译、连接等原理的人。

因为程序基本都是自己实现的,所以,只需要包含最基本的OpenGL头文件即可运行。

效果如下:

Background

由于固定管线编程过程中涉及的大量计算(当然,上个例子并没有多少计算量)都是再CPU上进行的,而这些可能影响CPU处理其他事情的效率,所以不妨运用一种编程技法,将这些繁重的运算交予GPU去处理, 进而腾出CPU的时间。于是就引进了我们今天的前提——可编程管线,其对应的语言为GLSL(OpenGL Shading Language)。

先说一下博文撰写原则,博文撰写涉及到的背景知识只阐述与本次主题相关的,至于整合的那种完整的理论原理综述,如果需要的话会在后面做统一的阐述,而单篇有主题的文章中不做总结,不然可能会喧宾夺主。

看完这一篇可能会有几个疑问:着色器到底干了啥,它是怎么导致图形效果的,以及它里面每一个关键字都是干什么的,等等

上述问题,可能考虑下一篇讲,有人会问为什么不先弄清楚上面的再写下面的,因为我就是那样过来的,只有空洞的理论和语法,这种还是先看到效果,然后追究其原理更合适一点,这是我的一个安排和考虑,理解万岁。

 

Getting ready

觉得还是需要简单说明一下可编程渲染管线的流程(超级精简版):

对于顶点属性数据,如:位置、颜色、纹理等等,他们都需要进行某些操作,比如,位置可能需要平移,颜色可能需要渐变等,而这些一定都是矩阵运算,而不同的处理就对应着不同的着色器,嗯,就是酱紫。

而我们今天只需要用到顶点着色器和片元着色器。

以顶点着色器为例来介绍我们这次需要掌握的GLSL语法

 

vertex Shader (顶点着色器)

将下面的内容写到一个名为basic.vert的文件中 (emmm,最后一行可能要有一个空行,这个不是语法要求,这个是该程序所需,后面会讲到)

#version 430

layout (location=0) in vec3 VertexPosition;                   
layout (location=1) in vec3 VertexColor;

out vec3 Color;

void main()
{
    Color = VertexColor;
    
    gl_Position = vec4(VertexPosition,1.0);
}

顶点着色器:对于绘制命令传输的每一个顶点,OpenGL都会调用一个顶点着色器来处理顶点相关的属性数据。

顶点着色器的复杂与否与其他着色器的活跃与否有关,所以,它可能会非常简单,如:值将数据复制并传递到下一个阶段,这类似于pass-through shader,就是,走一下形式,数据简单流过,无任何操作。它也可能非常复杂,如:执行大量的计算来得到顶点在屏幕上的位置,或者通过光照的计算来判断顶点的颜色。

注:一个复杂应用程序可能有多个顶点着色器,但统一时刻只能有一个顶点着色器起作用

in 从应用程序(我们习惯将主程序称为应用程序)中传递进来的数据

out 从该着色器传递出去的新数据(相当于C++程序中的return 变量)

第一句#version 430指的是opengl或GLSL的版本为4.3(后面用程序获取本机的版本号)

因为可能会有很多个in 对象,而我们知道应用程序中需要获取它的位置,所以就要有Location,即下标、索引 号,而layout就是一个GLSL语言的关键字用于人工确定索引的排布方式。上一节中,提到过Location:(https://www.cnblogs.com/lv-anchoret/p/9221504.html)

 

vec3就是一个一维的有三个元素的数值类型(三元组)

gl_Position就是OpenGL系统内部的量,需要将处理好的量传递给它,它和out量都会传递到下面继续处理.

vertex shader takes the input attributes VertexPosition and VertexColor and passes them along to the fragment shader via the output variabes gl_Position and Color.

 

而关于主程序代码中,对着色器的处理与之前固定管线的结合,我们也并不需要担心太多,主程序中,单纯从代码上说它们可能交融的不是很紧密。

我们需要用一些window工具包来构建脚本,跨平台的有GLFW、GLUT、FLTK、QT或者wxWidgets。我们这里继续选用GLUT / freeglut

 

Compiling a shader

the diagram of this recipe

 

第一步 #  创建一个着色器对象   Create the shader object 

vertShader = glCreateShader(GL_VERTEX_SHADER);

函数的参数为着色器类型,该函数返回一个着色器对象的映射量(GLuint型),有时称之为对象句柄(object“handle”)。

如果创建失败,将返回一个0,所以我们可以根据返回值来检验是否创建成,如果没有创建成功,则返回一个正确的信息,以用于检验程序错误。

 

第二步 #  拷贝脚本源程序  Copy the source code

const GLchar* shaderCode = loadShaderAsString("basic.vert");
const GLchar* codeArray[] = {shaderCode};
glShaderSource(vertShader, 1, codeArray, NULL);

loadShaderAsString(); 似乎是在flew头文件中才有的吧,我也不是很清楚,原则上应该是这么整的,但是出于,我也不知道它的头文件,所以就这个也是自己写的。

它的目的就是将以参数为名的文件信息内容读取出来。(因为我也不知道这个东西,之前一直以为是类型转换之类的,然后就出现了很多错误)

glShaderSource();      // load the source code into the shader object

因为这个函数支持一次性加载多个源代码,所以我们把读取的字符串指针放入一个数组中,即 codeArray

parameter 1:创建的着色器对象的映射值(即:shader_Obj  handle)

para  2:  Array 数组里面元素的个数

para  3:  Array数组

para  4:  一个数组,记录Array数组中每个元素的长度。如果你读取的每个源代码字符串是以null结尾的,那么此处可以填写NULL

 

第三步 #  编译着色器   Compile the shader

glCompileShader(vertShader);

 

第四步 #  验证编译状态    Vertify the compilation status

GLint result;
glGetShaderiv(vertShader, GL_COMPILE_STATUS, &result);
if (GL_FALSE == result)
{
    fprintf(stderr, "Vertex shader compilation failed!\n");
    GLint logLen;
    glGetShaderiv(vertShader, GL_INFO_LOG_LENGTH, &logLen);
    if (logLen > 0)
    {
        char* log = new char[logLen];
        GLsizei written;
        glGetShaderInfoLog(vertShader, logLen, &written, log);
        fprintf(stderr, "Shader log:\n%s", log);
        delete[] log;
    }
}

glGetShaderiv(shader_Obj handle,要查询的状态的枚举量,用于记录结果的参数);

之后就是逐步验证着色器编译时候的每一个状态,直到找到错误点为止,然后提示相应的错误信息

 

Linking a shader program

the diagram of this recipe:

我们编译好我们的着色器之后,在将它们初始化到管线之前,我们还需要将它们链接到同一个着色器程序中。

我们知道上面提到的管线过程中,着色器之间是有数据交流的,所以为了让所有在同一个着色器程序中的各个着色器能够互相通信,我们需要把它们链接到同一个着色器程序中。

其步骤分为一下几步:

 

第一步 #  创建一个着色器程序     Create the program object 

GLuint programHandle = glCreateProgram();

 

第二步 #  关联着色器与着色器程序   attach the shaders to the program object 

glAttachShader(programHandle, vertShader);
glAttachShader(programHandle, fragShader);

就是将着色器对象句柄与着色器程序句柄关联在一起

 

第三步 #  链接着色器程序   Link the progra

glLinkProgram(programHandle);

 

检验,如果没问题,则进行下一步

第四步 #  Install the program into the OpenGL  pipline

glUseProgram(programHandle);

 

到此,我们拥有了一个完整的OpenGL管线,然后就可以渲染了。

 

如果要删除句柄可以如下这么做

glDeleteProgram(programHandle); 如果正在使用该着色器程序,那么,将会当它 不再使用的时候删除

glDeleteShader   同上

 

代码:做了点小小的整合

顶点着色器:basic.vert

#version 430

layout (location=0) in vec3 VertexPosition;
layout (location=1) in vec3 VertexColor;

out vec3 Color;

void main()
{
    Color = VertexColor;
    
    gl_Position = vec4(VertexPosition,1.0);
}

片元着色器:basic.frag

#version 430

in vec3 Color;

out vec4 FragColor;

void main()
{
    FragColor = vec4(Color, 1.0);
}

注意两者后面要空一行空行,原因在前面说过了

主程序:

与本节无关的代码,如果有什么问题,可以参见上一节 笔记<1>:https://www.cnblogs.com/lv-anchoret/p/9221504.html

//配置代码
#if _MSC_VER>=1900
#include "stdio.h"
_ACRTIMP_ALT FILE* __cdecl __acrt_iob_func(unsigned);
#ifdef __cplusplus
extern "C"
#endif
FILE* __cdecl __iob_func(unsigned i) {
    return __acrt_iob_func(i);
}
#endif /* _MSC_VER>=1900 */

//code-list
//using namespace std;
#include <iostream>
#include <fstream>
using namespace std;
#include <vgl.h>

GLint vertShader, fragShader;
GLuint vaoHandle;

float positionDate[] =
{
    -0.8f,-0.8f,0.0f,
    0.8f,-0.8f,0.0f,
    0.0f,0.8f,0.0f,
};
float colorDate[] =
{
    1.0f,0.0f,0.0f,
    0.0f,1.0f,0.0f,
    0.0f,0.0f,1.0f,
};

void init();
void Display();
void _Compiling_Shader_(GLint& shaderHandle, GLint GL_Shader_type, GLchar* shaderName);  //编译着色器
void _Link_Shader_();                          //链接着色器
bool readFile(const char*, string&);           //读取文件内容的函数

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowSize(1024, 768);
    glutInitWindowPosition(20, 20);
    glutCreateWindow("test");

    if (glewInit())
    {
        cout << "Error!" << glGetString(glewInit()) << endl;
        exit(EXIT_FAILURE);
    }

    cout << "GL version:" << glGetString(GL_VERSION) << endl;         //查询本机OpenGL版本

    init();


    _Compiling_Shader_(vertShader, GL_VERTEX_SHADER,"basic.vert");
    _Compiling_Shader_(fragShader, GL_FRAGMENT_SHADER, "basic.frag");

    _Link_Shader_();

    glutDisplayFunc(Display);
    
    glutMainLoop();
}


void init()
{
    GLuint vboHandles[2];

    glGenBuffers(2, vboHandles);

    GLuint postionBufferHandle = vboHandles[0];
    GLuint colorBufferHanle = vboHandles[1];

    glBindBuffer(GL_ARRAY_BUFFER, postionBufferHandle);
    glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), positionDate, GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, colorBufferHanle);
    glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), colorDate, GL_STATIC_DRAW);

    glGenVertexArrays(1, &vaoHandle);
    glBindVertexArray(vaoHandle);

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glBindBuffer(GL_ARRAY_BUFFER, postionBufferHandle);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);

    glBindBuffer(GL_ARRAY_BUFFER, colorBufferHanle);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
}

void Display()
{
    glClear(GL_COLOR_BUFFER_BIT);

    glBindVertexArray(vaoHandle);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    glutSwapBuffers();
}

bool readFile(const char* filename, string& content)
{
    ifstream infile;
    infile.open(filename);
    if (!infile.is_open())return false;

    char ch;
    infile >> noskipws;
    while (!infile.eof())
    {
        infile >> ch;
        content += ch;
    }
    infile.close();
    content += '\0';
    return true;
}

void _Compiling_Shader_(GLint& shaderHandle, GLint GL_Shader_type, GLchar* shaderName)
{
    shaderHandle = glCreateShader(GL_Shader_type);
    //检查编译情况
    if (0 == shaderHandle)
    {
        fprintf(stderr, "Error creating shader.\n");
        exit(EXIT_FAILURE);
    }
    string ShaderCode;
    if (!readFile(shaderName, ShaderCode))
    {
        cout << "readFile Error!" << endl;
        exit(EXIT_FAILURE);
    }
    const GLchar* shaderSource = ShaderCode.c_str();
    const GLchar* codeArray[] = { shaderSource };
    glShaderSource(shaderHandle, 1, codeArray, NULL);

    glCompileShader(shaderHandle);

    GLint result;
    glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &result);
    if (GL_FALSE == result)
    {
        fprintf(stderr, "shader compilation failed!\n");

        GLint logLen;
        glGetShaderiv(shaderHandle, GL_INFO_LOG_LENGTH, &logLen);

        if (logLen > 0)
        {
            char* log = new char[logLen];

            GLsizei written;
            glGetShaderInfoLog(shaderHandle, logLen, &written, log);

            fprintf(stderr, "Shader log:\n%s", log);
            delete[] log;
        }
    }
}
void _Link_Shader_()
{
    GLuint programHandle = glCreateProgram();
    if (0 == programHandle)
    {
        fprintf(stderr, "Error creating program object.\n");
        exit(1);
    }

    glAttachShader(programHandle, vertShader);
    glAttachShader(programHandle, fragShader);

    glLinkProgram(programHandle);

    GLint status;
    glGetProgramiv(programHandle, GL_LINK_STATUS, &status);
    if (GL_FALSE == status)
    {
        fprintf(stderr, "Failed to link shader program!\n");

        GLint logLen;
        glGetProgramiv(programHandle, GL_INFO_LOG_LENGTH, &logLen);
        if (logLen > 0)
        {
            char* log = new char[logLen];

            GLsizei written;
            glGetShaderInfoLog(programHandle, logLen, &written, log);

            fprintf(stderr, "Program log:\n%s", log);
            delete[] log;
        }
    }
    else
        glUseProgram(programHandle);
}

 

如果出现_fprintf、_sscanf或者__iob_func问题,请参见:https://www.cnblogs.com/lv-anchoret/p/8729384.html

如果没有vgl.h的,见如下

#ifndef __VGL_H__
#define __VGL_H__

// #define USE_GL3W

#ifdef USE_GL3W

#include <GL3/gl3.h>
#include <GL3/gl3w.h>

#else

#define GLEW_STATIC

#include <GL/glew.h>

#ifdef _MSC_VER
#  ifdef _DEBUG
#    if (_MSC_VER >= 1600)
#      pragma comment (lib, "glew_static_vs2010_d.lib")
#    else
#      pragma comment (lib, "glew_static_d.lib")
#    endif
#  else
#    if (_MSC_VER >= 1600)
#      pragma comment (lib, "glew_static_vs2010.lib")
#    else
#      pragma comment (lib, "glew_static.lib")
#    endif
#  endif
#endif

#endif

#define FREEGLUT_STATIC

#include <GL/freeglut.h>

#ifdef _MSC_VER
#  ifdef _DEBUG
#    if (_MSC_VER >= 1600)
#      pragma comment (lib, "freeglut_static_vs2010_d.lib")
#    else
#      pragma comment (lib, "freeglut_static_d.lib")
#    endif
#  else
#    if (_MSC_VER >= 1600)
#      pragma comment (lib, "freeglut_static_vs2010.lib")
#    else
#      pragma comment (lib, "freeglut_static.lib")
#    endif
#  endif
#endif

#define BUFFER_OFFSET(x)  ((const void*) (x))

#endif /* __VGL_H__ */

 

如果下载的是9th版本的OpenGL库文件,那么vgl是不完整的

 

这个程序的运行效果在一开始有,就不放了,放一个错误提示,当初因为一个函数解读错误导致的

可以看出来错误定位还是非常精准的

 

今天就到这里

 

感谢您的阅读,生活愉快~

posted @ 2018-06-26 23:37  林-兮  阅读(1207)  评论(1编辑  收藏  举报