如何将着色器中生成的浮点数据(而不是颜色)拉回到 CPU?

How can I pull back float data, not color, generated in a shader to the CPU?

我写了一个生成噪声的着色器,它使用 gl_TexCoord[0].xy(由 SFML 生成的东西)作为输入并为每个像素输出一个浮点数。

我想要 CPU 上的数据,作为 32 位浮点数的数组。

这是我正在做的事情:

//using opengl 3.0 (I also tried 3.2)
ContextSettings settings(0, 0, 0, 3, 0);
sf::RenderWindow window(sf::VideoMode(windowsize.x, windowsize.y),
    "yada", 7, settings
);

这里我创建贴图后覆盖贴图。我正在使用 windows,它只有 opengl1.1 headers,所以我必须手动设置 GL_R32F(很高兴由 dav1d 完成):

#ifndef GL_R32F 
#define GL_R32F 0x822E // found this in glad.h for opengl 3.0, easy guess is that this value doesn't change across opengl versions
#endif
auto handle = sprite_perlin.getTexture()->getNativeHandle(); 
glBindTexture(GL_TEXTURE_2D, handle);
auto size = sprite_perlin.getTexture()->getSize();
glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, size.x, size.y, 0, GL_RED, GL_FLOAT, NULL);
        }

这里我把纹理数据读入值:

float values[4096];
// whole_size is the size of the texture, here it is 60
if(whole_size*whole_size < 4096)
    glGetTexImage(GL_TEXTURE_2D, 0, GL_RED, GL_FLOAT, values);

Image brought_back;
brought_back.create(whole_size, whole_size, Color::Red);

float mi = 1e20, ma = -1e20;

for (int i = 0; i < whole_size; ++i) {
    for (int j = 0; j < whole_size; ++j) {
        brought_back.setPixel(i, j, { 0,Uint8(255.f*values[whole_size*i + j]),0 });
        ma = max(values[whole_size*i + j], ma);
        mi = min(values[whole_size*i + j], mi);
    }
}

mi和ma都为0.0f;

这是在带有着色器的精灵上显示时产生的噪音(带有一些采用浮点数的颜色函数)着色器按预期工作,我只想在 CPU.

这是着色器的简化版本:

uniform int texture_size;

float get_value_at(ivec2 pixel_coord) {
    //dummy value
    return 0.34f;
}

void main(){
    ivec2 pix_pos = ivec2(gl_TexCoord[0].xy*texture_size);

    float val = get_value_at(pix_pos);

    gl_FragColor = vec4(val,0,0,1);
}

How can I pull back float data, [...]

片段着色器的输出被写入帧缓冲区。精度取决于帧缓冲区的格式。 Default Framebuffer 的格式(可能)是每个颜色通道 1 个字节。因此,从缓冲区读取颜色时使用哪种类型并不重要。当颜色写入缓冲区时,精度会丢失。

如果您想以更高的精度存储颜色,则必须使用可以更高精度存储值的格式呈现 Framebuffer, that uses a Renderbuffer or Texture
参见 Image Format

如果你想渲染到帧缓冲区,那么我建议阅读像 Framebuffers or Render To Texture respectively Khronos wiki - Framebuffer Object Extension Examples 这样的教程。

无论如何,我会提供一个例子。您必须设置一个提供内部格式 GL_R32F(或 GL_RGBA32F 用于 4 个颜色通道)的帧缓冲区。

使用内部格式 GL_R32F 设置渲染缓冲区并将其附加到帧缓冲区:

// render buffer with internal format `GL_RGBA32F`
GLuint rbo32F = 0;
glGenRenderbuffers(1, &rbo32F);
glBindRenderbuffer(GL_RENDERBUFFER, rbo32F);
glRenderbufferStorage(GL_RENDERBUFFER, GL_R32F, size.x, size.y);

// framebuffer with attached render buffer
GLuint fbo = 0;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo32F);
glBindFramebuffer(GL_FRAMEBUFFER, 0);

或者设置格式为 GL_R32F 的纹理并将其附加到帧缓冲区。如果您只是想创建一个用于进一步渲染的纹理,那么这就是您过去的方法。不需要上传图片到CPU,因为图片已经渲染成纹理,可以进一步使用。

// texture with internal format `GL_R32F`
GLuint tex32F = 0;
glGenTextures(1, &tex32F);
glBindTexture(GL_TEXTURE_2D, tex32F);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, size.x, size.y, 0, GL_RED, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

// framebuffer with attached texture
GLuint fbo = 0;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex32F, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);

在这两种情况下,您都应该验证 frambuffer 的完整性 glCheckFramebufferStatus:

glBindFramebuffer(GL_FRAMEBUFFER, fbo);
int status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
    // error handling
    // [...]
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);

渲染前绑定帧缓冲区:

glBindFramebuffer(GL_FRAMEBUFFER, fbo);

绘制几何体后,glGetTexImage 可以读取纹理。例如:

std::vector<GLfloat> buffer(size.x * size.y * 1); // widht * height * 1 color channel
glBindTexture(GL_TEXTURE_2D, tex32F);
glGetTexImage(GL_TEXTURE_2D, 0, GL_RED, GL_FLOAT, buffer.data());