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

Posted

技术标签:

【中文标题】如何使用计算着色器计算值并将它们存储在 3D 纹理中?【英文标题】:How can I use a compute shader to calculate values and store them in a 3D texture? 【发布时间】:2021-04-22 19:38:25 【问题描述】:

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

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

我尝试过的所有方法都会输出一个黑色立方体:

这是我的代码(我一直在关注 learnopengl.com,所以除了我扩展了着色器类来处理计算着色器之外,它几乎是相同的样板文件):

#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);

计算.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));

【问题讨论】:

我在您的代码中没有看到任何 glGetError() 调用。你确定你的设置和纹理初始化工作正常吗? @HughFisher - 是的,在我的完整(更复杂的)程序中,我在每次 opengl 调用后调用 glGetError 并且没有错误。我可以快速将它添加到上面的重现代码中,但我怀疑它会出现任何问题。 @HughFisher - glGetError 返回 GL_NO_ERROR 【参考方案1】:

原来我错过了对 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);

【讨论】:

以上是关于如何使用计算着色器计算值并将它们存储在 3D 纹理中?的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL 计算着色器中的样本深度缓冲区

Unity学习——纹理材质&着色器

使用着色器进行计算

从片段着色器中的地形高程数据计算法线

计算机图形学----基于3D图形开发技术

什么是依赖纹理读取?