Self-occlusion-aware C++ OpenGL 中的多几何混合(渲染)

Self-occlusion-aware multiple geometry blending in C++ OpenGL (rendering)

我有几个相同复合体 object 的多个网格 (~100) 处于不同的姿势,旋转和平移参数略有不同。 object 由多个刚性部件组成,例如手臂和腿。

目标是生成一个独特的灰度图片,显示特定 body 部分的这些姿势的累积。获得的 heat-map 给出了 body 部分的可能像素位置的概念,其中白色代表最大概率,黑色代表最小(越亮概率越高)。假设我对腿部的堆积感兴趣。如果许多腿部姿势样本位于相同的 (x,y) 像素位置,那么我希望在那里看到亮像素。最终腿部姿势可能不会完全重叠,所以我也希望看到腿部轮廓边界周围平滑过渡到黑色低概率。

为了解决这个任务,我决定在 OpenGL 帧缓冲区中使用渲染,因为众所周知这些在计算上很便宜,而且我需要经常 运行 这个累积过程。

我所做的是以下内容。我使用 GL_BLEND 在同一个帧缓冲区 'fboLegsId' 上累积我感兴趣的 body 部分的相应渲染(让我们仍然保留腿示例)。为了区分腿 和其余 body,我用两种颜色对网格进行纹理处理:

然后我通过执行以下操作累积 100 个渲染图(对于腿来说应该总计为白色 = 255):

glBindFramebuffer(GL_FRAMEBUFFER, fboLegsId);

glClearColor(0,0,0,255);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glBlendFunc(GL_ONE, GL_ONE);
glEnable(GL_BLEND);

for each sample s = 0...100

   mesh.render(pose s);

end

glReadPixels(...)

这几乎符合我的预期。我确实获得了我想要的平滑灰度 heat-map。但是存在 self-occlusion 个问题 即使我只使用 1 个样本也会出现这种情况。对于单个姿势样本,其中一只手臂在腿之前移动,部分遮挡了它们。我希望在渲染过程中消除被遮挡腿部的影响。然而,它呈现的好像手臂是 invisible/translucent,允许完全显示后面的像素。这会导致错误的渲染,从而导致错误的累积。

如果我简单地禁用混合,我会看到正确的 self-occlusion 感知结果。所以,显然问题出在混合时间的某个地方。

我也尝试了不同的混合函数,到目前为止,下面的函数产生了更接近 self-occlusion 感知累积方法的结果: glBlendFunc(GL_ONE, GL_SRC_ALPHA); 无论如何,这里仍然存在一个问题:一个样本现在看起来是正确的;两个或更多累积样本反而显示与其他样本重叠的伪影。如果像素是 not 腿的一部分,则看起来每个累积都会替换当前缓冲区像素。如果腿在(比方说)手臂前面多次被发现,它就会变得越来越暗,而不是越来越亮。 我试图通过在每次启用深度计算的渲染迭代中清除深度缓冲区来解决此问题,但这并没有解决问题。

我觉得我的方法在概念上有问题,或者某处有小错误。


我已经根据按预期执行的建议尝试了不同的方法。现在我正在使用 2 个帧缓冲区。第一个 (SingleFBO) 用于渲染具有正确 self-occlusion 处理的单个样本。第二个 (AccFBO) 用于使用混合从第一个缓冲区累积 2D 纹理。请检查我的代码:

 // clear the accumulation buffer
 glBindFramebuffer(GL_FRAMEBUFFER, AccFBO);
 glClearColor(0.f, 0.f, 0.f, 1.f);
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

 for each sample s = 0...100
 {
        // set rendering destination to SingleFBO
        glBindFramebuffer(GL_FRAMEBUFFER, SingleFBO);

        glClearColor(0.f, 0.f, 0.f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glEnable(GL_DEPTH_TEST);
        glDisable(GL_LIGHTING);
        mesh->render(pose s);
        glDisable(GL_DEPTH_TEST);
        glEnable(GL_LIGHTING);

        // set rendering destination to the accumulation buffer
        glBindFramebuffer(GL_FRAMEBUFFER, AccFBO);

        glClear(GL_DEPTH_BUFFER_BIT);

        glBlendFunc(GL_ONE, GL_ONE);
        glEnable(GL_BLEND);

        // draw texture from previous buffer to a quad
        glBindTexture(GL_TEXTURE_2D, textureLeg);
        glEnable(GL_TEXTURE_2D);

        glDisable(GL_DEPTH_TEST);
        glDisable(GL_LIGHTING);
        glDepthMask(GL_FALSE);

        glMatrixMode(GL_PROJECTION);
        glPushMatrix();
        glLoadIdentity();
        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
        glLoadIdentity();

        glBegin( GL_QUADS );
        {
            glTexCoord2f(0,0); glVertex2f(-1.0f, -1.0f);
            glTexCoord2f(1,0); glVertex2f(1.0f, -1.0f);
            glTexCoord2f(1,1); glVertex2f(1.0f, 1.0f);
            glTexCoord2f(0,1); glVertex2f(-1.0f, 1.0f);
        }
        glEnd();

        glPopMatrix();
        glMatrixMode(GL_PROJECTION);
        glPopMatrix();
        glMatrixMode(GL_MODELVIEW);

        // restore
        glDisable(GL_TEXTURE_2D);
        glEnable(GL_DEPTH_TEST);
        glEnable(GL_LIGHTING);
        glDepthMask(GL_TRUE);

        glDisable(GL_BLEND);
  }

  glBindFramebuffer(GL_FRAMEBUFFER, AccFBO);
  glReadPixels(...)

请同时检查我用于初始化 SingleFBO 的(标准)代码(类似于 AccFBO):

    // create a texture object
    glGenTextures(1, &textureLeg);
    glBindTexture(GL_TEXTURE_2D, textureLeg);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0,
                 GL_RGB, GL_UNSIGNED_BYTE, 0);
    glBindTexture(GL_TEXTURE_2D, 0);

    // create a renderbuffer object to store depth info
    glGenRenderbuffers(1, &rboLeg);
    glBindRenderbuffer(GL_RENDERBUFFER, rboLeg);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT,
                          width, height);
    glBindRenderbuffer(GL_RENDERBUFFER, 0);

    // create a framebuffer object
    glGenFramebuffers(1, &SingleFBO);
    glBindFramebuffer(GL_FRAMEBUFFER, SingleFBO);

    // attach the texture to FBO color attachment point
    glFramebufferTexture2D(GL_FRAMEBUFFER,        // 1. fbo target: GL_FRAMEBUFFER 
                           GL_COLOR_ATTACHMENT0,  // 2. attachment point
                           GL_TEXTURE_2D,         // 3. tex target: GL_TEXTURE_2D
                           textureLeg,             // 4. tex ID
                           0);                    // 5. mipmap level: 0(base)

    // attach the renderbuffer to depth attachment point
    glFramebufferRenderbuffer(GL_FRAMEBUFFER,      // 1. fbo target: GL_FRAMEBUFFER
                              GL_DEPTH_ATTACHMENT, // 2. attachment point
                              GL_RENDERBUFFER,     // 3. rbo target: GL_RENDERBUFFER
                              rboLeg);              // 4. rbo ID

    // check FBO status
    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if(status != GL_FRAMEBUFFER_COMPLETE)
        error(...);

    // switch back to window-system-provided framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

这里有一个不同的方法:

创建两个帧缓冲区:normalaccnormal 帧缓冲区应该有纹理存储(glFramebufferTexture2D)。

基本算法如下:

  1. acc变黑
  2. 绑定normal,清除为黑色,渲染腿为白色,其他部分为黑色的场景
  3. 绑定acc,渲染一个全屏矩形,上面有normal纹理,混合模式GL_ONE,GL_ONE
  4. 转发动画,如果还没播放完,转到2。
  5. 你的结果在acc

所以,基本上,acc 将包含求和的各个帧。