我的印象是你可以渲染到一个屏幕外的帧缓冲区,附带一个纹理,然后使用着色器修改纹理,然后使用 glReadPixels 来取回修改后的数据。这就是我想要做的。
好吧,所以你想通过片段着色器提供纹理以获得新纹理。首先,您必须记住,您不能仅就地修改纹理,因为您无法从当前渲染的纹理中读取。您必须将要修改的纹理作为普通纹理输入到片段着色器中,然后像往常一样将结果放入帧缓冲区,这可能是一个附加了不同纹理的 FBO,一个渲染缓冲区(如果你想读回它无论如何)或默认帧缓冲区。如果您只想将一个图像转换为另一个图像,则不需要 FBO,仅当您希望将结果写入屏幕外缓冲区或纹理时。
此外,您仍然需要绘制一些东西才能让光栅器生成实际的片段来调用片段着色器。执行此操作的常用方法是仅绘制一个与视平面平行的屏幕大小的四边形,以便用片段填充整个视口:
//initialization code
glGenVertexArrays(1, &quad_vao);
glBindVertexArray(quad_vao);
const GLfloat vertices[] = {
-1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f };
glGenBuffers(1, &quad_vbo);
glBindBuffer(GL_ARRAY_BUFFER, quad_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
glDeleteBuffers(1, &quad_vbo);
...
//render code
glBindVertexArray(quad_vao);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
作为顶点着色器,一个简单的直通着色器就足够了,因为顶点位置已经在剪辑空间中:
#version 330
layout(location = 0) in vec4 in_position;
void main()
{
gl_Position = in_position;
}
在片段着色器中,我们将纹理作为输入。纹理坐标已经由片段在屏幕上的位置给出,我们只需要通过除以纹理大小来归一化它(或者可以使用aGL_TEXTURE_RECTANGLE
和a对应samplerRect
直接使用片段坐标):
#version 330
uniform sampler2D tex;
uniform vec2 tex_size;
layout(location = 0) out vec4 out_color;
void main()
{
vec4 in_color = texture(tex, gl_FragCoord.xy / tex_size);
out_color = //do whatever you want with in_color;
}
就是这样,修改后的纹理被写入帧缓冲区,无论重定向到哪里或之后您如何处理帧缓冲区数据。
编辑:使用 OpenGL 4.3 及其计算着色器,现在有一种更直接的方法来处理像图像处理这样的非光栅化纯 GPGPU 任务。您可以在常规 2D 域上调用计算着色器(与其他 OpenGL 着色器相比,它更类似于其他 GPU 计算框架,如CUDA或OpenCL )并处理纹理(使用 OpenGL 4.2 的图像加载/存储功能)直接就地。在这种情况下,您只需要相应的计算着色器:
#version 430
layout(local_size_x=32,local_size_y=8) in; //or whatever fits hardware and shader
layout(binding = 0, rgba) uniform image2D img; //adjust format to the actual data
void main()
{
const uint2 idx = gl_GlobalInvocationID.xy;
vec4 color = imageLoad(img, idx);
//do whatever you want with color
imageStore(img, idx, color);
}
然后您需要做的就是将纹理绑定到相应的图像单元(0,在着色器中设置)并在二维图像域上调用计算着色器:
//again use the format that fits the texture data
glBindImageTexture(0, textureId, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA8);
glUseProgram(compute_program); //a program with a single GL_COMPUTE_SHADER
glDispatchCompute(texture_width, texture_height, 1);
仅此而已,您不需要 FBO,不需要任何其他着色器,不需要绘制任何东西,只需进行原始计算。但必须评估这种更直接的方法是否也能带来更好的性能。同样,您可能需要注意要修改的纹理的正确内存同步,尤其是在之后尝试从中读取时。但请参阅有关图像加载/存储的更深入的材料以获取更多信息。