如何使用计算着色器计算值并将它们存储在 3D 纹理中?

How can I use a compute shader to calculate values and store them in a 3D texture?

我正在尝试使用计算着色器进行三维物理模拟,但无法将任何内容存储到我的 3D 纹理中。我的着色器编译成功,但是当从 3D 纹理中读回任何值时,我得到一个零向量。我之前也没有使用过计算着色器,所以我不确定我是否正确地分配了工作负载以实现我想要的。

我已经在此处的一个小示例中隔离了问题。基本上 compute.glsl 着色器具有统一的 image3D 并使用 imageStore 将 vec4(1,1,0,1) 写入 gl_WorkGroupID。在 C++ 中,我创建了一个 100x100x100 的 3D 纹理并将其绑定到着色器的制服,然后调用 glDispatchCompute(100,100,100) - 据我所知,这将创建 1,000,000 jobs/shader 次调用,纹理中的每个坐标调用一次。在我的 view.glsl 片段着色器中,我读取了一个随机坐标的值(在本例中为 (3,5,7))并输出它。我用这个阴影一个立方体对象。

我尝试过的所有操作都会导致输出黑色立方体:

这是我的代码(我一直在关注 learnopengl.com,所以除了我扩展了着色器 class 来处理计算着色器之外,它基本上是相同的样板内容):

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <stb_image.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <learnopengl/shader_m.h>

#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

struct vaoinfo
{
    unsigned int VBO, VAO, EBO;
    vaoinfo() : VAO(0), VBO(0), EBO(0)
    {}
};

void create_vao(vaoinfo& info)
{
    glGenVertexArrays(1, &info.VAO);
    glGenBuffers(1, &info.VBO);
    glGenBuffers(1, &info.EBO);
}

void init_vao(vaoinfo& info, float* vertices, int num_vertices, int* indices, int num_indices)
{
    glGenVertexArrays(1, &info.VAO);
    glGenBuffers(1, &info.VBO);
    glGenBuffers(1, &info.EBO);
    glBindVertexArray(info.VAO);
    glBindBuffer(GL_ARRAY_BUFFER, info.VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * num_vertices, vertices, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, info.EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * num_indices, indices, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glBindVertexArray(0);
}

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    vaoinfo cube;
    create_vao(cube);

    glm::vec3 c(0.5, 0.5, 0.5);
    float verts[24] =
    {
         c.x, -c.y,  c.z,
         c.x,  c.y,  c.z,
        -c.x,  c.y,  c.z,
        -c.x, -c.y,  c.z,

         c.x, -c.y, -c.z,
         c.x,  c.y, -c.z,
        -c.x,  c.y, -c.z,
        -c.x, -c.y, -c.z,
    };

    int indices[36] =
    {
        7, 4, 5,
        5, 6, 7,

        3, 0, 1,
        1, 2, 3,

        2, 6, 7,
        7, 3, 2,

        1, 5, 4,
        4, 0, 1,

        7, 4, 0,
        0, 3, 7,

        6, 5, 1,
        1, 2, 6
    };

    init_vao(cube, verts, 24, indices, 36);

    // Create a 3D texture
    unsigned int texId;
    glGenTextures(1, &texId);
    glBindTexture(GL_TEXTURE_3D, texId);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8_SNORM, 100, 100, 100, 0, GL_RGBA, GL_FLOAT, nullptr);

    // Create shaders
    Shader computeShader("compute.glsl");
    Shader viewShader("coords.glsl", "view.glsl");
    
    while (!glfwWindowShouldClose(window))
    {
        processInput(window);

        computeShader.use();
        computeShader.setInt("img", 0);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_3D, texId);
        glDispatchCompute(100, 100, 100);
        glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


        viewShader.use();
        viewShader.setInt("img", 0);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_3D, texId);

        glm::mat4 model = glm::mat4(1.0f); // make sure to initialize matrix to identity matrix first
        glm::mat4 view = glm::mat4(1.0f);
        glm::mat4 projection = glm::mat4(1.0f);
        model = glm::rotate(model, (float)glfwGetTime(), glm::vec3(0.5f, 1.0f, 0.0f));
        view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
        projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        
        unsigned int modelLoc = glGetUniformLocation(viewShader.ID, "model");
        unsigned int viewLoc = glGetUniformLocation(viewShader.ID, "view");
        unsigned int projLoc = glGetUniformLocation(viewShader.ID, "projection");
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));

        // render cube
        glBindVertexArray(cube.VAO);
        glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, nullptr);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}

void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}

compute.glsl:

#version 430

layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(rgba8_snorm, binding=0) uniform image3D img;

uniform vec3 position;

void main()
{
    ivec3 voxel_coord = ivec3(gl_WorkGroupID);
    imageStore(img, voxel_coord, vec4(1, 1, 0, 1));
}

coords.glsl(顶点着色器):

#version 430 core
layout (location = 0) in vec3 aPos;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0f);
}

view.glsl(片段着色器):

#version 430 core
out vec4 FragColor;

layout(rgba8_snorm, binding=0) uniform image3D img;

void main()
{
    FragColor = imageLoad(img, ivec3(3,5,7));
}

原来我错过了对 glBindImageTexture 的调用 - 我认为为了将我的纹理绑定到着色器的图像变量,我需要设置制服并调用 glActiveTexture+glBindTexture 但似乎只需要 glBindImageTexture。

我替换了:

computeShader.setInt("img", 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_3D, texId)

与:

glBindImageTexture(0, texId, 0, true, 0, GL_WRITE_ONLY, GL_RGBA16);