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