如何将着色器中生成的浮点数据(而不是颜色)拉回到 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());
我写了一个生成噪声的着色器,它使用 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());