渲染到1D纹理

6
我正在尝试几种实现简单粒子系统的方法。 在这里,我使用了多个纹理之间的ping-pong技术,所有纹理都连接到一个唯一的fbo上。
我认为所有的绑定和设置都是正确的,因为我看到它使用A纹理的数据写入B纹理。
问题在于只有B纹理的一个像素被写入:
在这张图片中,我试图将源纹理的像素复制到目标纹理中。
那么让我们来看看代码:

设置代码

#define NB_PARTICLE 5

GLuint fbo;
GLuint tex[6];
GLuint tex_a[3];
GLuint tex_b[3];
int i;

// 3 textures for position/vitesse/couleur
// we double them for ping-pong so 3 * 2 = 6 textures
glGenTextures(6, tex);

for (i = 0 ; i < 6 ; i++ )
{
    glBindTexture(GL_TEXTURE_1D, tex[i]);
    glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, NB_PARTICLE, 0, GL_RGBA, GL_FLOAT, NULL);
    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_CLAMP);
}

for (i = 0; i < 3; i++)
{
    tex_a[i] = tex[i];
    tex_b[i] = tex[i + 3];
}

// Uploads particle data from "a" textures
glBindTexture(GL_TEXTURE_1D, tex_a[0]);
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, NB_PARTICLE, 0, GL_RGBA, GL_FLOAT, seed_pos);
glBindTexture(GL_TEXTURE_1D, tex_a[1]);
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, NB_PARTICLE, 0, GL_RGBA, GL_FLOAT, seed_vit);
glBindTexture(GL_TEXTURE_1D, tex_a[2]);
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, NB_PARTICLE, 0, GL_RGBA, GL_FLOAT, seed_color);

// Create the fbo
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);

// Attach the textures to the corresponding fbo color attachments
int i;
for (i = 0; i < 6; i++)
    glFramebufferTexture1D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_1D, tex[i], 0);

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

渲染代码

static int pingpong = 1;

glUseProgram(integratorShader);
glBindVertexArray(0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);

// Set the input textures to be the "a" textures
int i;
for (i = 0 ; i < 3 ; i++ )
{
    glActiveTexture(GL_TEXTURE0 + i);
    glBindTexture(GL_TEXTURE_1D, tex_a[i]);
}

// Set the draw buffers to be the "b" textures attachments
glDrawBuffers(3, GL_COLOR_ATTACHMENT0 + (pingpong * 3) );

glViewport(0, 0, NB_PARTICLE, 1);

glDrawArrays(GL_POINTS, 0, NB_PARTICLE);

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glUseProgram(0);

// A became B and B became A
swapArray(tex_a, tex_b, 3);
pingpong++;
pingpong %= 2;

顶点着色器

#version 430

void main() {

    gl_Position = vec4(gl_VertexID, 0, 0, 1);

}

片元着色器

#version 430

// binding = texture unit
layout (binding = 0) uniform sampler1D position_texture;
layout (binding = 1) uniform sampler1D vitesse_texture;
layout (binding = 2) uniform sampler1D couleur_texture;

// location = index in the "drawBuffers" array
layout (location = 0) out vec4 position_texel;
layout (location = 1) out vec4 vitesse_texel;
layout (location = 2) out vec4 couleur_texel;

void main() {

    vec4 old_position_texel = texelFetch(position_texture, int(gl_FragCoord.x), 0);
    vec4 old_vitesse_texel =  texelFetch(vitesse_texture, int(gl_FragCoord.x), 0);
    vec4 old_couleur_texel =  texelFetch(couleur_texture, int(gl_FragCoord.x), 0);

    position_texel = old_position_texel;
    vitesse_texel = old_vitesse_texel;
    couleur_texel = old_couleur_texel;
}

由于我正在使用一维纹理,因此我认为我需要发送的唯一数据是索引,并且我可以完全使用gl_VertexID来实现。这就是为什么我发送0个属性数据的原因。

我认为问题在于我设置了gl_FragCoord的方式(可悲的是这是我无法调试的唯一变量 :( )。

2个回答

6
问题在于如何调用着色器。您基本上设置了一个标准的GPGPU片段着色器管道,处理输入纹理的每个像素,并将结果写入输出纹理中相应的像素。但是您调用这个GPGPU管道的方式,即渲染几何图形的方式,完全是垃圾。所有其他的东西,特别是您的片段着色器和使用gl_FragCoord的方式都是完全正确的。
看起来你混淆了计算阶段(在此阶段中,您想要计算粒子位置)和绘图阶段(在此阶段中,您想要呈现粒子,可能作为点)。但是在这个GPGPU阶段,绝对没有必要渲染N个点,因为您在顶点着色器中没有做任何有用的事情。您想要做的只是为帧缓冲区的每个像素生成一个片段。但是,光栅化器已经为您实现了这一点。当您想要在“正常”的图形应用程序中绘制三角形时,您不会自己将该三角形细分为像素,并以GL_POINTS形式绘制它们,对吗?失败的确切错误是您奇怪地使用gl_VertexID作为坐标。由于您不使用任何转换,输出的顶点坐标应位于[-1,1]框中,其他所有内容都会被剪辑掉。但是您的点位于位置(0,0),(1,0),(2,0)...,因此它们无论如何都不会密集地覆盖帧缓冲区,只有在(0,0)处绘制的点才会被绘制,这正是您看到的中心像素((1,0)处的点可能也会由于舍入和其他问题而被剪切掉)。

所以你需要做的就是绘制一个覆盖整个帧缓冲区的四边形。这意味着在没有任何变换的情况下,它应该覆盖完整的剪辑空间,因此也覆盖了[-1,1]正方形(实际上,当使用一个只有一个像素高的帧缓冲区时,甚至一条线也可以)。光栅化器会自动生成你所需的所有片段。你仍然可以在不使用任何属性的情况下实现这一点,只需渲染一个单独的四边形(不激活任何属性):

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

然后使用顶点ID选择四边形的适当角落:

const vec2 corners[4] = { 
    vec2(-1.0, 1.0), vec2(-1.0, -1.0), vec2(1.0, 1.0), vec2(1.0, -1.0) };

void main()
{
    gl_Position = vec4(corners[gl_VertexID], 0.0, 1.0);   
}

这将为每个像素生成一个片段,其他所有内容都应该正常工作。
作为一个旁注,我不确定你是否真的需要一个1D纹理,因为与缓冲区相比,1D纹理有相当严格的尺寸限制。你可以使用2D纹理,这不会改变你当前的处理方式(尽管在实际绘制粒子时可能需要一些小的索引操作)。
事实上,当你已经使用OpenGL 4.3时,做像粒子引擎这样的GPGPU任务的更自然的方法是使用计算着色器。这样,你可以将粒子数据存储在缓冲区对象中,并直接使用计算着色器对其进行操作,而无需将其打包到纹理中(因为你可能想要稍后渲染它们,因此需要在顶点着色器中读取纹理),无需滥用图形管线进行计算任务,也无需任何乒乓存储(只需在原地对缓冲区进行操作)。

问题在于,我之前做的粒子系统版本都是使用统一变量数组(uniforms)、UBO、SSBO 来工作的,在这些中你需要明确地指定数据写入的位置。 但是使用纹理时,你需要使用顶点处理阶段的输出,正如你所解释的那样,由于裁剪的原因完全是错误的。 另外,是的,我计划也尝试使用计算着色器 :) - Teybeo

0

顶点着色器代码中存在问题:

gl_Position = vec4(gl_VertexID, 0, 0, 1);

gl_VertexID的值将为0、1、2等,对于每个在调用中渲染的点而言。要渲染一个点,它必须在(-1.0, 1.0)的范围内。只有gl_VertexID值为0的点才在可渲染的范围内,因此这是您在演示纹理中看到的唯一像素。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接