在 OpenGL 中动态加载着色器毫无意义吗?

Is dynamically load shaders in OpenGL senseless?

我刚刚开始我的图形计算之旅,所以我开始了解这个教程:https://learnopengl.com/Getting-started/Hello-Triangle

在尝试自己做一些 "dynamic" 绘图之前,我认为变换 这变成 "Renderer" class 会很有趣 这是结果:

#pragma once

#include <fstream>
#include <vector>

#include "pch.h"

class Renderer {
public:
    Renderer(GLFWwindow*);

    void initVertexShaders(std::vector<std::string>&);
    void initFragmentShaders(std::vector<std::string>&);
    void load(float*, size_t);
    void draw();

    const GLubyte* renderer;
    const GLubyte* version;

private:
    GLFWwindow* window = nullptr;

    GLuint vbo;
    GLuint vao;

    GLuint shaderProgram = 0; //= glCreateProgram();
    GLuint vs[100];
    GLuint fs[100];
};

pch.h 是一个预编译的头文件,以避免每次构建项目时都编译 glm、glfw 和 glew。

我的想法是隔离不同的实用程序,我会使用 load(float*, size_t) 将信息发送到 gpu,并在主 window 循环期间使用 draw()。这个想法是对二维无相机渲染引擎进行初步近似。

反正源码是:

#include "Renderer.hpp"

Renderer::Renderer(GLFWwindow* window) {
    this->window = window;
    glfwMakeContextCurrent(window);

    glewExperimental = GL_TRUE;
    glewInit();

    this->renderer  =   glGetString(GL_RENDERER);
    this->version   =   glGetString(GL_VERSION);

    this->vao       =   0;
    this->vbo       =   0;
}

void Renderer::load(float* points, size_t memory) {
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, memory, points, GL_STATIC_DRAW);

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
}

void Renderer::draw() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glUseProgram(shaderProgram);
    glBindVertexArray(vao);

    glDrawArrays(GL_TRIANGLES, 0, 6);
}

void Renderer::initVertexShaders(std::vector<std::string>& paths) {
    int i = 0;
    std::ifstream ifs;

    if(this->shaderProgram == 0) shaderProgram = glCreateProgram();

    for (const auto& path : paths){
        ifs.open(path);    
        std::string program_str((  std::istreambuf_iterator<char>(ifs)),
                                (  std::istreambuf_iterator<char>()  ));
        const char* program_src = program_str.c_str();
        vs[i] = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vs[i], 1, &program_src, NULL);
        glCompileShader(vs[i]);
        glAttachShader(shaderProgram, vs[i]);
        i++;
    }
    glLinkProgram(shaderProgram);
}

void Renderer::initFragmentShaders(std::vector<std::string>& paths) {
    int i = 0;
    std::ifstream ifs;

    if(this->shaderProgram == 0) shaderProgram = glCreateProgram();

    for (const auto& path : paths){

        ifs.open();
        std::string program_str((  std::istreambuf_iterator<char>(ifs)),
                                (  std::istreambuf_iterator<char>()  ));
        const char* program_src = program_str.c_str();

        fs[i] = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fs[i], 1, &program_src, NULL);
        glCompileShader(fs[i]);
        glAttachShader(shaderProgram, fs[i]);
        i++;
    }
    glLinkProgram(shaderProgram);
}

问题出在着色器时,我可以毫无问题地读取文件,但它们什么也没做。

问题是:将所有着色器存储在一个数组中是否有意义? 一个着色器程序可以包含多个顶点 shader/fragment 着色器吗? 错误是在另一个地方吗? 整个 class 有任何意义吗?

谢谢。

编辑:来自 main.cpp

的代码
#include "Renderer.hpp"

    float points[] = {
        0.5f,  0.5f,  0.0f,
        0.5f, -0.5f,  0.0f,
        -0.5f, -0.5f, 0.0f,
        //
         0.5f, 0.5f,  0.0f,
        -0.5f, 0.5f,  0.0f,
        -0.5f, -0.5f, 0.0f

    };

int main() {

    std::vector<std::string> vertexShaders;
    std::vector<std::string> fragmentShaders;

    vertexShaders.push_back("shader/vs.glsl");
    fragmentShaders.push_back("shader/fs.glsl");

    glfwInit();                                                         //glfwGetPrimaryMonitor()
    GLFWwindow *window = glfwCreateWindow(960, 540, "Hello Triangle",   NULL, NULL);
    Renderer Ren(window);

    Ren.load(points, sizeof(points));
    Ren.initVertexShaders(vertexShaders);
    Ren.initFragmentShaders(fragmentShaders);

    while (!glfwWindowShouldClose(window)) {
        Ren.draw();
        glfwPollEvents();
        glfwSwapBuffers(window);
    }

    glfwTerminate();
    return 0;
}

在编译所有附加的着色器对象后,link 一次着色器程序对象就足够了。


OpenGL 4.6 API Core Profile Specification; 7.3. PROGRAM OBJECTS; page 94:

Shader objects may be attached to program objects before source code has been loaded into the shader object, or before the shader object has been compiled or specialized.

注意,着色器对象成功编译就足够了,然后它所附加的着色器程序对象得到 linked。


OpenGL 4.6 API Core Profile Specification; 7.3. PROGRAM OBJECTS; page 94:

Multiple shader objects of the same type may be attached to a single program object, and a single shader object may be attached to more than one program object.

例如

一个着色器对象包含一个函数(FragColor

#version 460

uniform sampler2D u_texture;

vec4 FragColor(vec2 uv)
{
    return texture(u_texture, uv);
}

相同类型的第二个着色器对象包含函数签名(但不包含实现)和函数的用法。

#version 460

in vec2 vUV;
out vec4 fragColor;

vec4 FragColor(vec2 uv);

void main()
{
    fragColor = FragColor(vUV);
}

以上两个代码片段都可以放置到 2 个单独的 GL_FRAGMENT_SHADER 类型的着色器对象中。 2 个着色器对象中的每一个都可以成功编译。并且如果它们附加到同一个shader program对象,那么shader program对象可以成功被点赞。

另见 Attaching multiple shaders of the same type in a single OpenGL program?


此外,我建议检查着色器对象是否已成功编译:

GLuint shaderObj = .... ;
glCompileShader( shaderObj );

GLint status = GL_TRUE;
glGetShaderiv( shaderObj, GL_COMPILE_STATUS, &status );
if ( status == GL_FALSE )
{
    GLint logLen;
    glGetShaderiv( shaderObj, GL_INFO_LOG_LENGTH, &logLen );
    std::vector< char >log( logLen );
    GLsizei written;
    glGetShaderInfoLog( shaderObj, logLen, &written, log.data() );
    std::cout << "compile error:" << std::endl << log.data() << std::endl;
}

并且着色器程序对象已成功 link编辑:

GLuint progObj = ....;
glLinkProgram( progObj );

GLint status = GL_TRUE;
glGetProgramiv( progObj, GL_LINK_STATUS, &status );
if ( status == GL_FALSE )
{
    GLint logLen;
    glGetProgramiv( progObj, GL_INFO_LOG_LENGTH, &logLen );
    std::vector< char >log( logLen );
    GLsizei written;
    glGetProgramInfoLog( progObj, logLen, &written, log.data() );
    std::cout  << "link error:" << std::endl << log.data() << std::endl;
}