FBO:渲染到纹理,绘制渲染纹理时纹理映射错误

Posted

技术标签:

【中文标题】FBO:渲染到纹理,绘制渲染纹理时纹理映射错误【英文标题】:FBO: render to texture, wrong texture mapping when drawing rendered texture 【发布时间】:2012-02-06 12:23:07 【问题描述】:

我在 Mac OS X 应用程序上使用 OpenGL 在 NSOpenGLView 上绘制纹理。

该应用程序是一个电影播放器​​。它将电影帧解码为CVOpenGLTextureRef(这是OpenGL纹理),我使用GL_QUAD将它们直接绘制到视图中。一切正常。

下面是代码的相关部分。

// "image" is the CVOpenGLTextureRef containing the movie frame as texture
GLenum textureTarget = CVOpenGLTextureGetTarget(image);
GLuint textureName = CVOpenGLTextureGetName(image);

glEnable(textureTarget);
glBindTexture(textureTarget, textureName); 

glBegin(GL_QUADS);
// Draw the quads
//Note: textureTagret is NOT GL_TEXTURE_2D, therefore texture coordinates
//are NOT scaled to [0, 1] 

glTexCoord2f(0.0f, imageRect.size.height);
glVertex2f (0.0f, 0.0f);

glTexCoord2f(0.0f, 0.0f); 
glVertex2f (0.0f, windowRect.size.height); 

glTexCoord2f(imageRect.size.width, 0.0f);
glVertex2f (windowRect.size.width, windowRect.size.height);

glTexCoord2f(imageRect.size.width, imageRect.size.height);
glVertex2f (windowRect.size.width,0.0f); 

glEnd();
glDisable(textureTarget);
glFlush();

它工作得很好,我可以调整窗口大小,并且纹理正确映射到较小的窗口。

查看此处了解从全屏到 500x280 像素的不同窗口大小:

我现在想使用 FBO 渲染到纹理,我开始制作一个非常简单的实现,其中包括将电影帧渲染到屏幕外 FBO(纹理),然后将该纹理绑定到屏幕上绘制。

代码如下:

// "image" is the CVOpenGLTextureRef containing the movie frame as texture
GLenum textureTarget = CVOpenGLTextureGetTarget(image);
GLuint textureName = CVOpenGLTextureGetName(image);

////////////////////////////////////////////////////////////////////
// the creation on the FBO is done only once on program start:

GLuint fboId;
GLuint textureId;
float targetWidth = 2048;
float targetHeight = 2048;

// create a texture object
glGenTextures(1, &textureId);
glBindTexture(textureTarget, textureId);
glTexParameterf(textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameterf(textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameteri(textureTarget, GL_GENERATE_MIPMAP, GL_TRUE); // automatic mipmap
glTexImage2D(textureTarget, 0, GL_RGBA8, targetWidth, targetHeight, 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glBindTexture(textureTarget, 0);

// create a framebuffer object
glGenFramebuffersEXT(1, &fboId);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);

// attach the texture to FBO color attachment point
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                           textureTarget, textureId, 0);

// check FBO status
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if(status != GL_FRAMEBUFFER_COMPLETE_EXT)
    return FALSE;

// switch back to window-system-provided framebuffer
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
///////////////////////////////////////////////////////////////////////////////

// Render to texture

glEnable(textureTarget);
glBindTexture(textureTarget, textureName);

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
glClear(GL_COLOR_BUFFER_BIT);   

glBegin(GL_QUADS);
// Draw the quads

glTexCoord2f(0.0f, imageRect.size.height);
glVertex2f (0.0f, 0.0f);        

glTexCoord2f(0.0f, 0.0f); 
glVertex2f (0.0f,imageRect.size.height);

glTexCoord2f(imageRect.size.width, 0.0f);
glVertex2f (imageRect.size.width, imageRect.size.height);

glTexCoord2f(imageRect.size.width, imageRect.size.height);
glVertex2f (imageRect.size.width,0.0f);

glEnd();
glFlush();

// Bind newly rendered texture
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
glClear(GL_COLOR_BUFFER_BIT);
glBindTexture(textureTarget, textureId);
glGenerateMipmapEXT(textureTarget);

// draw on-screen
glBegin(GL_QUADS);
// Draw the quads
//Note: textureTagret is NOT GL_TEXTURE_2D, therefore texture coordinates
//are NOT scaled to [0, 1] 

glTexCoord2f(0.0f, imageRect.size.height);
glVertex2f (0.0f, 0.0f);

glTexCoord2f(0.0f, 0.0f); 
glVertex2f (0.0f, windowRect.size.height); 

glTexCoord2f(imageRect.size.width, 0.0f);
glVertex2f (windowRect.size.width, windowRect.size.height);

glTexCoord2f(imageRect.size.width, imageRect.size.height);
glVertex2f (windowRect.size.width,0.0f); 

glEnd();
glDisable(textureTarget);
glFlush();

代码不能正常工作,因为图像/纹理不仅是颠倒的,而且纹理映射也是错误的。只有当窗口全屏时它才能正常工作,否则它的行为真的很奇怪。

见下图:

如您所见,纹理在窗口内正确缩放,但会根据全屏窗口大小和实际窗口大小之间的差异按比例“裁剪”。

我什么都试过了。

由于这是我第一次使用 OpenGL,我是否遗漏了什么?我快疯了..

【问题讨论】:

【参考方案1】:
// Render to texture

glEnable(textureTarget);
glBindTexture(textureTarget, textureName);

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
glClear(GL_COLOR_BUFFER_BIT);   

我没有在此处设置帧缓冲区对象的视口和投影。

glBegin(GL_QUADS);
// Draw the quads

glTexCoord2f(0.0f, imageRect.size.height);
g    lVertex2f (0.0f, 0.0f);        

glTexCoord2f(0.0f, 0.0f); 
glVertex2f (0.0f,imageRect.size.height);

glTexCoord2f(imageRect.size.width, 0.0f);
glVertex2f (imageRect.size.width, imageRect.size.height);

glTexCoord2f(imageRect.size.width, imageRect.size.height);
glVertex2f (imageRect.size.width,0.0f);

glEnd();
glFlush();

【讨论】:

哦..我的错..非常感谢您的回答。我只在调整窗口大小时设置视口和投影。我没有意识到我必须为 FBO 和后台缓冲区指定两个不同的视口和投影。这解决了纹理映射问题,但(显然)纹理仍然颠倒/反射。我知道我可以更改 glTexCoord2fglVertex2f 之间的关联来修复它,但我为什么需要它?如果我直接绘制到后台缓冲区,纹理的方向是正确的..为什么我需要在绘制到 FBO 时进行更改? @Andrea3000:嗯,它被翻转了,因为你把它颠倒了。图像原点是左下角,而不是您似乎假设的右上角。至于视口和投影的设置:这大约是我告诉 OpenGL 新手的 80%——在绘图函数中设置它们,而不是在窗口调整大小处理程序中。窗口调整大小处理程序是一个不好的地方,因为它在使用 FBO 或绘制 HUD 或类似时会导致麻烦。所有的OpenGL操作(静态数据上传除外)都属于绘图函数。 感谢您对 OpenGL 的建议,我会记住它们的 ;-) 关于翻转图像:FBO 和后缓冲有不同的坐标系吗?我问你是因为我不明白图像在绘制到后台缓冲区时如何正确,以及在绘制到 FBO 然后再到后台缓冲区时如何翻转。 glTexCoord2fglVertex2f 对于这两种情况都是一样的。 @Andrea3000:最有可能发生的情况是,假设原点位于左上角,电影帧已被存储。所以你必须翻转这个。某些图像格式(例如 TGA)具有固定的来源。其他像 PNG 或 BMP 允许两者。你必须考虑到这一点。 OpenGL 总是假定图像数据从左下角开始。因此,要么适当地翻转纹理坐标,要么在提供给 glTexImage2D 之前重新排序图像数据。 好吧,也许现在我明白了,谢谢你的解释;-) 如果我明白你的意思,事情就是这样:我的电影帧的原点位于左上角。当我将它渲染到 FBO 时,我正确地渲染它(未翻转),但现在原点位于左下角。所以从现在开始我必须改变纹理坐标,因为即使我将它渲染到多个 FBO,原点也总是在左下角。这样对吗?所以渲染到纹理是正确的,我必须将第二张图更改为后缓冲区。

以上是关于FBO:渲染到纹理,绘制渲染纹理时纹理映射错误的主要内容,如果未能解决你的问题,请参考以下文章

带有 FBO 的 OpenGL 阴影映射 - 多个拖尾绘制

openGL FBO 复制到纹理导致黑色/深色图像

WebGL入门(四十一)-使用帧缓冲区对象(FBO)实现将渲染结果作为纹理绘制到另一个物体上

在 OS X 10.7 而不是 Linux 中使用 FBO 渲染到纹理的问题

从纹理渲染到屏幕外纹理 (FBO)

为啥渲染到 FBO 会导致纹理变暗?