Search code examples
c++openglglsltexturesstb-image

Writing to a 3D Texture Using Framebuffers and Saving as Image


I'm trying to generate a 3D worley noise texture for some volumetric effects. I am simply generating a set of random points and for each fragment, I will find the closest point and get the distance between the fragment and the pixel. The distance is then used as the final color of the image. The result looks something like this in 2D: Worley

This works very well when I generate a 2D texture. I start running into some issues when trying to generate a 3D texture.

Right now I attach each slice of the 3D texture as a color attachment and write to it using a shader. My Code is as follows:

Function to generate noise (Note: this code assumes that width, height and depth are all equal):

    void WorleyGenerator::GenerateWorley3D(int numPoints, Texture3D* texture, PrimitiveShape* quad, unsigned int nativeWidth, unsigned int nativeHeight)
{
    int resolution = texture->GetWidth();
    glViewport(0, 0, resolution, resolution);
    glDisable(GL_CULL_FACE);

    frameBuffer->Bind();
    shader3D->Bind();

    shader3D->SetFloat("uResolution", resolution);
    shader3D->SetInt("uNumWorleyPoints", numPoints); // Tell shader how many points we have

    // Generate random points and pass them ot shader
    for (int i = 0; i < numPoints; i++)
    {
        shader3D->SetFloat3("uPointData[" + std::to_string(i) + "]", glm::vec3(Utils::RandInt(0, resolution), Utils::RandInt(0, resolution), Utils::RandInt(0, resolution)));
    }

    // Render to each "slice" of the texture
    for (int z = 0; z < resolution; z++)
    {
        shader3D->SetInt("uCurrentSlice", z);
        glFramebufferTexture3D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_3D, texture->GetID(), 0, z);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        quad->Draw();
    }

    frameBuffer->Unbind();

    glViewport(0, 0, nativeWidth, nativeHeight);
}

My fragment shader:

#version 420 

in vec2 mTextureCoordinates;

out vec4 oColor;

uniform int uCurrentSlice;
uniform float uResolution;
uniform int uNumWorleyPoints;
uniform vec3 uPointData[256];

void main()
{
    vec3 fragPos = vec3(gl_FragCoord.xy, uCurrentSlice) / uResolution; // Convert frag pos to range 0 - 1

    // Find closest point's distance
    float closestDist = 100000.0f;

    // Get closest point for red channel
    for (int i = 0; i < uNumWorleyPoints; i++)
    {
        vec3 p = uPointData[i] / uResolution; // Convert point to range 0 - 1

        float dist = distance(fragPos, p);
        if (dist < closestDist)
        {
            closestDist = dist;
        }
    }

    oColor = vec4(closestDist, closestDist, closestDist, 1.0f);
}

Now to test my code, I will save each slice of the 3D texture to a BMP file:

void Utils::SaveTexture3DAsBMP(const std::string& savePath, Texture3D* texture)
{
    unsigned int size = texture->GetWidth() * texture->GetHeight() * 4;
    float* data = new float[size];
    for (int z = 0; z < texture->GetDepth(); z++)
    {
        glGetTextureSubImage(texture->GetID(), 0, 0, 0, z, texture->GetWidth(), texture->GetHeight(), z, GL_RGBA16F, GL_FLOAT, size, data);
        stbi_write_bmp((savePath + std::to_string(z) + ".bmp").c_str(), texture->GetWidth(), texture->GetHeight(), 4, data);
    }

    delete[] data;
}

The result of the saved images looks something like this:

worleyFail

Moreover, each slice is exactly the same which should never happen.

I suspect that I am either incorrectly writing to a slice, or my code to save the image is wrong.

Any help would be appreciated!


Solution

  • My issue was not with the texture but rather with how I was saving the images. I changed my texture saving function from this:

    void Utils::SaveTexture3DAsBMP(const std::string& savePath, Texture3D* texture)
    {
        unsigned int size = texture->GetWidth() * texture->GetHeight() * 4;
        float* data = new float[size];
        for (int z = 0; z < texture->GetDepth(); z++)
        {
            glGetTextureSubImage(texture->GetID(), 0, 0, 0, z, texture->GetWidth(), texture->GetHeight(), z, GL_RGBA16F, GL_FLOAT, size, data);
            stbi_write_bmp((savePath + std::to_string(z) + ".bmp").c_str(), texture->GetWidth(), texture->GetHeight(), 4, data);
        }
    
        delete[] data;
    }
    

    To this:

    void Utils::SaveTexture3DAsBMP(const std::string& savePath, Texture3D* texture)
    {
        unsigned int size = texture->GetWidth() * texture->GetHeight() * 4;
        uint8_t* data = new uint8_t[size];
    
        for (int z = 0; z < texture->GetDepth(); z++)
        {
            glGetTextureSubImage(texture->GetID(), 0, 0, 0, z, texture->GetWidth(), texture->GetHeight(), 1, GL_RGBA, GL_UNSIGNED_BYTE, size, data);
            stbi_write_bmp((savePath + std::to_string(z) + ".bmp").c_str(), texture->GetWidth(), texture->GetHeight(), 4, data);
        }
    
        delete[] data;
    }