OpenGL 帧缓冲

news/2025/2/13 5:25:09/

1.简介

我们已经使用了很多屏幕缓冲了:用于写入颜色值的颜色缓冲、用于写入深度信息的深度缓冲和允许我们根据一些条件丢弃特定片段的模板缓冲。这些缓冲结合起来叫做帧缓冲(Framebuffer),它被储存在内存中。OpenGL允许我们定义我们自己的帧缓冲,也就是说我们能够定义我们自己的颜色缓冲,甚至是深度缓冲和模板缓冲。

我们目前所做的所有操作都是在默认帧缓冲的渲染缓冲上进行的。

渲染场景到不同的帧缓冲能够让我们在场景中加入类似镜子的东西,或者做出很酷的后期处理效果。

以下是效果图:

2.使用步骤

创建一个帧缓冲:我们会使用一个叫做glGenFramebuffers的函数来创建一个帧缓冲对象(Framebuffer Object, FBO)

    //创建一个自定义的帧缓冲unsigned int fbo;glGenFramebuffers(1, &fbo);

使用glBindFramebuffer来绑定帧缓冲:

glBindFramebuffer(GL_FRAMEBUFFER, fbo);

在绑定到GL_FRAMEBUFFER目标之后,所有的读取写入帧缓冲的操作将会影响当前绑定的帧缓冲。我们也可以使用GL_READ_FRAMEBUFFER或GL_DRAW_FRAMEBUFFER,将一个帧缓冲分别绑定到读取目标或写入目标。

  • 绑定到GL_READ_FRAMEBUFFER的帧缓冲将会使用在所有像是glReadPixels的读取操作中
  • 绑定到GL_DRAW_FRAMEBUFFER的帧缓冲将会被用作渲染、清除等写入操作的目标。

大部分情况你都不需要区分它们,通常都会使用GL_FRAMEBUFFER,绑定到两个上。

我们现在还不能使用我们的帧缓冲,因为它还不完整,一个完整的帧缓冲需要满足以下的条件:

  • 附加至少一个缓冲(颜色、深度或模板缓冲)。
  • 至少有一个颜色附件(Attachment)。
  • 所有的附件都必须是完整的(保留了内存)。
  • 每个缓冲都应该有相同的样本数。

在完成所有的条件之后,我们可以以GL_FRAMEBUFFER为参数调glCheckFramebufferStatus,检查帧缓冲是否完整。返回的GL_FRAMEBUFFER_COMPLETE,帧缓冲就是完整的了。

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)// 执行胜利的舞蹈

之后所有的渲染操作将会渲染到当前绑定帧缓冲的附件中。由于我们的帧缓冲不是默认帧缓冲,渲染指令将不会对窗口的视觉输出有任何影响,出于这个原因,渲染到一个不同的帧缓冲被叫做离屏渲染,要保证所有的渲染操作在主窗口中有视觉效果,我们需要再次激活默认帧缓冲,将它绑定到defaultFramebufferObject()。

glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());

在完成所有的帧缓冲操作之后,不要忘记删除这个帧缓冲对象:

glDeleteFramebuffers(1, &fbo);

创建纹理附件:

当把一个纹理附加到帧缓冲的时候,所有的渲染指令将会写入到这个纹理中,就像它是一个普通的颜色/深度或模板缓冲一样。使用纹理的优点是,所有渲染操作的结果将会被储存在一个纹理图像中,我们之后可以在着色器中很方便地使用它。为帧缓冲创建一个纹理和创建一个普通的纹理差不多:

    //创建一个纹理glGenTextures(1, &texture);glBindTexture(GL_TEXTURE_2D, texture);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width(), height(), 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

主要的区别就是,我们将维度设置为了屏幕大小(尽管这不是必须的),并且我们给纹理的data参数传递了NULL。对于这个纹理,仅仅分配了内存而没有填充它。填充这个纹理将会在渲染到帧缓冲之后来进行。

将它附加到帧缓冲上:

//附加到帧缓冲上
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

glFrameBufferTexture2D有以下的参数:

  • target:帧缓冲的目标(绘制、读取或者两者皆有)
  • attachment:我们想要附加的附件类型。当前我们正在附加一个颜色附件。注意最后的0意味着可以附加多个颜色附件。
  • textarget:希望附加的纹理类型
  • texture:要附加的纹理本身
  • level:多级渐远纹理的级别。保留为0。

创建深度、模板缓冲对象,使用渲染缓冲对象:

和纹理图像一样,渲染缓冲对象是一个真正的缓冲,即一系列的字节、整数、像素等。渲染缓冲对象附加的好处是,它会将数据储存为OpenGL原生的渲染格式,它是为离屏渲染到帧缓冲优化过的。

    unsigned int rbo; glGenRenderbuffers(1, &rbo);

渲染缓冲对象直接将所有的渲染数据储存到它的缓冲中,不会做任何针对纹理格式的转换,让它变为一个更快的可写储存介质。然而,渲染缓冲对象通常都是只写的,所以你不能读取它们(比如使用纹理访问)。

我们需要绑定这个渲染缓冲对象,让之后所有的渲染缓冲操作影响当前的rbo:

 glBindRenderbuffer(GL_RENDERBUFFER, rbo);

由于渲染缓冲对象通常都是只写的,它们会经常用于深度和模板附件,因为大部分时间我们都不需要从深度和模板缓冲中读取值,只关心深度和模板测试。我们需要深度和模板值用于测试,但不需要对它们进行采样,所以渲染缓冲对象非常适合它们。当我们不需要从这些缓冲中采样的时候,通常都会选择渲染缓冲对象,因为它会更优化一点。

创建一个深度和模板渲染缓冲对象可以通过调用glRenderbufferStorage函数来完成:

    // 指定存储在 renderbuffer 中图像的宽高以及颜色格式,并按照此规格为之分配存储空间glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width(), height());

最后一件事就是附加这个渲染缓冲对象:

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

渲染缓冲对象能为你的帧缓冲对象提供一些优化,但知道什么时候使用渲染缓冲对象,什么时候使用纹理是很重要的。通常的规则是,如果你不需要从一个缓冲中采样数据,那么对这个缓冲使用渲染缓冲对象会是明智的选择。如果你需要从缓冲中采样颜色或深度值等数据,那么你应该选择纹理附件。性能方面它不会产生非常大的影响的。

渲染到纹理:

要想绘制场景到一个纹理上,我们需要采取以下的步骤:

  • 将新的帧缓冲绑定为激活的帧缓冲,和往常一样渲染场景
  • 绑定默认的帧缓冲
  • 绘制一个横跨整个屏幕的四边形,将帧缓冲的颜色缓冲作为它的纹理。
#include "axbopemglwidget.h"
#include "vertices.h"
const unsigned int timeOutmSec=50;QVector3D viewInitPos(0.0f,5.0f,20.0f);
float _near=0.1f,_far=100.0f;
QMatrix4x4 model;
QMatrix4x4 view;
QMatrix4x4 projection;
QPoint lastPos;vector<QVector3D> windows;
map<float, QVector3D> sorted;unsigned int fbo;
unsigned int rbo;
unsigned int texture;
AXBOpemglWidget::AXBOpemglWidget(QWidget *parent) : QOpenGLWidget(parent)
{connect(&m_timer,SIGNAL(timeout()),this,SLOT(on_timeout()));m_timer.start(timeOutmSec);m_time.start();m_camera.Position=viewInitPos;setFocusPolicy(Qt::StrongFocus);setMouseTracking(true);windows.push_back(QVector3D( 0.0f, 2.5f, 0.7f));foreach(auto item,windows) {float distance = m_camera.Position.distanceToPoint(item);sorted[distance] = item;}}AXBOpemglWidget::~AXBOpemglWidget()
{for(auto iter=m_Models.begin();iter!=m_Models.end();iter++){ModelInfo *modelInfo=&iter.value();delete modelInfo->model;}glDeleteFramebuffers(1, &fbo);
}void AXBOpemglWidget::loadModel(string path)
{static int i=0;makeCurrent();Model * _model=new Model(QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_3_Core>(),path.c_str());//m_camera.Position=cameraPosInit(_model->m_maxY,_model->m_minY);m_Models["Julian"+QString::number(i++)]=ModelInfo{_model,QVector3D(0,0-_model->m_minY,0),0.0,0.0,0.0,false,QString::fromLocal8Bit("张三")+QString::number(i++)};doneCurrent();
}void AXBOpemglWidget::initializeGL()
{initializeOpenGLFunctions();//创建VBO和VAO对象,并赋予IDbool success;m_ShaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,":/shaders/shaders/shapes.vert");m_ShaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,":/shaders/shaders/shapes.frag");success=m_ShaderProgram.link();if(!success) qDebug()<<"ERR:"<<m_ShaderProgram.log();m_BoxDiffuseTex=newQOpenGLTexture(QImage(":/images/images/container2.png").mirrored());m_WindowDiffuseTex=newQOpenGLTexture(QImage(":/images/images/blending_transparent_window.png"));m_PlaneDiffuseTex=newQOpenGLTexture(QImage(":/images/images/wall.jpg").mirrored());//创建一个自定义的帧缓冲glGenFramebuffers(1, &fbo);glBindFramebuffer(GL_FRAMEBUFFER, fbo);//创建一个纹理并作为帧缓冲的附件glGenTextures(1, &texture);glBindTexture(GL_TEXTURE_2D, texture);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width(), height(), 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);//创建深度、模板缓冲对象,使用渲染缓冲对象glGenRenderbuffers(1, &rbo);glBindRenderbuffer(GL_RENDERBUFFER, rbo);// 指定存储在 renderbuffer 中图像的宽高以及颜色格式,并按照此规格为之分配存储空间glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width(), height());//最后一件事就是附加这个渲染缓冲对象glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);//善后工作if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)qDebug() << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject() );m_PlaneMesh=processMesh(planeVertices,6,m_PlaneDiffuseTex->textureId());m_WindowMesh=processMesh(transparentVertices,6,texture);
}void AXBOpemglWidget::resizeGL(int w, int h)
{Q_UNUSED(w);Q_UNUSED(h);
}void AXBOpemglWidget::paintGL()
{model.setToIdentity();view.setToIdentity();projection.setToIdentity();// float time=m_time.elapsed()/50.0;projection.perspective(m_camera.Zoom,(float)width()/height(),_near,_far);view=m_camera.GetViewMatrix();glClearColor(0.2f, 0.3f, 0.3f, 1.0f);// first pass
glBindFramebuffer(GL_FRAMEBUFFER, fbo);glEnable(GL_DEPTH_TEST);glDepthFunc(GL_LESS);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);m_ShaderProgram.bind();m_ShaderProgram.setUniformValue("projection", projection);m_ShaderProgram.setUniformValue("view", view);//model.rotate(time, 1.0f, 1.0f, 0.5f);m_ShaderProgram.setUniformValue("viewPos",m_camera.Position);// light properties, note that all light colors are set at full intensitym_ShaderProgram.setUniformValue("light.ambient", 0.7f, 0.7f, 0.7f);m_ShaderProgram.setUniformValue("light.diffuse", 0.9f, 0.9f, 0.9f);m_ShaderProgram.setUniformValue("light.specular", 1.0f, 1.0f, 1.0f);// material propertiesm_ShaderProgram.setUniformValue("material.shininess", 32.0f);m_ShaderProgram.setUniformValue("light.direction", -0.2f, -1.0f, -0.3f);m_ShaderProgram.setUniformValue("model", model);m_PlaneMesh->Draw(m_ShaderProgram);foreach(auto modelInfo,m_Models){model.setToIdentity();model.translate(modelInfo.worldPos);model.rotate(modelInfo.pitch,QVector3D(1.0,0.0,0.0));model.rotate(modelInfo.yaw,QVector3D(0.0,1.0,0.0));model.rotate(modelInfo.roll,QVector3D(0.0,0.0,1.0));m_ShaderProgram.setUniformValue("model", model);modelInfo.model->Draw(m_ShaderProgram);}
// second pass
glBindFramebuffer(GL_FRAMEBUFFER,defaultFramebufferObject() ); // back to defaultmodel.setToIdentity();m_ShaderProgram.setUniformValue("model", model);
m_PlaneMesh->Draw(m_ShaderProgram);foreach(auto modelInfo,m_Models){model.setToIdentity();model.translate(modelInfo.worldPos);model.rotate(modelInfo.pitch,QVector3D(1.0,0.0,0.0));model.rotate(modelInfo.yaw,QVector3D(0.0,1.0,0.0));model.rotate(modelInfo.roll,QVector3D(0.0,0.0,1.0));m_ShaderProgram.setUniformValue("model", model);modelInfo.model->Draw(m_ShaderProgram);}for(map<float,QVector3D>::reverse_iterator riter=sorted.rbegin();riter!=sorted.rend();riter++){model.setToIdentity();model.translate(riter->second);m_ShaderProgram.setUniformValue("model", model);m_WindowMesh->Draw(m_ShaderProgram);}}void AXBOpemglWidget::wheelEvent(QWheelEvent *event)
{m_camera.ProcessMouseScroll(event->angleDelta().y()/120);
}void AXBOpemglWidget::keyPressEvent(QKeyEvent *event)
{float deltaTime=timeOutmSec/1000.0f;switch (event->key()) {case Qt::Key_W: m_camera.ProcessKeyboard(FORWARD,deltaTime);break;case Qt::Key_S: m_camera.ProcessKeyboard(BACKWARD,deltaTime);break;case Qt::Key_D: m_camera.ProcessKeyboard(RIGHT,deltaTime);break;case Qt::Key_A: m_camera.ProcessKeyboard(LEFT,deltaTime);break;case Qt::Key_Q: m_camera.ProcessKeyboard(DOWN,deltaTime);break;case Qt::Key_E: m_camera.ProcessKeyboard(UP,deltaTime);break;case Qt::Key_Space: m_camera.Position=viewInitPos;break;default:break;}
}void AXBOpemglWidget::mouseMoveEvent(QMouseEvent *event)
{makeCurrent();if(m_modelMoving){for(auto iter=m_Models.begin();iter!=m_Models.end();iter++){ModelInfo *modelInfo=&iter.value();if(!modelInfo->isSelected) continue;modelInfo->worldPos=QVector3D(worldPosFromViewPort(event->pos().x(),event->pos().y()));}}elseif(event->buttons() & Qt::RightButton|| event->buttons() & Qt::LeftButton|| event->buttons() & Qt::MiddleButton){auto currentPos=event->pos();QPoint deltaPos=currentPos-lastPos;lastPos=currentPos;if(event->buttons() & Qt::RightButton)m_camera.ProcessMouseMovement(deltaPos.x(),-deltaPos.y());elsefor(auto iter=m_Models.begin();iter!=m_Models.end();iter++){ModelInfo *modelInfo=&iter.value();if(!modelInfo->isSelected) continue;if(event->buttons() & Qt::MiddleButton){modelInfo->roll+=deltaPos.x();}else if(event->buttons() & Qt::LeftButton){modelInfo->yaw+=deltaPos.x();modelInfo->pitch+=deltaPos.y();}}}doneCurrent();
}void AXBOpemglWidget::mousePressEvent(QMouseEvent *event)
{bool hasSelected=false;makeCurrent();lastPos=event->pos();if(event->buttons()&Qt::LeftButton){QVector4D wolrdPostion=worldPosFromViewPort(event->pos().x(),event->pos().y());mousePickingPos(QVector3D(wolrdPostion));for(QMap<QString, ModelInfo>::iterator iter=m_Models.begin();iter!=m_Models.end();iter++){ModelInfo *modelInfo=&iter.value();float r=(modelInfo->model->m_maxY-modelInfo->model->m_minY)/2;if(modelInfo->worldPos.distanceToPoint(QVector3D(wolrdPostion))<r&&!hasSelected){modelInfo->isSelected=true;hasSelected=true;}elsemodelInfo->isSelected=false;//            qDebug()<<modelInfo->worldPos.distanceToPoint(QVector3D(wolrdPostion))//             <<"<"<<r<<"="<<modelInfo->isSelected;}}doneCurrent();
}void AXBOpemglWidget::mouseDoubleClickEvent(QMouseEvent *event)
{Q_UNUSED(event);if(m_modelMoving){//再次双击取消移动m_modelMoving=false;}elseforeach(auto modelInfo,m_Models){//双击启动移动if(modelInfo.isSelected==true)m_modelMoving=true;qDebug()<<modelInfo.name<<modelInfo.isSelected;}
}void AXBOpemglWidget::on_timeout()
{update();
}QVector3D AXBOpemglWidget::cameraPosInit(float maxY, float minY)
{QVector3D temp={0,0,0};float height=maxY-minY;temp.setZ(1.5*height);if(minY>=0)temp.setY(height/2.0);viewInitPos=temp;return temp;
}Mesh* AXBOpemglWidget::processMesh(float *vertices, int size, unsigned int textureId)
{vector<Vertex> _vertices;vector<unsigned int> _indices;vector<Texture> _textures;//memcpy(&_vertices[0],vertices,8*size*sizeof(float));for(int i=0;i<size;i++){Vertex vert;vert.Position[0]=vertices[i*5+0];vert.Position[1]=vertices[i*5+1];vert.Position[2]=vertices[i*5+2];vert.TexCoords[0]=vertices[i*5+3];vert.TexCoords[1]=vertices[i*5+4];_vertices.push_back(vert);_indices.push_back(i);}Texture tex; tex.id=textureId;tex.type="texture_diffuse";_textures.push_back(tex);return new Mesh(QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_3_Core>(),_vertices,_indices,_textures);
}QVector4D AXBOpemglWidget::worldPosFromViewPort(int posX, int posY)
{float winZ;glReadPixels(posX,this->height()-posY,1,1,GL_DEPTH_COMPONENT,GL_FLOAT,&winZ);float x=(2.0f*posX)/this->width()-1.0f;float y=1.0f-(2.0f*posY)/this->height();float z=winZ*2.0-1.0f;float w = (2.0 * _near * _far) / (_far + _near - z * (_far - _near));//float w= _near*_far/(_near*winZ-_far*winZ+_far);QVector4D wolrdPostion(x,y,z,1);wolrdPostion=w*wolrdPostion;return view.inverted()*projection.inverted()*wolrdPostion;
}

3.相关参考

OpenGL模型加载_Mr.codeee的博客-CSDN博客

OpenGL 鼠标拾取模型_Mr.codeee的博客-CSDN博客

4.完整工程 

https://download.csdn.net/download/wzz953200463/87947814icon-default.png?t=N5K3https://download.csdn.net/download/wzz953200463/87947814


http://www.ppmy.cn/news/571219.html

相关文章

推荐一款好用的时序预测工具——Alibaba DChain Forecast

前言 绝大部分行业场景&#xff0c;尤其是互联网、量化行业&#xff0c;每天都会产生大量的数据。金融领域股票价格随时间的走势&#xff1b;电商行业每日的销售额&#xff1b;旅游行业随着节假日周期变化的机票酒店价格等。我们称这种不同时间收到的&#xff0c;描述一个或多…

【Java 基础篇】Java文件类详解

文章目录 导言一、文件类概述二、创建文件三、删除文件四、遍历目录五、获取文件信息总结 导言 Java提供了丰富的文件操作功能&#xff0c;其中java.io.File类是用于处理文件和目录的主要类之一。通过File类&#xff0c;我们可以创建、删除、重命名和检查文件或目录的存在等操…

【看完就会】Jmeter接口测试之断言详解

什么是断言&#xff1f; 断言&#xff0c;这是一个软件术语&#xff0c;简单来说&#xff0c;就是依靠软件程序自动判断操作结果的正确性。在接口测试中&#xff0c;这里的断言就是判断接口请求是否符合预期&#xff0c;从而判断接口用例是否执行通过。 你应该也听过一个概念…

Go语言——线程、进程、协程的区别

线程、进程、协程的区别 进程、线程、协程对比 通俗描述 有一个老板想要开个工厂进行生产某件商品&#xff08;例如剪子&#xff09; 他需要花一些财力物力制作一条生产线&#xff0c;这个生产线上有很多的器件以及材料这些所有的 为了能够生产剪子而准备的资源称之为&#…

第十六届CISCN复现----MISC

1.被加密的生产流量 下载附件&#xff0c;发现是一个文件名为modus的压缩包&#xff0c;解压后是一个pcap文件&#xff0c;用wireshark打开 文件名modus&#xff0c;已经提示了工控流量&#xff0c;很多情况下都是和TCP协议结合起来的 工控CTF之协议分析1——Modbus_ctf modb…

解决不能完全卸载solidworks2016的方法之一

solidworks2016&#xff0c;以前在win7安装过&#xff0c;并且正常使用。 如今在win10安装&#xff0c;无法安装成功并且无法卸载干净。另外&#xff0c;还按照搜索到的一些方法进行了文件的删除。 但是依然每次开机都是配置画面&#xff0c;小崩溃┭┮﹏┭┮ 目前算是可以解…

ORACLE 完美卸载

完全卸载oracle11g步骤&#xff1a; 1、 开始&#xff0d;&#xff1e;设置&#xff0d;&#xff1e;控制面板&#xff0d;&#xff1e;管理工具&#xff0d;&#xff1e;服务&#xff0c;停止所有Oracle服务。 2、 开始&#xff0d;&#xff1e;程序&#xff0d;&#xff1e;…

完美卸载.net framework 2.0

卸载.net framework 2.0偶然发现电脑上基于.net的程序有一些不能使用了。比如vista master&#xff0c;vlite等。本以为这些程序不能在xp下运行&#xff0c;今天发现连xpmanger也出了问题。让我想到可能是.net framework 除了问题。上次卸载vs2008以后。发现其实很多东西没卸载…