如何为附加纹理的帧缓冲区编写传递顶点和片段着色器?

时间:2013-06-12 11:04:06

标签: opengl fragment-shader vertex-shader

我试图使用着色器来修改绑定到帧缓冲区的纹理,但是我对着色器如何获得"原始"而感到困惑。输入值。

我正在做以下事情:

GLuint textureId = 0;
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexImage2D(GL_TEXTURE_2D, ...);

GLuint framebufferId = 0;
glGenFramebuffers(1, &framebufferId);
glBindFramebuffer(GL_FRAMEBUFFER, frameBufferId);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureId, 0);
glBindTexture(GL_TEXTURE_2D, 0);

GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) { ... }

glUseProgram(programId);
const GLenum buffer = GL_COLOR_ATTACHMENT0;
glDrawBuffers(1, &buffer);

空顶点和片段着色器看起来像什么?由于我没有绘制灵长类动物,如何在顶点着色器中设置gl_Position?如何通过输入颜色作为片段着色器的输出颜色?

空顶点着色器:

#version 330

void main()
{
    gl_Position = ??;
}

空片段着色器:

#version 330

layout(location = 0) out vec4 out_colour;

void main()
{
    out_colour = ???;
}

1 个答案:

答案 0 :(得分:16)

  

我的印象是你可以渲染到屏幕外   framebuffer,附加纹理,然后使用着色器修改   纹理,然后使用glReadPixels来获取修改后的数据。这是   我正在努力做什么。

好的,所以你想通过片段着色器提供纹理以获得新的纹理。首先,你必须记住,你不能只是就地修改纹理,因为你无法从你当前渲染的纹理中读取。您必须将要修改的纹理作为普通纹理输入到片段着色器中,并像往常一样将结果输出到帧缓冲区中,这可能是附加不同纹理的FBO,渲染缓冲区(如果你想将它读回CPU,无论如何),或默认的帧缓冲。如果您只想将一个图像转换为另一个图像,只需要将结果写入屏幕外缓冲区或纹理,就不需要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);

作为顶点着色器,一个简单的pass-thru着色器就足够了,因为顶点位置已经在剪辑空间中:

#version 330

layout(location = 0) in vec4 in_position;

void main()
{
    gl_Position = in_position;
}

在片段着色器中,我们将纹理作为输入。纹理坐标已经由片段在屏幕上的位置给出,我们只需要通过划分纹理大小来标准化(或者使用GL_TEXTURE_RECTANGLE和相应的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域和进程上调用计算着色器(与其他GPU计算框架更相似,如 CUDA OpenCL ,而不是其他OpenGL着色器)一个纹理(使用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,你不需要任何其他着色器,你不需要绘制任何东西,只需要原始计算。但是,如果这种更直接的方法也能带来更好的性能,则必须对其进行评估。同样,您可能需要注意要修改纹理的正确内存同步,尤其是在尝试从之后读取它时。但请参阅有关图像加载/存储的更深入的资料以获取更多信息。