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

Mar*_*ram 8 opengl vertex-shader fragment-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);
Run Code Online (Sandbox Code Playgroud)

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

空顶点着色器:

#version 330

void main()
{
    gl_Position = ??;
}
Run Code Online (Sandbox Code Playgroud)

空片段着色器:

#version 330

layout(location = 0) out vec4 out_colour;

void main()
{
    out_colour = ???;
}
Run Code Online (Sandbox Code Playgroud)

Chr*_*ica 16

我的印象是你可以使用附加纹理渲染到屏幕外帧缓冲区,然后使用着色器修改纹理,然后使用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);
Run Code Online (Sandbox Code Playgroud)

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

#version 330

layout(location = 0) in vec4 in_position;

void main()
{
    gl_Position = in_position;
}
Run Code Online (Sandbox Code Playgroud)

在片段着色器中,我们将纹理作为输入.纹理坐标已经由片段在屏幕上的位置给出,我们只需要通过划分纹理大小来标准化它(或者可以使用a 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;
}
Run Code Online (Sandbox Code Playgroud)

就是这样,修改后的纹理被写入帧缓冲区,无论重定向的位置或之后使用帧缓冲区数据做什么.


编辑:使用OpenGL 4.3及其计算着色器,现在有一种更直接的方式可用于非光栅化纯GPGPU任务,如图像处理.您可以在常规2D域上调用计算着色器(与其他GPU计算框架(如CUDAOpenCL,比其他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);
}
Run Code Online (Sandbox Code Playgroud)

然后,您需要做的就是将纹理绑定到相应的图像单元(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);
Run Code Online (Sandbox Code Playgroud)

这就是全部,你不需要一个FBO,你不需要任何其他着色器,你不需要绘制任何东西,只需要原始计算.但是,如果这种更直接的方法也能带来更好的性能,则必须对其进行评估.同样,您可能需要注意要修改纹理的正确内存同步,尤其是在尝试从之后读取它时.但请参阅更深入的图像加载/存储材料以获取更多信息.