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对象,并赋予ID
bool 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=new
QOpenGLTexture(QImage(":/images/images/container2.png").mirrored());
m_WindowDiffuseTex=new
QOpenGLTexture(QImage(":/images/images/blending_transparent_window.png"));
m_PlaneDiffuseTex=new
QOpenGLTexture(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 intensity
m_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 properties
m_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 default
model.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()));
}
}else
if(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());
else
for(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;
}
else
modelInfo->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;
}else
foreach(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/87947814https://download.csdn.net/download/wzz953200463/87947814