OpenGL计算着色器——奇怪的结果

8
我正在尝试实现一个多通道计算着色器来进行图像处理。每个通道都有一个输入图像和一个输出图像。下一个通道的输入图像是前一个通道的输出图像。
这是我第一次在OpenGL中使用计算着色器,所以我的设置可能会有一些问题。我使用OpenCV的Mat作为容器来进行读取/复制操作。
代码中有一些与问题无关的部分,因此我没有包括它们。其中一些部分包括加载图像或初始化上下文。

初始化:

//texture init
glGenTextures(1, &feedbackTexture_);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, feedbackTexture_);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);

glGenTextures(1, &resultTexture_);
glActiveTexture(GL_TEXTURE0+1);
glBindTexture(GL_TEXTURE_2D, resultTexture_);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);

// shader init
computeShaderID = glCreateShader(GL_COMPUTE_SHADER);
glShaderSource(computeShaderID, 1, &computeShaderSourcePtr, &computeShaderLength);
glCompileShader(computeShaderID);
programID = glCreateProgram();
glAttachShader(programID, computeShaderID);
glLinkProgram(programID);
glDeleteShader(computeShaderID);

着色器代码:

//shader code (simple invert)
#version 430
layout (local_size_x = 1, local_size_y = 1) in;

layout (location = 0, binding = 0, /*format*/ rgba32f) uniform readonly image2D inImage;
layout (location = 1, binding = 1, /*format*/ rgba32f) uniform writeonly image2D resultImage;

uniform writeonly image2D image;

void main()
{
    // Acquire the coordinates to the texel we are to process.
    ivec2 texelCoords = ivec2(gl_GlobalInvocationID.xy);

    // Read the pixel from the first texture.
    vec4 pixel = imageLoad(inImage, texelCoords);

    pixel.rgb = 1. - pixel.rgb;

    imageStore(resultImage, texelCoords, pixel);
}

使用方法:

cv::Mat image = loadImage().clone();
cv::Mat result(image.rows,image.cols,image.type());
// These get the appropriate enums used by glTexImage2D
GLenum internalformat = GLUtils::getMatOpenGLImageFormat(image);
GLenum format = GLUtils::getMatOpenGLFormat(image);
GLenum type = GLUtils::getMatOpenGLType(image);

int dispatchX = 1;
int dispatchY = 1;

for ( int i = 0; i < shaderPasses_.size(); ++i)
{
    // Update textures
    glBindTexture(GL_TEXTURE_2D, feedbackTexture_);
    glTexImage2D(GL_TEXTURE_2D, 0, internalformat, result.cols, result.rows, 0, format, type, result.data);
    glBindTexture(GL_TEXTURE_2D, resultTexture_);
    glTexImage2D(GL_TEXTURE_2D, 0, internalformat, image.cols, image.rows, 0, format, type, 0);
    glBindTexture(GL_TEXTURE_2D, 0);

    glClear(GL_COLOR_BUFFER_BIT);
    std::shared_ptr<Shader> shaderPtr = shaderPasses_[i];
    // Enable shader
    shaderPtr->enable();
    {
        // Bind textures
        // location = 0, binding = 0
        glUniform1i(0,0);
        // binding = 0
        glBindImageTexture(0, feedbackTexture_, 0, GL_FALSE, 0, GL_READ_ONLY, internalformat);
        // location = 1, binding = 1
        glUniform1i(1,1);
        // binding = 1
        glBindImageTexture(1, resultTexture_, 0, GL_FALSE, 0, GL_WRITE_ONLY, internalformat);

        // Dispatch rendering
        glDispatchCompute((GLuint)image.cols/dispatchX,(GLuint)image.rows/dispatchY,1);
        // Barrier will synchronize
        glMemoryBarrier(GL_TEXTURE_UPDATE_BARRIER_BIT);
    }
    // disable shader
    shaderPtr->disable();

    // Here result is now the result of the last pass.
}

有时候我会得到奇怪的结果(有故障的纹理,部分渲染的纹理),而且有时候第一个像素(在0,0)没有被写入。 我设置了一切正确吗还是缺少什么东西? 这种使用纹理的方法似乎非常慢,有没有任何替代方案可以提高性能?
编辑1:更改了memorybarrier标志。
3个回答

5

哦,我错过了那部分,谢谢澄清。另外,有没有什么方法可以增加上传/下载部分的速度?我的图像很少会在程序中改变大小,它们大约以30帧每秒的速度传输。 - Gábor Fekete
好的,看起来标志的变化没有产生作用,第一个像素仍然在闪烁,有时整个图像会出现奇怪的伪影。 - Gábor Fekete

4

我终于解决了这个问题!

问题出在cv::Mat的构造函数上。下面这行代码只会为cv::Mat创建一个头部:

cv::Mat result(image.rows,image.cols,image.type());

它确实分配数据,但它不会初始化这些数据,这就是我得到这些奇怪结果的原因。在内存中是垃圾数据。

使用任何一个既分配又初始化数据的函数都可以解决这个问题:

cv::Mat::zeros
cv::Mat::ones
cv::Mat::create

1
严格来说,您的初始调用 cv::Mat result(image.rows,image.cols,image.type()); 应该 分配 图像缓冲区,就像 cv::Mat::create 一样,但它不会 初始化 像素值。 cv::Mat::zeroscv::Mat::ones 都分配并初始化像素值。 - BConic

3

我不确定这是否能解决你的问题,但我没有看到你初始化纹理设置的标志有任何明显错误。当我将你的代码与我的项目进行比较时,引起了我的注意的是API调用的顺序。在你的源代码中,你按照以下顺序:

glGenTextures(...);    // Generate
glActiveTexture(...);  // Set Active
glBindTexture(...);    // Bind Texture
glTexParameteri(...);  // Wrap Setting
glTexParameteri(...);  // Wrap Setting
glTexParameteri(...);  // Mipmap Setting
glTexParameteri(...);  // Mipmap Setting
glBindTexture(...);    // Bind / Unbind

对于每个纹理,除了传递纹理变量和增加id值外,您需要重复此过程。

我不知道这是否有所区别,但在我的引擎中,按照我设置的逻辑路径进行尝试,并查看是否有任何区别。

glGenTextures(...);    // Generate
glBindTexture(...);    // Bind Texture
glTexParameteri(...);  // Wrap Setting
glTexParameteri(...);  // Wrap Setting
glTexParameteri(...);  // Mipmap Setting
glTexParameteri(...);  // Mipmap Setting

glActiveTexture(...);  // Set Active
glBindTexture(...);    // Bind / Unbind

我没有使用计算着色器,但是在我的引擎中有几个管理不同功能的类。我有一个资源存储类来将所有资源保存到内存数据库中,包括图像纹理,我有一个着色器管理类来管理不同的着色器,目前仅使用顶点和片元着色器。它将读取和编译着色器文件,创建着色器程序,设置属性和统一变量,链接程序并运行着色器。我使用批处理过程,其中有一个批处理类和一个批处理管理器类来渲染不同类型的基元。因此,当我查看我的解决方案并跟随代码的路径或逻辑流时,这就是我在代码中看到的。

正是AssetStorage类正在为纹理设置属性,并在其add()函数中按照以下顺序调用这些API调用以将纹理添加到内存中。

 glGenTextures(...);
 glBindTextures(...);
 glTexParameteri(...);
 glTexParameteri(...);
 glTexParameteri(...);
 glTexParameteri(...);

AssetStorage 也在调用这些东西。
glPixelStorei(...);
glTexImage2D(...)

添加纹理到AssetStorage的函数最终将返回一个名为TextureInfo对象的自定义结构。当我检查我的Batch类的render()函数时,它调用ShaderManager的函数来设置uniform以使用纹理,然后调用ShaderManager的函数来设置纹理,如果纹理包含alpha通道,则再次设置uniform。在ShaderManger类中,setTexture()函数是最终调用glActiveTexture()和glBindTexture()的地方。简而言之,请尝试将glActiveTexture()调用移动到两个纹理的最后一个glTexParameter()和最后一个glBindTexture()调用之间。我认为它也应该在这两个调用之后出现,即在glPixelStorei()和glTexImage2D()之后,因为你想在渲染之前激活纹理。正如我之前提到的,我不确定这是否是问题的根本原因,但我相信值得一试,看看它是否有帮助。如果你尝试了这个方法,请告诉我结果如何。我想知道这些API调用的顺序是否会产生影响。请注意,你纹理设置的标志只在wrap/repeat部分中。你可以尝试在前两个glTexParameteri()调用中使用GL_REPEAT,而不是使用GL_CLAMP_TO_EDGE,并让我知道你的结果。对于最后两个glTexParameteri()调用,你不必担心mipmap设置,因为从你使用的设置中似乎没有使用mipmap。

1
感谢您抽出时间并关注我的问题。我不久前已经找到了解决方案,答案实际上不是gl调用的顺序或纹理参数,而是OpenCV的Mat构造函数。我很快会提供一个答案。 - Gábor Fekete
@GáborFekete 哦,好的,是的,错误可能来自外部源或第三方库。 - Francis Cugler
调试OpenCV比OpenGL更容易。OpenGL的状态都没问题,除了一些标志和你提到的glActiveTexture。我犯了个错误,依赖其中一个库...永远不要相信它们!:D - Gábor Fekete
@GáborFekete 我在使用OpenGL时唯一使用的主要第三方库是GLM数学库和用于加载PNG图像的库,而对于音频,我使用alut和ogg/vorbis。 - Francis Cugler
1
我需要opencv,因为项目的其余部分都使用它进行图像处理。这就是为什么它是必需的。这个带有opengl的功能利用GPU进行图像处理。 - Gábor Fekete
我理解,有时候你需要使用特定的媒介、平台和集成开发环境,同样地,使用特定的库也是如此。 - Francis Cugler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接