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

自从面试腾讯实习生结束后,一直在学习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 core

in vec2 fTexcoord;

uniform sampler2D screenTexture;

out vec4 color;

void main()
{
    color = vec4(texture2D(screenTexture, fTexcoord));
}

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

#version 330 core

in 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)踏踏实实,不仅要看书,更要去实践,动手写代码,这样不仅能增强记忆,在写代码的过程中还能发现很多细节问题,这些问题往往可以导致整个程序运行失败,此外,通过代码实践->发现问题->解决问题的良性循环,能够增强对整个知识的理解!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值