c++ - 在 OpenGL 中创建和混合动态纹理

标签 c++ opengl shader alphablending render-to-texture

我需要将球体渲染到纹理(使用帧缓冲区对象 (FBO) 完成),然后将该纹理与后台缓冲区进行 alpha 混合。到目前为止,除了在每帧开始时清除纹理外,我没有对纹理进行任何处理。

我应该说我的场景只包含一个空旷空间中的行星,球体应该出现在行星旁边或周围(目前有点像月亮)。当我将球体直接渲染到后台缓冲区时,它显示正确;但是当我执行将其渲染为纹理然后将该纹理与后台缓冲区混合的中间步骤时,球体仅在它位于行星前面时才会显示,不在前面的部分只是“被切断” ":

Sphere that is cut off by the edge of the planet

我使用 glutSolidSphere 将球体渲染为绑定(bind)到 FBO 的 RGBA8 全屏纹理,确保每个球体像素的 alpha 值为 1.0。然后我将纹理传递给片段着色器程序,并使用此代码渲染全屏四边形 - 纹理映射到它 - 到后台缓冲区,同时进行 alpha 混合:

glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);

glDisable(GL_DEPTH_TEST);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glBegin(GL_QUADS);
glTexCoord2i(0, 1);
glVertex3i(-1,  1, -1);   // TOP LEFT
glTexCoord2i(0, 0);
glVertex3i(-1, -1, -1);   // BOTTOM LEFT
glTexCoord2i(1, 0);   
glVertex3i( 1, -1, -1);   // BOTTOM RIGHT
glTexCoord2i(1, 1);   
glVertex3i( 1,  1, -1);   // TOP RIGHT
glEnd();
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glEnable(GL_DEPTH_TEST);

glDisable(GL_BLEND);

这是着色器代码(取自用 Cg 编写的 FX 文件):

sampler2D BlitSamp = sampler_state
{
    MinFilter = LINEAR;
    MagFilter = LINEAR;
    MipFilter = LINEAR;
    AddressU = Clamp;
    AddressV = Clamp;
};

float4 blendPS(float2 texcoords : TEXCOORD0) : COLOR
{
    float4 outColor = tex2D(BlitSamp, texcoords);
    return outColor;
}

我什至不知道这是深度缓冲区的问题还是 alpha 混合的问题,我尝试了很多启用和禁用深度测试(将深度缓冲区附加到 FBO)和 alpha 混合的组合.

编辑:我试着直接渲染一个空白的全屏四边形到后台缓冲区,甚至在地球的边缘周围也被裁剪了。出于某种原因,启用渲染四边形的深度测试(即删除上面代码中的行 glDisable(GL_DEPTH_TEST)glEnable(GL_DEPTH_TEST)) 解决了这个问题,但是现在除了行星和球体之外的所有东西都显示为白色:

Sphere with white incorrect white background

我确定(并且可以确认)纹理的 alpha channel 在除球体以外的每个像素处均为 0,因此我不明白可以在何处引入白度。 (也仍然对解释为什么启用深度测试有这种效果感兴趣。)

最佳答案

我在这里看到两个可能的错误来源:

1。呈现给 FBO

如果在渲染后丢失的像素甚至不存在于 FBO 中,则一定有某种机制丢弃了相应的片段。 OpenGL 管道包括四种不同类型的片段测试,这些片段测试可能会导致片段被丢弃:

  1. 剪刀测试:不太可能是原因,因为剪刀测试仅影响屏幕的矩形部分。
  2. Alpha 测试:同样不太可能,因为您的片段应该都具有相同的 alpha 值。
  3. 模板测试:同样不太可能,除非您在绘制背景行星时使用模板操作并将模板缓冲区从后台缓冲区复制到 FBO。
  4. 深度测试:与模板测试相同。

因此很有可能渲染到 FBO 不是这里的问题。但为了绝对确定,您应该读回您的颜色附件纹理并将其转储到文件中以供检查。您可以为此使用以下函数:

void TextureToFile(GLuint texture, const char* filename) {
  glBindTexture(GL_TEXTURE_2D, texture);
  GLint width, height;
  glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
  glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);

  std::vector<GLubyte> pixels(3 * width * height);
  glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, &pixels[0]);

  std::ofstream out(filename, std::ios::out | std::ios::binary);
  out << "P6\n"
      << width << '\n'
      << height << '\n'
      << 255 << '\n';
  out.write(reinterpret_cast<const char*>(&pixels[0]), pixels.size());  
}

生成的文件是 portable pixmap (.ppm)。在读回纹理之前一定要解绑 FBO。

2。纹理贴图

假设 FBO 渲染按预期工作,唯一的其他错误来源是将纹理混合到先前渲染的场景上。有两种情况:

a) 碎片被丢弃

片段被丢弃的可能原因同1.:

  1. 剪刀测试:不,只影响矩形区域。
  2. Alpha 测试:可能不是,纹理元素覆盖的球体应该都具有相同的 alpha 值。
  3. Stencil Test:如果您在绘制背景行星时使用模板操作/模板测试并且旧的模板状态仍处于事件状态,则可能是原因。
  4. 深度测试:可能是原因,但由于您已经禁用它,所以它真的不应该有任何影响。

所以你应该确保所有这些测试都被禁用,尤其是模板测试。

b) 混合的错误结果

假设所有片段都到达后台缓冲区,混合是唯一仍然可能导致错误结果的事情。使用混合函数 (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 后缓冲区中的值与混合无关,我们假设纹理中的 alpha 值是正确的。所以我看不出为什么混合应该是这里的根本原因。

结论

总而言之,观察到的结果的唯一合理原因似乎是模板测试。如果不是,我别无选择:)

关于c++ - 在 OpenGL 中创建和混合动态纹理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12643488/

相关文章:

C++显式实例化非模板类的模板构造函数

c++ - 在 OpenGL 中使用多个纹理

c++ - 如何在 Qt 应用程序中替换 'gluOrtho2d'

javascript - 将顶点数据打包到 WebGL 纹理中

opengl - TEXTURE_2D_ARRAY 未渲染

用于模板基类的 C++ STL 容器

c++ - nm 报告的未解析符号

opengl - 寻找正常的OpenGL

glsl - 为什么在 glsl 中 sin/cos 返回值 > 1

c# - C++ 数组 vs C# ptr 速度混淆