基于Qt的OpenGL编程(3.x以上GLSL可编程管线版)---(二十一)帧缓冲

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/z136411501/article/details/83588308

Vries的教程是我看过的最好的可编程管线OpenGL教程,没有之一其原地址如下,https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/05%20Framebuffers/ 关于帧缓冲的详细知识了解请看原教程,本篇旨在对Vires基于visual studio平台的编程思想与c++代码做纯Qt平台的移植,代码移植顺序基本按照原教程顺序,并附加一些学习心得,重在记录自身学习之用

Tip: 这篇比较复杂,写了两份代码,第一份“FrameBuffers”是Vries代码的直接移植,使用的函数是glfw的原始OpenGL函数,与帧缓冲相关的代码有30行左右。第二份“FrameBuffers_QT”为使用Qt自己集成的帧缓冲相关代码,使用QOpenGLFrameBuffer类,只需要4行代码即可实现原始OpenGL函数的功能,但一些缓冲的细节问题无法复现。

 

程序源代码链接:https://pan.baidu.com/s/1ZoxVUOHz24H43BVGfOG89A 提取码:7pmt

编译环境:Qt5.9.4

编译器:Desktop Qt5.9.4 MSVC2017 64bit

IDE:QtCreator

 

一,什么是帧缓冲

  按照Vries的解释,帧缓冲就是把3D视口的场景实时转换为一张2D纹理,这样解释有些抽象,举例说明。使用帧缓冲技术,还是在深度测试时用的场景,但切换为线框模式时,却是一张四边形纹理式样

  而不是我们想象中的下图这样。

  这样把3D场景转换为一张纹理有什么好处呢,Vries解释道这样可以非常方便的对场景做特效处理。恰逢笔者最近在学习OpenCV,可以对一张纹理做很多事情,这是类似的思想。只需要简单修改片段着色器就可实现以下这些效果,当然这些改变都是实时的。

二,程序流程

  关于帧缓冲的知识,Vries在原文里写的非常详细。。。。。。与复杂。帧简单来说,帧缓冲是我们可以自定义的一种缓冲,我们可以按照需求,像这个缓冲里添加颜色部件(color attachment),深度部件(depth attachment)与模板部件(stencil attachment)的一种或几种,同时可以把在自定义缓冲中进行绘制的场景,用纹理的样式进行保存,步骤如下:

  1. 生成自定义帧缓冲,附加颜色与深度部件,绑定一张空纹理至这个帧缓冲中
  2. 将控制权由默认缓冲交至自定义帧缓冲
  3. 绘制场景,系统自动将数据填充至绑定的纹理中
  4. 释放自定义帧缓冲,控制权回到默认缓冲中
  5. 绘制与帧缓冲绑定的那张纹理

三,QT代码移植

好了,现在说代码。当然,我们可以通过Qt的上下文核函数,直接对OpenGL原始代码进行移植,如下。

 QOpenGLFunctions_3_3_Core *core  = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_3_Core>();
 
............
  core->glGenFramebuffers(1, &frameBuffer);
  core->glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);

  core->glGenTextures(1, &textureBuffer);
  core->glBindTexture(GL_TEXTURE_2D, textureBuffer);
  core->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, OGLMANAGER_WIDTH, OGLMANAGER_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
  core->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  core->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  core->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  core->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  core->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureBuffer, 0);

  core->glGenRenderbuffers(1, &renderBuffer);
  core->glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
  core->glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, OGLMANAGER_WIDTH, OGLMANAGER_HEIGHT);
  core->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderBuffer);


  if(core->glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    qDebug() << "ERROR::FRAMEBUFFER:: Framebuffer is not complete";
  core->glBindFramebuffer(GL_FRAMEBUFFER, 0);
...............

这是Vries的源代码,只要在每个函数的前方加上QT自身的核函数控制,即可直接运行,这里必须注意Qt自身的两个对OpenGL支持bug:

  1. 在paintGL()函数中绘制时,必须先绘制默认缓冲,在绘制自定义缓冲,这个顺序不能颠倒,即我们必须现在默认缓冲中绘制那张绑定纹理,再在自定义缓冲中绘制整个场景。(这个bug我找了四个小时)
  2. 在自定义缓冲中进行绘制时,必须使用顶点缓冲对象vertex buffer object(VBO),不能用顶点数组对象vertex Array Object(VAO),名词解释见这个链接Vries教程这是Qt的支持bug,暂时无解。(这个bug我找了八个小时🙂,找到时我都快哭了)

但如果使用Qt自己的QOpenGLFrameBufferObject类,一切又变得如此简单,只需要四行代码:

QOpenGLFrameBufferObject *fbo = new QOpenGLFramebufferObject(QSize(OGLMANAGER_WIDTH, OGLMANAGER_HEIGHT), QOpenGLFramebufferObject::CombinedDepthStencil, GL_TEXTURE_2D, GL_RGB);
//这行代码意思是生成一个帧缓冲,设置缓冲大小,添加深度与模板控件(颜色控件默认已添加),设置生成纹理的样式为2D普通纹理,纹理存储格式为GL_RGB
......
fbo->bind(); //绑定这个帧缓冲

绘制箱子场景;

fbo->release(); //释放这张帧缓冲,回到默认缓冲

core->glBindTexture(GL_TEXTURE_2D, fbo->texture()); //绑定fbo缓冲所生成的纹理ID,不能使用takeTexture()函数,因为该函数每次会重新创造绑定一张纹理,放在循环里不断运行内存会泄露
quad->draw();  //在四边形中绘制这张纹理

简单的令人发指,但缺陷也很明显,通过QT帮助文档,我们知道QOpenGLFramebufferObject默认一经生成会:

  1. 自动绑定一张纹理,但这个纹理的一些参数设置,如边缘的扩展方式“GL_CLAMP_TO_EDGE”等,暂未找到函数可以进行设置。
  2. 默认只添加颜色控件,且无法取消。深度与模板控件作为选择可以添加。
  3. 没有渲染缓冲对象,即RenderBuffer,所有的缓冲控件都放在FrameBuffer中。(渲染缓冲好处在于如果不需要从缓冲中对数据进行采样时,它比帧缓冲速度更快一些,但总体影响不大)

四,后期处理特效

    当3D场景变成了一张纹理,我们就可以很方便的做一些后期处理了。

    首先纹理时映射在一张四边形上的,数据如下:

  float vertices[] = {
  // positions    // textures
    -1.0f,  1.0f,  0.0f, 1.0f,
    -1.0f, -1.0f,  0.0f, 0.0f,
     1.0f, -1.0f,  1.0f, 0.0f,

    -1.0f,  1.0f,  0.0f, 1.0f,
     1.0f, -1.0f,  1.0f, 0.0f,
     1.0f,  1.0f,  1.0f, 1.0f
  };

  绑定纹理后,使用下述着色器渲染这张四边形

  screen.vert


#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTex;

out vec2 TexCoords;

void main(){
  gl_Position = vec4(aPos.x, aPos.y, 0.0f, 1.0f); //注意在载入数据时,z值应是0
  TexCoords = aTex;
}

  screen.frag


#version 330 core
out vec4 FragColor;
in vec2 TexCoords;

uniform sampler2D screenTexture;

void main(){
  vec3 color = texture2D(screenTexture, TexCoords).rgb;
  FragColor = vec4(color, 1.0f);
}

  这样我们就可以得到最初的效果了。

4.1反相

  只需要修改片段着色器,顶点着色器不变,即可增添特殊效果

  screen.frag

#version 330 core
out vec4 FragColor;
in vec2 TexCoords;

uniform sampler2D screenTexture;

void main(){
  vec3 color = texture2D(screenTexture, TexCoords).rgb;
  FragColor = vec4(1.0 - color, 1.0f);
}

4.2灰度

  screen.frag

#version 330 core
out vec4 FragColor;
in vec2 TexCoords;

uniform sampler2D screenTexture;

void main(){
  vec3 color = texture2D(screenTexture, TexCoords).rgb;
  float average = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
  FragColor = vec4(average, average, average, 1.0f);
}

4.3锐化

  这里要讲一个图像处理中较为常用的核函数(kernel)思想:将某点像素颜色与该点周围的像素颜色进行合并,做一些加权运算。

 

  如上图,中间像素点为选定像素点,该点最终的颜色表现为,该点自身的rgb颜色值乘以-15,再与周围八个像素点颜色的2倍进 行相加。最终得到的rgb颜色值,则为该点最终渲染出的颜色。这个表格数据就称为“kernel”。

  通过改变kernel的值,我们可以得到很多有趣的效果。

  引进kernel核思想,修改着色器如下:

  screen.frag


#version 330 core
out vec4 FragColor;
in vec2 TexCoords;

uniform sampler2D screenTexture;
uniform float kernels[9];
const float offset = 1.0f / 300.0f;

void main(){
  vec2 offsets[9] = vec2[](
    vec2(-offset,  offset),
    vec2( 0.0f,    offset),
    vec2( offset,  offset),
    vec2(-offset,  0.0f),
    vec2( 0.0f,    0.0f),
    vec2( offset,  0.0f),
    vec2(-offset, -offset),
    vec2( 0.0f,   -offset),
    vec2( offset, -offset)
          );


  vec3 sampleTex[9];
  for(int i = 0; i < 9; ++i)
    sampleTex[i] = texture2D(screenTexture, TexCoords.st + offsets[i]).rgb;

  vec3 color;
  for(int i = 0; i < 9; ++i)
    color += sampleTex[i] * kernels[i];

  FragColor = vec4(color, 1.0f);
}
    float kernels[9] = {
      -1, -1, -1,
      -1,  9, -1,
      -1, -1, -1
    };

4.4模糊

  修改kernel值:

    float kernels[9] = {
      1.0f/16.0f, 2.0f/16.0f, 1.0f/16.0f,
      2.0f/16.0f, 4.0f/16.0f, 2.0f/16.0f,
      1.0f/16.0f, 2.0f/16.0f, 1.0f/16.0f,

    };

4.5边缘检测

  修改kernel值:

    float kernels[9] = {
      1,  1, 1,
      1, -8, 1,
      1,  1, 1
    };

 

 

展开阅读全文

qt 帧缓冲,图像采集

12-13

rn您好,本人做了一个程序通过V4L2采集摄像头的图像使之显示在七寸的LCD液晶屏上,使用了QT做界面,利用的帧缓冲技术显示的QT 和采集的视频,此程序在idea6410开发板上可以正常运行。rn然后我在UT6410CV01核心板上做测试,同样使用AVIN采集视频,使用同样的系统和应用程序,可是在核心板上出现了一些问题,在LCD液晶屏上可以显示QT界面,但是采集的视频只显示一下,显示视频的区域就变黑了,但是QT界面正常,经测试程序没有跑飞,不知道哪里有什么问题,请指教。rn经检查,核心板提供的camera接口原理图供电电压是1.8v和2.8v,rn而开发板上camera接口电压是5v,我同时使用的AVIN模块,可是我将电路改为和开发板一样的接口电路,还是出现上述的问题,核心板上LCD接口电路,我采用是,开发板上应该是使用的,,,,,难道是因为不同的接口电路的原因吗,现在的问题就好像是QT的界面以显示视频的区域就变黑,但是我做过测试,单独采集视频通帧缓冲直接显示在LCD上可以显示,但是有QT界面就不行,可是带QT界面的视频采集程序可在开发板上显示正常啊,非常不解,,,,,我同时也测试过AVIN 采集模块的接口电压,图像变黑后,只有视频数据位是显示0v,其它位包括电源均正常。好像是什么东西促使AVIN模块不能正常采集图像了,但是我很困惑,找不到原因,希望得到你的技术支持,谢谢啦 论坛

没有更多推荐了,返回首页