如何在运行时重置 GLFW 片段/加载不同的着色器?

How do I reset the GLFW Fragment / Load a different shader during runtime?

我正在为桌面编写类似 shadertoy 的工具。它使用 GLFW (OpenGL)、Glad 和 ImGui 来渲染图像。

我现在已经解决了所有以前的问题,但我现在陷入了过去几天一直困扰我的一个特定问题。

我希望能够热重载着色器。现在它需要一个简单的 mandelbulb raymarcher GLSL 片段,绘制它,还绘制一个带有“重新加载着色器”按钮的小 ImGui 面板。

我一直在想办法取消链接当前的着色器程序,然后用新的着色器再次加载它,但我一无所获。该代码没有任何错误,但它肯定不起作用。当我单击按钮时,OpenGL 视口变黑。

我试过在网上查找,但基本上没有可靠的解决方案,我已经看到的大多数问题从未真正得到解答。

这是我当前用于重置着色器(并再次从文件中获取它)的代码

        if (ImGui::Button("Reload Shader", ImVec2(0, 0))) {
            glDeleteVertexArrays(1, &VAO);
            glDeleteBuffers(1, &VBO);
            glDeleteProgram(shaderProgram);
            gladLoadGL();
            glViewport(0, 0, width, height);

            vertexShader = glCreateShader(GL_VERTEX_SHADER);
            fragmentShader = loadShaderFromFile("assets\round.glsl", GL_FRAGMENT_SHADER);

            shaderProgram = glCreateProgram();
            glAttachShader(shaderProgram, vertexShader);
            glAttachShader(shaderProgram, fragmentShader);
            glLinkProgram(shaderProgram);
            glDeleteShader(vertexShader);
            glDeleteShader(fragmentShader);

            GLuint VAO, VBO;
            glGenVertexArrays(1, &VAO);
            glGenBuffers(1, &VBO);
            glBindVertexArray(VAO);
            glBindBuffer(GL_ARRAY_BUFFER, VBO);
            glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
            glEnableVertexAttribArray(0);
            glBindBuffer(GL_ARRAY_BUFFER, 0);
            glBindVertexArray(0);

            glUseProgram(shaderProgram);
            GLint resUniform = glGetUniformLocation(shaderProgram, "iResolution");
            glUniform3f(resUniform, width, height, width / height);
            double mxpos, mypos;
            glfwGetCursorPos(window, &mxpos, &mypos);
            GLint mouseUniform = glGetUniformLocation(shaderProgram, "iMouse");
            glUniform4f(mouseUniform, mxpos, mypos, mxpos, mypos);
            GLint timeUniform = glGetUniformLocation(shaderProgram, "iTime");
            glUniform1f(timeUniform, currentTime);
            glBindVertexArray(VAO);
            glDrawArrays(GL_TRIANGLES, 0, 3);

            glfwSwapBuffers(window);
            glfwPollEvents();
        }

这是当前 github 提交的所有代码/整个程序,以备不时之需:

https://github.com/ArctanStudios/OpenGL-Sandbox/tree/e7ba32310b898d1a60eae89f8969179600f5d391

包含我的其余 GLFW 代码的实际 Main.cpp 文件:

https://github.com/ArctanStudios/OpenGL-Sandbox/blob/e7ba32310b898d1a60eae89f8969179600f5d391/Main.cpp

于是折腾了一阵子,还真的想通了。

原来我做的比要求的多得多。

我需要做的就是在开始时分离片段,然后删除所有内容,然后 re-attach 一个新片段,再次 link 程序,然后再次分离并删除它。

现在我可以在运行时重新加载我的着色器。

我在初始化时使用的代码:

    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);

    GLuint fragmentShader = loadShaderFromFile("assets\round.glsl", GL_FRAGMENT_SHADER);

    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    glDetachShader(shaderProgram, fragmentShader);
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

它所做的只是抓住碎片+顶点,附加它们,缓存/link程序到渲染器,然后分离碎片,删除顶点+碎片以节省内存,它准备好了随时被覆盖。

这是我在重新加载按钮中使用的代码:

        if (ImGui::Button("Reload Shader", ImVec2(0, 0))) {
            glDetachShader(shaderProgram, fragmentShader);
            fragmentShader = loadShaderFromFile("assets\round.glsl", GL_FRAGMENT_SHADER);
            glAttachShader(shaderProgram, fragmentShader);
            glLinkProgram(shaderProgram);
            glDetachShader(shaderProgram, fragmentShader);
            glDeleteShader(fragmentShader);
        }

它所做的是抓取当前着色器程序,没有片段,并且已经存在的顶点仍然缓存在它上面。

然后它附加新的片段,links / 再次将程序缓存到渲染器,然后从初始的开始重复该过程。它分离片段,将其删除以节省内存,然后准备好再次覆盖它。

你也可以将它与顶点着色器一起使用,只需分离并删除它,与片段相同。我决定不这样做,因为我的程序的目的是将 2D 着色器渲染到平面上,类似于 shadertoy

要为当前程序使用着色器,您只需执行此操作。

 glUseProgram(shaderID);