在固定功能管线绘图中使用FBO将OpenGL场景渲染为纹理 [英] Render OpenGL scene to texture using FBO in fixed function pipeline drawing
问题描述
我致力于开源游戏torcs( http://torcs.sourceforge.net/) . 游戏的图形流水线仍使用OpenGL 1.3的固定功能流水线(FFP).
I work on the open source game torcs (http://torcs.sourceforge.net/). The game's graphic pipeline is still using the fixed function pipeline (FFP) of OpenGL 1.3.
我尝试将游戏场景渲染为FBO(帧缓冲对象)中的纹理,以便对渲染的纹理进行一些后处理.我使用OpenGL 3.3.在我的机器上.
I try to render the game scenes to textures in a FBO (Framebuffer Object) in order to do some post-processing on the rendered textures. I use OpenGL 3.3. on my machine.
当前,我已在FBO上设置了附着纹理,在GL_COLOR_ATTACHMENT0&1
(2,以便在着色器中可以读取两个连续的帧),并在GL_DEPTH_ATTACHMENT
附着了渲染缓冲区.
Currently I have set up the FBO with attached textures at GL_COLOR_ATTACHMENT0&1
(2 in order to have two consecutive frames readable in the shader) and an attached renderbuffer at GL_DEPTH_ATTACHMENT
.
绑定FBO之后,将执行游戏渲染功能.然后,当我解除绑定FBO并进行验证时,通过着色器程序将纹理写回到窗口缓冲区,则场景是不完整的.更具体地说,仅绘制了汽车的轮廓,轮胎的打滑痕迹和一些烟雾也被绘制.这表明某些东西已渲染到FBO的纹理上,但不是全部.除其他外,FBO中没有将纹理(树木,房屋,草等)渲染为该纹理.这表明我的纹理设置不正确,但是不幸的是,我对OpenGL的了解有限,这就是为什么希望得到您的帮助.
After binding the FBO the game rendering function is executed. When I afterwards unbind the FBO and for validation write back the texture via a shader program to the window buffer the scene is incomplete. To be more specific, only the outline of the car is rendered, so are skidmarks of the tires and some smoke. This shows that something is rendered to the texture of the FBO, but not everything. Among others no textures (for trees, houses, grass etc.) are rendered to the texture in the FBO. This suggests that my texture set up is incorrect, but unfortunately my knowledge of OpenGL is limited, which is why I hope for your help.
另一件事值得注意的是,如果在绘制发生之前我省略了glActiveTexture(GL_TEXTURE0);
线,则将显示一个纹理(即,将其写入FBO并写回到窗口系统的帧缓冲中而不是汽车中)大纲.
One other thing worth noting is, that if I leave out the line glActiveTexture(GL_TEXTURE0);
before the drawing happens then one texture will be displayed (i.e. will be written to the FBO and written back to the window system framebuffer instead of the car outline.
以下代码显示了FBO的初始化(来自 https://en.wikibooks.org/wiki/OpenGL_Programming/后处理):
int screen_width = 640;
int screen_height = 480;
/* Texture A*/
glGenTextures(1, &fbo_texture);
glBindTexture(GL_TEXTURE_2D, fbo_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, screen_width, screen_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
/* Texture B*/
//glActiveTexture(GL_TEXTURE1);
glGenTextures(1, &fbo_texture_a);
glBindTexture(GL_TEXTURE_2D, fbo_texture_a);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, screen_width, screen_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
/* Depth buffer */
glGenRenderbuffers(1, &rbo_depth);
glBindRenderbuffer(GL_RENDERBUFFER, rbo_depth);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, screen_width, screen_height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
/* Framebuffer to link everything together */
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo_texture, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, fbo_texture_a, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo_depth);
GLenum status;
if ((status = glCheckFramebufferStatus(GL_FRAMEBUFFER)) != GL_FRAMEBUFFER_COMPLETE) {
fprintf(stderr, "glCheckFramebufferStatus: error 0x%x", status);
return 0;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
/* Compile and link shaders */
...
以下代码显示了绘制的位置:
编辑:如果为use_fbo=false
,则所有内容将像以前一样直接呈现到屏幕上.我所做的唯一更改是在方括号内.
The following code shows where the drawing happens:
Edit: if use_fbo=false
then everything will be rendered to screen directly as before. The only changes I made are within the brackets.
if (use_fbo)
{
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glPushAttrib(GL_VIEWPORT_BIT);
glViewport(0,0,grWinw, grWinh);
if (fbo_a) // drawing to fbo_texture_a
{
glDrawBuffer(GL_COLOR_ATTACHMENT1);
glActiveTexture(GL_TEXTURE0+11);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, fbo_texture_a);
}
else
{
glDrawBuffer(GL_COLOR_ATTACHMENT0);
glActiveTexture(GL_TEXTURE0+12);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, fbo_texture);
}
glActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_2D);
}
glClearColor(0.7f, 0.1f, 0.1f, 1.0f); //clear with red to see what is drawn to the fbo
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
grScreens[0]->update(s, grFps);//THIS IS WHERE THE DRAWING HAPPENS unchanged from original drawing in TORCS
if (use_fbo)
{
glPopAttrib();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glEnable(GL_TEXTURE_2D);
glDrawBuffer(GL_BACK);
glClearColor(1.0, 1.0, 1.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glUseProgram(program_postproc);
if (fbo_a) // drawn to fbo_texture_a
{
glUniform1i(uniform_fbo_texture, 11);
glUniform1i(uniform_fbo_texture_a, 12);
fbo_a=!fbo_a;
}
else
{
glUniform1i(uniform_fbo_texture, 12);
glUniform1i(uniform_fbo_texture_a, 11);
fbo_a=!fbo_a;
}
glEnableVertexAttribArray(attribute_v_coord_postproc);
glBindBuffer(GL_ARRAY_BUFFER, vbo_fbo_vertices);
glVertexAttribPointer(
attribute_v_coord_postproc, 2, GL_FLOAT, GL_FALSE, 0, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(attribute_v_coord_postproc);
glUseProgram(0);
}
希望我能为您提供足够的信息来帮助我.任何建议表示赞赏.
I hope I provided enough information for you to help me with this. Any advice is appreciated.
编辑:我再次检查了着色器代码和FBO实现(将其简化为一个颜色附件等,并带有简化的图形),并且一切正常.我认为麻烦在于绘图的固定功能管道与FBO的实现...
I checked my shader code and FBO implementation again (simplified it to just one color attachment etc. with a simplified drawing) and it all worked. I think the trouble is the mix of fixed function pipeline for drawing and my implementation of the FBO...
编辑:以下是两幅有关use_fbo = true与false发生的情况的图像: (注意:红色是FBO绑定后的透明颜色,以查看向FBO渲染的内容:除了阴影和防滑痕迹之外没有其他内容)
here are two images of what happens with use_fbo=true vs. false: (Note: the red color is the clear color after the FBO is bound, to see what gets rendered to the fbo: nothing apart from the shadow and skid marks)
我还尝试可视化深度缓冲区(将实现更改为深度的纹理附件),即使我进行了线性化处理,也没有任何信息.我认为深度也未正确写入FBO.
I also tried to visualize the depth buffer (changed the implementation to a texture attachment for depth) and even though I linearized, there was no information. I suppose the depth is not correctly written to the FBO either.
推荐答案
当我将您的代码与我的工作引擎进行比较时,我看到了这些差异,因此请一一尝试:
When I compared your code with mine working engine I see these differences so try them one by one:
-
纹理格式
您正在使用:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, screen_width, screen_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
因此将您的所有合并到:
so merging all your to:
GL_COLOR_ATTACHMENT0: GL_RGBA,GL_RGBA,GL_UNSIGNED_BYTE
GL_COLOR_ATTACHMENT1: GL_RGBA,GL_RGBA,GL_UNSIGNED_BYTE
我正在使用:
GL_COLOR_ATTACHMENT0 : GL_RGBA , GL_RGBA8 , GL_UNSIGNED_BYTE
GL_DEPTH_ATTACHMENT : GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT16, GL_UNSIGNED_BYTE
GL_STENCIL_ATTACHMENT: GL_STENCIL_INDEX , GL_STENCIL_INDEX8 , GL_UNSIGNED_BYTE
您需要更改纹理的内部像素格式以指定位宽.如果我在编写此代码时(几年前)记忆力很好,则不能仅使用GL_RGBA,GL_RGBA
.
You need to change internal pixel format of textures to specify bit width. If my memory serves well when I code this (some years back) it did not work with just GL_RGBA,GL_RGBA
.
深度目标
我使用depth
和stencil
纹理的方式与color attachment
相同,我不使用任何RenderBuffer
调用.那并不意味着您的代码是错误的,而是我的代码已经过测试并且可以工作.
I am using depth
and stencil
textures the same way as color attachment
I do not use any RenderBuffer
calls. That does not mean your code is wrong but mine is tested and works.
纹理大小
这很可能不再有效,因为大多数gfx卡都支持矩形纹理扩展,但是 OpenGL 纹理应为2分辨率的幂.因此,对于初学者来说,请尝试使用512x512
而不是您的640x480
,并在代码工作时更改回去(请确保...).
This is most likely not valid anymore as most gfx cards support rectangle texture extension but OpenGL textures should be power of 2 resolution. So for starters try 512x512
instead of your 640x480
And change back when your code is working (just to be sure ...).
如果这对我的 C ++ FBO class
有帮助,那么您可以从中进行比较(因为它使用了引擎中的纹理和内容,因此无法单独使用):
In case it helps here is my C++ FBO class
taken from mine engine so you got something to compare to (will not work alone as it uses textures and stuff from the engine):
//------------------------------------------------------------------------------
//--- Open GL FBO object ver 2.31 ----------------------------------------------
//------------------------------------------------------------------------------
#ifndef _OpenGL_FBO_h
#define _OpenGL_FBO_h
//------------------------------------------------------------------------------
class OpenGL_FBO
{
public:
GLuint fbo;
int xs,ys;
struct _dst
{
GLint txr; // texture id
GLenum dst; // GL_DEPTH_COMPONENT, GL_COLOR_ATTACHMENT0, ...
_dst() { txr=-1; dst=GL_COLOR_ATTACHMENT0; }
_dst(_dst& a) { *this=a; }
~_dst() {}
_dst* operator = (const _dst *a) { *this=*a; return this; }
//_dst* operator = (const _dst &a) { ...copy... return this; }
};
List<_dst> dst;
OpenGL_FBO() { fbo=0xFFFFFFFF; xs=1; ys=1; dst.reset(); }
OpenGL_FBO(OpenGL_FBO& a) { fbo=0xFFFFFFFF; dst.reset(); *this=a; }
~OpenGL_FBO() { if (fbo!=0xFFFFFFFF) glDeleteFramebuffers(1,&fbo); }
OpenGL_FBO* operator = (const OpenGL_FBO *a) { *this=*a; return this; }
//OpenGL_FBO* operator = (const OpenGL_FBO &a) { ...copy... return this; }
void resize(OpenGLscreen &scr,int _xs=-1,int _ys=-1)
{
int i;
_dst *d;
if (_xs<=0) _xs=scr.xs;
if (_ys<=0) _ys=scr.ys;
// for (xs=1;xs<_xs;xs<<=1);
// for (ys=1;ys<_ys;ys<<=1);
xs=_xs; ys=_ys; // ****
if (fbo==0xFFFFFFFF) glGenFramebuffers(1,&fbo);
glBindFramebuffer(GL_FRAMEBUFFER,fbo);
for (d=dst.dat,i=0;i<dst.num;i++,d++)
{
scr.txrs.bind(d->txr);
scr.txrs.resize(d->txr,xs,ys,1);
// glFramebufferTexture2D(GL_FRAMEBUFFER,t->dst,GL_TEXTURE_2D,scr.txrs.names[d->txr],0);
glFramebufferTexture(GL_FRAMEBUFFER,d->dst,scr.txrs.names[d->txr],0);
// glCheckFramebufferStatus(GL_FRAMEBUFFER);
}
scr.txrs.unbind();
glBindFramebuffer(GL_FRAMEBUFFER,0);
}
int add(OpenGLscreen &scr,int _dest=GL_COLOR_ATTACHMENT0) // add txr to fbo
{
_dst d;
OpenGL_TXR tmp;
// colro atachments
tmp.pixelformat =GL_RGBA;
tmp.pixeliformat=GL_RGBA8;
tmp.pixeltype=GL_UNSIGNED_BYTE;
tmp.mag=GL_NEAREST;
tmp.min=GL_NEAREST;
if (_dest==GL_DEPTH_ATTACHMENT)
{
tmp.pixelformat =GL_DEPTH_COMPONENT;
tmp.pixeliformat=GL_DEPTH_COMPONENT16;
// tmp.pixeltype=GL_FLOAT;
tmp.pixeltype=GL_UNSIGNED_BYTE;
}
if (_dest==GL_STENCIL_ATTACHMENT)
{
tmp.pixelformat =GL_STENCIL_INDEX;
tmp.pixeliformat=GL_STENCIL_INDEX8;
tmp.pixeltype=GL_UNSIGNED_BYTE;
}
tmp.xs=xs;
tmp.ys=ys;
tmp.zs=1;
tmp._mipmap=0;
tmp.txrtype=GL_TEXTURE_2D;
d.txr=scr.txrs.add(tmp);
d.dst=_dest;
dst.add(d);
return d.txr;
}
void bind(OpenGLscreen &scr) // init fbo >> txr
{
// init and resize
if (fbo==0xFFFFFFFF) glGenFramebuffers(1,&fbo);
glBindFramebuffer(GL_FRAMEBUFFER,fbo);
glViewport(0,0,xs,ys);
scr.cls();
}
void unbind(OpenGLscreen &scr)
{
glBindFramebuffer(GL_FRAMEBUFFER,0);
glViewport(scr.x0,scr.y0,scr.xs,scr.ys);
}
};
//------------------------------------------------------------------------------
#endif
//------------------------------------------------------------------------------
//--- end. ---------------------------------------------------------------------
//------------------------------------------------------------------------------
其中:
OpenGLscreen scr
是我的渲染引擎
scr.cls()
只是glClear
而要放入初始化帧
scr.x0,y0,xs,ys
是目标窗口的视口
scr.txrs
是纹理系统类(处理所有纹理),例如add
新纹理从文件加载/保存到文件,在 CPU/GPU 之间进行转换等等.
OpenGLscreen scr
is my rendering engine
scr.cls()
is just glClear
and stuff to init frame
scr.x0,y0,xs,ys
is viewport of target window
scr.txrs
is texture system class (handles all the textures) like add
new texture loading/saving from/to file, conversion between CPU/GPU and much much more.
我还使用了我的动态列表模板,所以:
I also use mine dynamic list template so:
List<double> xxx;
与double xxx[];
相同
xxx.add(5);
将5
添加到列表的末尾
xxx[7]
访问数组元素(安全)
xxx.dat[7]
访问数组元素(不安全但快速的直接访问)
xxx.num
是数组的实际使用大小
xxx.reset()
清除数组并设置xxx.num=0
xxx.allocate(100)
为100
个项目预分配空间
List<double> xxx;
is the same as double xxx[];
xxx.add(5);
adds 5
to end of the list
xxx[7]
access array element (safe)
xxx.dat[7]
access array element (unsafe but fast direct access)
xxx.num
is the actual used size of the array
xxx.reset()
clears the array and set xxx.num=0
xxx.allocate(100)
preallocate space for 100
items
典型用法是:
// [globals and init]
OpenGLScreen scr; // can ignore this
OpenGL_FBO fbo;
scr.init(window_handle); // init OpenGL stuff can ignore this
fbo.add(scr,GL_COLOR_ATTACHMENT0);
fbo.add(scr,GL_DEPTH_ATTACHMENT);
fbo.resize(scr,512,512);
// [render loop]
fbo.bind(scr);
// here render
fbo.unbind(scr);
// here you can use the textures fbo.dst[].txr
在这里查看具体示例:
对于那些被较旧的 Intel HD图形卡住的人来说,由于驱动程序中的错误,他们并不希望这种方法能正常工作.请参阅以下缓慢的解决方法:
Of coarse for those of you that are stuck with older Intel HD graphics do not expect that this will work due to bug in drivers. See this slow workaround:
这篇关于在固定功能管线绘图中使用FBO将OpenGL场景渲染为纹理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!