C/C++中OpenGL着色器的简易框架

Nil*_*ils 31 c c++ opengl

我只是想在平面图像上尝试一些着色器.事实证明,编写一个C程序,它只是将图片作为纹理并应用,假设是高斯模糊,因为它上面的片段着色器并不那么容易:你必须初始化OpenGL,就像100行代码,然后了解GLBuffers等.另外要与窗口系统进行通信,必须使用另一个框架GLUT.

事实证明,Nvidia的Fx作曲家很适合使用着色器.但我仍然希望有一个简单的C或C++程序,它只是将给定的片段着色器应用于图像并显示结果.有人有例子还是有框架?

Jer*_*fin 55

首先,我会避免使用过剩 - 这是错误的,大约十年没有更新,它的设计并不能很好地适应大多数人今天想要的东西(例如,虽然你可以用它来做动画,它主要用于生成静态显示).我在之前的回答中指出了一些过剩的替代品.

这(主要)留下代码来编译,链接和使用着色器.我写了一个小课程,我觉得这个目的很方便:

class shader_prog {
    GLuint vertex_shader, fragment_shader, prog;

    template <int N>
    GLuint compile(GLuint type, char const *(&source)[N]) {
        GLuint shader = glCreateShader(type);
        glShaderSource(shader, N, source, NULL);
        glCompileShader(shader);
        GLint compiled;
        glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
        if (!compiled) {
            GLint length;
            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
            std::string log(length, ' ');
            glGetShaderInfoLog(shader, length, &length, &log[0]);
            throw std::logic_error(log);
            return false;
        }
        return shader;
    }
public:
    template <int N, int M>
    shader_prog(GLchar const *(&v_source)[N], GLchar const *(&f_source)[M]) {
        vertex_shader = compile(GL_VERTEX_SHADER, v_source);
        fragment_shader = compile(GL_FRAGMENT_SHADER, f_source);
        prog = glCreateProgram();
        glAttachShader(prog, vertex_shader);
        glAttachShader(prog, fragment_shader);
        glLinkProgram(prog);
    }

    operator GLuint() { return prog; }
    void operator()() { glUseProgram(prog); }

    ~shader_prog() {
        glDeleteProgram(prog);
        glDeleteShader(vertex_shader);
        glDeleteShader(fragment_shader);
    }
};
Run Code Online (Sandbox Code Playgroud)

对于一个简单的演示,一些"传递"着色器(只是模仿固定功能管道):

const GLchar *vertex_shader[] = {
    "void main(void) {\n",
    "    gl_Position = ftransform();\n",
    "    gl_FrontColor = gl_Color;\n",
    "}"
};

const GLchar *color_shader[] = {
    "void main() {\n",
    "    gl_FragColor = gl_Color;\n",
    "}"
};
Run Code Online (Sandbox Code Playgroud)

您使用的类似于:

void draw() { 
    // compile and link the specified shaders:
    static shader_prog prog(vertex_shader, color_shader);

    // Use the compiled shaders:    
    prog(); 

    // Draw something:
    glBegin(GL_TRIANGLES);
        glColor3f(0.0f, 0.0f, 1.0f);
        glVertex3f(-1.0f, 0.0f, -1.0f);
        glColor3f(0.0f, 1.0f, 0.0f);
        glVertex3f(1.0f, 0.0f, -1.0f);
        glColor3f(1.0f, 0.0f, 0.0f);
        glVertex3d(0.0, -1.0, -1.0);
    glEnd();
}
Run Code Online (Sandbox Code Playgroud)

如果你要使用,例如,一些在绘制场景的过程中不同的片段着色的,您只需定义每个静态对象,然后执行prog1();,prog2();等等,只是事先绘制你希望与阴影的对象每个着色器.例如,

void draw() { 
    static shader_prog wall_shader("wall_vertex", "wall_frag");
    static shader_prog skin_shader("skin_vertex", "skin_frag");

    wall_shader();
    draw_walls();

    skin_shader();
    draw_skin();
}
Run Code Online (Sandbox Code Playgroud)

编辑:正如@rotoglup非常正确地指出的,这种static变量的使用延迟了破坏直到OpenGL上下文被破坏之后,所以当析构函数尝试使用glDeleteProgram/时glDeleteShader,结果是不可预测的(充其量).

虽然这在演示程序中可能是可以原谅的,但它在实际使用中显然是不可取的.同时,每次输入使用它们的函数时,通常希望重新编译着色器.

为了避免这两个问题,您通常希望将着色器对象创建为类实例的成员,而这些类实例的生命周期又依赖于它将变为阴影的生命周期:

class some_character_type { 
    shader_prog skin_shader;
public:
    // ...
};
Run Code Online (Sandbox Code Playgroud)

这将在您创建该类型的角色时编译/链接着色器程序一次,并在销毁该角色时将其销毁.

当然,在少数情况下,这也不是完全可取的.例如,考虑一下古代"杀死大量目标"游戏的3D版本,如Galaga或Centipede.对于像这样的游戏,你可以相对快速地创建和销毁许多基本相同的目标.给定大量基本相同的目标,您可能希望使用类似a的东西shared_ptr<shader_prog>来创建在特定目标类型的所有实例之间共享的着色器的单个实例.鉴于您多次重复使用相同的目标类型,您可能希望比此更进一步,因此您在整个游戏中保持相同的着色器,而不仅仅是在显示特定类型的目标时.

无论如何,我们在这里有点偏离轨道.关键是编译和链接着色器是一个相当昂贵的过程,所以你通常想要管理它们的生命周期,以避免比真正必要的更多地创建和销毁它们(尽管这并不是说在所有这些过程中创建它们都是至关重要的)游戏的开始,只在最后摧毁它们,或者).

  • 很抱歉对这个旧答案发表评论,但因为它出现在opengl faq问题中可能是值得的:我发现你对`shader_prog`的实例大胆使用`static`,这会将析构函数调用推迟到这个过程的结束,OpenGL上下文将很快消失.这通常会导致应用程序在退出时崩溃.这可能适用于测试应用程序,但不适用于生产用途. (6认同)
  • 我不知道,如果你知道,但在c ++中你可以这样做:"STRING_PART_1"\n [代码中的新行]"STRING_PART_2".所以你可以创建字符串,并将其作为程序传递,而不是字符串数组(不同的是,你不必在定义数组时使用逗号) (2认同)