帧缓冲对象-实践过程问题及解决

来源:互联网 发布:unity3d 官方文档 编辑:程序博客网 时间:2024/06/06 02:41

自从面试腾讯实习生结束后,一直在学习OpenGL,以前断断续续学过,但是主要的参考材料是 < < OpenGL 编程指南(第八版) > >(俗称红宝书)以及网络上零零碎碎的博客,去年在创业公司实习时,也学过,但是学习的效果一直都比较差,学到最后,只能知道渲染管线,顶点数组,光照等等,着色器只会最简单的,可以说,毫无竞争力.总结下来,主要是参考的资料不对,红宝书固然好,但不适合初学者,初学者还是需要一步步,动手实践好每个实例来学习,不然很容易变成空中楼阁.上周看到了Learn OpenGL这个教材,如获至宝,至少从现在的学习效果来看,还是蛮不错的.
废话说了好多,昨天学到帧缓冲(Frame Buffer)这一块,按照教材来,但是一直没有效果,现在回过头来看,造成的原因是:自己的封装造成写的程序和教程存在一些出入.本以为流程差不多效果就可以显示的,但是却没有,有些细节致使结果错误.LearnOpenGL帧缓冲
下面总结这些错误及对应的解决方法:
(1)先看如下代码:

void Test::init(){    glGenFramebuffers(1, &_frameBuffer);    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);    _textureColorBuffer = generateAttachmentTexture(false, false);    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,        _textureColorBuffer, 0);    glGenRenderbuffers(1, &_rbo);    glBindRenderbuffer(GL_RENDERBUFFER, _rbo);    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCREEN_WIDTH, SCREEN_HEIGHT);    //glBindRenderbuffer(GL_RENDERBUFFER, 0);    glBindFramebuffer(GL_FRAMEBUFFER, 0);    glCheckError();    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _rbo);    glCheckError();    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)    {        std::cout << "ERROR::FRAMEBUFFER-FRAMEBUFFER is not complete!" << std::endl;    }    glBindFramebuffer(GL_FRAMEBUFFER, 0);}

报错:
这里写图片描述
这里INVALID_OPERATION即OpenGL中用glErrors()获取的错误状态GL_INVALID_OPERATION,我用自定义glCheckError函数封装了该函数,出现该错误的原因:在输入代码时,我将glBindRenderbuffer(GL_RENDERBUFFER, 0)写成glBindFramebuffer(GL_FRAMEBUFFER, 0),这导致在接下来的glFramebufferRenderbuffer前,将帧缓冲对象(FBO)解绑,在接下去将渲染缓冲对象(RBO)绑定到帧缓冲时,没有可用的帧缓冲可用,所以出现GL_INVALID_OPERATION.粗心导致的错误!!!但这个错误让我对代码中出现的API有了更清晰的了解(小白伤不起);
(2)如下代码:

void Test::draw(){    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);    //glEnable(GL_DEPTH_TEST);    //glClearColor(0.0f, 1.0f, 1.0f, 1.0f);    //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    Shape* shape = new Sphere();    shape->setScale(glm::vec3(3.0f));    shape->setRotate(45.0f, glm::vec3(0.0f, 1.0f, 0.0f));    shape->setTexture2D(ResourceManager::getInstance().getTexture2D("earth"));    shape->setShaderProgram(ResourceManager::getInstance().getShaderProgram("basic"));    shape->draw();    Shape* cube = new Cube();    cube->setTranslate(glm::vec3(0, 0, 15));    cube->setRotate(45.0f, glm::vec3(0.0f, 1.0f, 0.0f));    cube->setShaderProgram(ResourceManager::getInstance().getShaderProgram("basic"));    cube->draw();    glBindFramebuffer(GL_FRAMEBUFFER, 0);    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);    glClear(GL_COLOR_BUFFER_BIT);    glDisable(GL_DEPTH_TEST);    Shape* plane = new Cube();    Texture2D texture2D(_textureColorBuffer);    plane->setTexture2D(texture2D);    plane->setShaderProgram(ResourceManager::getInstance().getShaderProgram("framebuffer"));    plane->draw();    glEnable(GL_DEPTH_TEST);    delete shape;    delete cube;    delete plane;}

几何图形和渲染过程我做了封装,看函数名字可以理解渲染过程,渲染结果:
这里写图片描述
黑屏…glClearColor和glClear,在draw函数调用前已经使用,如下:

bool Application::initRender(){    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);    int width, height;    glfwGetFramebufferSize(_window, &width, &height);    glViewport(0, 0, width, height);    ResourceManager::getInstance().initResource();    glCheckError();    _camera = Camera((float)width / (float)height);    _camera.bindUniformBuffer(CameraUniformBindPoint);    return true;}void Application::exec(){    glEnable(GL_DEPTH_TEST);    //开启深度测试    glEnable(GL_STENCIL_TEST);  //开启模板测试    while (!glfwWindowShouldClose(_window))    {        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);        static Timer timer;        glfwPollEvents();        processInput(timer.calcInvertal());        draw();        glfwSwapBuffers(_window);        sleep();    }    exit();}

所以在Test::draw()函数中开始并没有注释的部分,当时以为这没有问题的(现在证明太naive~),于是在Test::init()函数中找问题,在做了一些尝试后,发现注释了glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _rbo);后,可以渲染结果了,如下图:
这里写图片描述,可以发现,图像中的结果明显是因为没有开启深度测试(当时并没有想到)导致的,然后移动摄像机,结果如下图:
这里写图片描述,这种结果,明显是渲染开始没有清空GL_COLOR_BUFFER_BIT导致的,可是,当时我认为我已经在Application::initRender和Application::exec()中调用了,肯定不是上述的开启深度测试和清空颜色缓冲(事后被打脸,啪啪啪),于是陷入僵局了.
一直无果后,1点多钟师兄说要回寝室,就暂时放下了.
今天(话说今天是我浙120周年校庆,生日快乐!)早上来到实验室后,还是要面对昨晚未解决的问题,我一直肯定不是开启深度测试和清空颜色缓冲的问题,最后身边有一本< < OpenGL ES 3.0 编程指南 > >,也有讲述帧缓冲的部分,代码大致相同,但在对应Test::init中的代码,该书的代码最后没有glBindFramebuffer(GL_FRAMEBUFFER, 0)这行代码,我尝试注释了这行代码,结果…竟然可以了,如下图:
这里写图片描述
可是移动摄像机,还是有问题:
这里写图片描述
此时,想到注释glBindFramebuffer(GL_FRAMEBUFFER, 0)后,Application::exec函数里的操作可以应用到帧缓冲中,此时才明白,窗口缓冲和自定义帧缓冲是两个不同的缓冲,在glBindFramebuffer(GL_FRAMEBUFFER, 0)后,Application::exec()里的初始化操作是针对窗口缓冲的,而不是自定义的缓冲,绑定后自定义帧缓冲后,也必须有开启深度测试,清空颜色缓冲等操作,即上述Test::draw()中被注释的代码.修改后终于正常了.现在想到,当时注释glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _rbo)这行代码前无用的原因是,没有针对自定义缓冲执行清空颜色缓冲和深度缓冲的操作.
(3)Learn OpenGL中关于帧缓冲那部分,只讲了如何渲染颜色帧缓冲,没有讲怎么去渲染深度缓冲,正好< < OpenGL ES 3.0 编程指南 > >有讲,新代码如下:

void Test::init1(){    glGenFramebuffers(1, &_frameBuffer);    glGenTextures(2, textures);    glBindTexture(GL_TEXTURE_2D, textures[0]);    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCREEN_WIDTH, SCREEN_HEIGHT, 0,        GL_RGB, GL_UNSIGNED_SHORT_5_6_5, nullptr);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);    glBindTexture(GL_TEXTURE_2D, 0);    glBindTexture(GL_TEXTURE_2D, textures[1]);    glCheckError();    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, SCREEN_WIDTH,        SCREEN_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);    glCheckError();    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);    glBindTexture(GL_TEXTURE_2D, 0);    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[0], 0);    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, textures[1], 0);    glBindFramebuffer(GL_FRAMEBUFFER, 0);    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)    {        std::cout << "ERROR::FRAMEBUFFER-FRAMEBUFFER is not complete!" << std::endl;    }    glBindFramebuffer(GL_FRAMEBUFFER, 0);}void Test::draw(){    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);    glEnable(GL_DEPTH_TEST);    glClearColor(0.0f, 1.0f, 1.0f, 1.0f);    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    Shape* shape = new Sphere();    shape->setScale(glm::vec3(3.0f));    shape->setRotate(45.0f, glm::vec3(0.0f, 1.0f, 0.0f));    shape->setTexture2D(ResourceManager::getInstance().getTexture2D("earth"));    shape->setShaderProgram(ResourceManager::getInstance().getShaderProgram("basic"));    shape->draw();    Shape* cube = new Cube();    cube->setTranslate(glm::vec3(0, 0, 15));    cube->setRotate(45.0f, glm::vec3(0.0f, 1.0f, 0.0f));    cube->setShaderProgram(ResourceManager::getInstance().getShaderProgram("basic"));    cube->draw();    glBindFramebuffer(GL_FRAMEBUFFER, 0);    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    glDisable(GL_DEPTH_TEST);    Shape* plane = new Cube();    //Texture2D texture2D(textures[1]);    Texture2D texture2D(_textureColorBuffer);    plane->setTexture2D(texture2D);    plane->setShaderProgram(ResourceManager::getInstance().getShaderProgram("framebuffer"));    plane->draw();    glEnable(GL_DEPTH_TEST);    delete shape;    delete cube;    delete plane;}

变化的是Test::init(),Test::draw()修改的部分主要是注释的那行,Test::init()用两个纹理作为存放缓冲的内容,(1)(2)中的Test::init()是一个纹理和一个渲染缓冲,原着色器代码如下:

#version 330 corein vec2 fTexcoord;uniform sampler2D screenTexture;out vec4 color;void main(){    color = vec4(texture2D(screenTexture, fTexcoord));}

渲染深度缓冲的结果:
这里写图片描述
可以看到,全屏红色,深浅虽然正确,但是,这里并不是期望中的黑白色.一番折腾后,在以前写代码时,看别人的生成阴影的代码里,是取纹理采样结果的r分量,于是把片段着色器代码修改成如下:

#version 330 corein vec2 fTexcoord;uniform sampler2D screenTexture;out vec4 color;void main(){//  color = vec4(texture2D(screenTexture, fTexcoord));    color = vec4(texture2D(screenTexture, fTexcoord).r);}

结果:
这里写图片描述
终于正确了!!!
这样做的原因(猜测),深度纹理的internalFormat和type分别是GL_DEPTH_COMPONENT32F,GL_FLOAT,而不是GL_RGB和GL_UNSIGNED_SHORT_5_6_5这种颜色纹理的类型,所以在采样时,texture2D的结果是(R,G,B,A),深度数据全部存放在R变量里的,G,B,A都是0.0f,所以结果会泛红.这个问题的解决完全是因为之前看到的那份代码,不然又要走很多弯路,不过之前一直不懂为啥取r分量,现在终于明白了!

三个问题,解决的同时让自己对OpenGL的理解更进一步,对API的使用也有了进步,但还是要吐槽一句,OpenGL这种设计,确实有点反人类(可能是我太弱鸡了).

最后,解决问题过程的一些感想:
(1)细心,细心,细心,第一个问题就是粗心造成的!
(2)分析问题的能力挺弱鸡的,第二个问题,如果能够认真分析,然后针对性去找代码问题,可能就不会耗费那么久的时间了,侧面来说,这也是自己对OpenGL熟练造成的;
(3)我记得去年在**实习时,做的入职作业,弄得很差,阴影效果,我花了两周才弄出来.这样的原因,一是查找资料的能力太弱,当时找到的都是固定管线的资料,一起实习的伙伴做的好的就是根据LearnOpenGL这本教程来的;二是,眼高手低,当时红宝书上有阴影实现的代码轮廓,自己在没有搞明白纹理,帧缓冲的情况就去写,结果是浪费了大量时间,如果开始就扎扎实实,从简单地学起,或许可以缩短大量时间,还能完全理解背后的原理;
(4)踏踏实实,不仅要看书,更要去实践,动手写代码,这样不仅能增强记忆,在写代码的过程中还能发现很多细节问题,这些问题往往可以导致整个程序运行失败,此外,通过代码实践->发现问题->解决问题的良性循环,能够增强对整个知识的理解!