【发布时间】:2017-07-23 23:05:39
【问题描述】:
我有几个网格(约 100 个)相同复杂对象的不同姿势,旋转和平移参数略有不同。该对象由多个刚性组件组成,例如手臂和腿。
目标是生成一张独特的灰度图片,显示特定身体部位的这些姿势的累积。获得的热图给出了身体部位可能的像素位置的概念,其中白色表示最大概率,黑色表示最小概率(越浅,概率越高)。说我对腿的堆积感兴趣。如果许多腿部姿势样本位于相同的 (x,y) 像素位置,那么我预计会在那里看到浅色像素。最终,腿部姿势可能不会完全重叠,因此我还希望看到腿部轮廓边界周围的黑色低概率平滑过渡。
为了解决这个任务,我决定在 OpenGL 帧缓冲区中使用渲染,因为众所周知这些缓冲区的计算成本很低,而且我需要经常运行这个累积过程。
我所做的是以下。我使用 GL_BLEND 在同一个帧缓冲区“fboLegsId”上累积了我感兴趣的身体部位的相应渲染(让我们仍然保留腿部示例)。为了区分腿 和身体的其余部分,我用两种颜色纹理网格:
rgba(gray,gray,gray,255) 用于腿部,其中 gray = 255 / 样本数 = 255/100
身体其他部分的 rgba(0,0,0,0)
然后我通过执行以下操作来累积 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(...)
这几乎符合我的预期。我确实获得了我想要的平滑灰度热图。但是存在自闭塞问题 即使我只使用 1 个样本也会出现这种情况。假设单个姿势样本,其中一条手臂在腿之前移动,部分遮挡它们。我希望在渲染过程中取消遮挡腿部的影响。然而,它呈现为好像手臂是不可见/半透明的,允许完全显示后面的像素。这会导致错误的渲染,从而导致错误的累积。
如果我简单地禁用混合,我会看到正确的自遮挡感知结果。所以,显然问题出在混合时间的某个地方。
我还尝试了不同的混合函数,到目前为止,以下一个产生了更接近自遮挡感知累积方法的结果: glBlendFunc(GL_ONE, GL_SRC_ALPHA); 无论如何,这里仍然存在一个问题:一个样本现在看起来是正确的;相反,两个或更多累积样本显示与其他样本重叠的伪影。如果像素不是腿的一部分,则看起来每个累积都会替换当前缓冲区像素。如果在(比方说)手臂前面多次发现腿,那么它会变得越来越暗,而不是越来越亮。 我尝试通过在启用深度计算的每次渲染迭代时清除深度缓冲区来解决此问题,但这并没有解决问题。
我觉得我的方法在概念上存在一些错误,或者某个地方存在小错误。
我已根据按预期执行的建议尝试了不同的方法。现在我正在使用 2 个帧缓冲区。第一个(SingleFBO)用于渲染具有正确自遮挡处理的单个样本。第二个(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);
【问题讨论】:
-
这是一种不同的方法:使用另一个(累积)帧缓冲区,它被初始化为黑色。到正常帧缓冲区,正常渲染角色,腿为白色,其他部分为黑色。然后,将此渲染用作纹理,将其添加到累积 fb 中。然后渲染下一个动画帧,并将其添加到累积 fb 中。我希望你明白我的意思
-
我会尝试使用 stencil 吗?请参阅I have an OpenGL Tessellated Sphere and I want to cut a cylindrical hole in it 如果我没记错的话,您可以将其设置为增量,这正是您想要的。但我不确定如何通过 FBO 将模板返回到 CPU 端。
-
@geza:谢谢你的建议!您能否详细说明如何将内容从一个帧缓冲区传输到另一个帧缓冲区?这是否涉及 glReadPixels() 之间?这个操作计算量不是很大吗?
-
@Spektre:嗯,我在这方面的经验很少。与帧缓冲区相比,使用模板有什么优势?
-
不,那会很慢。在创建“普通”帧缓冲区时使用 glFramebufferTexture2D,因此您也可以将此帧缓冲区用作纹理。在这种情况下,我无法查看驱动程序所做的事情,但这可能是免费操作。甚至,如果它不是免费的,它一定很便宜。
标签: c++ opengl rendering mesh blending