(本文是LearnOpenGL的学习笔记, 教程中文翻译地址https://learnopengl-cn.github.io/(备用地址https://learnopengl-cn.readthedocs.io/zh/latest/),写于 2022-03-06)
0.前言(摘抄自 LearnOpenGL 教程中文版)
到目前为止,我们已经使用了很多屏幕缓冲了:用于写入颜色值的颜色缓冲、用于写入深度信息的深度缓冲和允许我们根据一些条件丢弃特定片段的模板缓冲。这些缓冲结合起来叫做帧缓冲(Framebuffer),它被储存在内存中。OpenGL允许我们定义我们自己的帧缓冲,也就是说我们能够定义我们自己的颜色缓冲,甚至是深度缓冲和模板缓冲。
我们目前所做的所有操作都是在默认帧缓冲的渲染缓冲上进行的。默认的帧缓冲一般框架或库会为我们生成和配置。有了我们自己的帧缓冲,我们就能够有更多方式来渲染了。
非默认的帧缓冲,渲染指令不会对窗口的视觉输出有任何影响,因此,渲染到一个自定义的帧缓冲也被叫做离屏渲染(Off-screen Rendering)。我们将场景渲染到一个附加到帧缓冲对象上的颜色纹理中,之后在默认帧缓冲绘制这个纹理就能将我们处理好的图像输出到窗口上。
1.知识点一个完整的帧缓冲需要满足以下的条件:
- 附加至少一个缓冲(颜色、深度或模板缓冲)。
- 至少有一个颜色附件(Attachment)。
- 所有的附件都必须是完整的(保留了内存)。
- 每个缓冲都应该有相同的样本数。
//为了简化操作,我这里将帧缓冲固定为400x400大小
int SCR_WIDTH = 400;
int SCR_HEIGHT = 400;
//创建帧缓冲对象
unsigned int framebuffer;
glGenFramebuffers(1, &framebuffer);
//绑定为激活的帧缓冲
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
//添加一个纹理附件
unsigned int texturebuffer;
glGenTextures(1, &texturebuffer);
glBindTexture(GL_TEXTURE_2D, texturebuffer);
//只设置了大小,分配了内存但是没填充数据(NULL),填充这个纹理将会在我们渲染到帧缓冲之后来进行
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_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, texturebuffer, 0);
//添加一个渲染缓冲附件
unsigned int rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT); // use a single renderbuffer object for both a depth AND stencil buffer.
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); // now actually attach it
//完成所有操作后,检测帧缓冲是否完整
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){
qDebug() bind();
glBindTexture(GL_TEXTURE_2D, fboBuffer->texture());
fboBuffer->addColorAttachment(fboBuffer->size(), GL_RGBA);
fboBuffer->release();
2.实现代码
(项目git链接:https://github.com/gongjianbo/OpenGLwithQtWidgets.git)
实现效果:
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//帧缓冲
//QOpenGLWidget窗口上下文
//QOpenGLFunctions访问OpenGL接口,可以不继承作为成员变量使用
class GLFrameBufferQt
: public QOpenGLWidget
, protected QOpenGLFunctions_4_5_Core
{
Q_OBJECT
public:
explicit GLFrameBufferQt(QWidget *parent = nullptr);
~GLFrameBufferQt();
protected:
//【】继承QOpenGLWidget后重写这三个虚函数
//设置OpenGL资源和状态。在第一次调用resizeGL或paintGL之前被调用一次
void initializeGL() override;
//渲染OpenGL场景,每当需要更新小部件时使用
void paintGL() override;
//设置OpenGL视口、投影等,每当尺寸大小改变时调用
void resizeGL(int width, int height) override;
//鼠标操作,重载Qt的事件处理
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
private:
//初始化
void initScreen();
void initFbo();
//初始化或resize之后重置帧缓冲区
void resetFbo();
//在paintGL中调用
void paintScreen();
void paintFbo();
//析构时调用
void freeScreen();
void freeFbo();
private:
//【】命名screen表示默认缓冲区相关
//着色器程序
QOpenGLShaderProgram screenShaderProgram;
//顶点数组对象
QOpenGLVertexArrayObject screenVao;
//顶点缓冲
QOpenGLBuffer screenVbo;
//【】命名fbo表示自定义缓冲区相关
//着色器程序
QOpenGLShaderProgram fboShaderProgram;
//顶点数组对象
QOpenGLVertexArrayObject fboCubeVao, fboPlaneVao;
//顶点缓冲
QOpenGLBuffer fboCubeVbo, fboPlaneVbo;
//纹理
QOpenGLTexture *fboCubeTexture{ nullptr }, *fboPlaneTexture{ nullptr };
//帧缓冲
QOpenGLFramebufferObject *fboBuffer{ nullptr };
//旋转
QVector3D rotationAxis;
QQuaternion rotationQuat;
//透视投影的fovy参数,视野范围
float projectionFovy{ 45.0f };
//鼠标位置
QPoint mousePos;
//右键菜单
QMenu *menu;
//图像处理算法选择
int algorithmType{ 0 };
int drawMode{ 0 };
};
#include "GLFrameBufferQt.h"
GLFrameBufferQt::GLFrameBufferQt(QWidget *parent)
: QOpenGLWidget(parent)
{
setFocusPolicy(Qt::ClickFocus); //默认没有焦点
QSurfaceFormat fmt = format();
fmt.setRenderableType(QSurfaceFormat::OpenGL);
fmt.setProfile(QSurfaceFormat::CoreProfile);
fmt.setVersion(4, 5);
setFormat(fmt);
//右键菜单
menu = new QMenu(this);
//0默认,1反相,2灰度,3锐化,4模糊,5边缘检测
menu->addAction("Default", [this]{ algorithmType = 0; update(); });
menu->addAction("Inversion", [this]{ algorithmType = 1; update(); });
menu->addAction("Gray", [this]{ algorithmType = 2; update(); });
menu->addAction("Sharpen", [this]{ algorithmType = 3; update(); });
menu->addAction("Blur", [this]{ algorithmType = 4; update(); });
menu->addAction("Edge-detection", [this]{ algorithmType = 5; update(); });
menu->addAction("Set Fill Mode", [this]{ drawMode = 0; update(); });
menu->addAction("Set Line Mode", [this]{ drawMode = 1; update(); });
menu->addAction("Set Point Mode", [this]{ drawMode = 2; update(); });
}
GLFrameBufferQt::~GLFrameBufferQt()
{
//initializeGL在显示时才调用,释放未初始化的会异常
if(!isValid())
return;
//QOpenGLWidget
//三个虚函数不需要makeCurrent,对应的操作已由框架完成
//但是释放时需要设置当前上下文
makeCurrent();
freeScreen();
freeFbo();
doneCurrent();
}
void GLFrameBufferQt::initializeGL()
{
//QOpenGLFunctions
//为当前上下文初始化opengl函数解析
initializeOpenGLFunctions();
initScreen();
initFbo();
}
void GLFrameBufferQt::paintGL()
{
//渲染自定义帧缓冲
paintFbo();
//渲染默认帧缓冲
paintScreen();
}
void GLFrameBufferQt::resizeGL(int width, int height)
{
if (width < 1 || height < 1) {
return;
}
//重置自定义帧缓冲大小
resetFbo();
}
void GLFrameBufferQt::mousePressEvent(QMouseEvent *event)
{
event->accept();
if(event->button()==Qt::RightButton){
menu->popup(QCursor::pos());
}else{
mousePos = event->pos();
}
}
void GLFrameBufferQt::mouseReleaseEvent(QMouseEvent *event)
{
event->accept();
}
void GLFrameBufferQt::mouseMoveEvent(QMouseEvent *event)
{
event->accept();
//参照示例cube
QVector2D diff = QVector2D(event->pos()) - QVector2D(mousePos);
mousePos = event->pos();
QVector3D n = QVector3D(diff.y(), diff.x(), 0.0).normalized();
rotationAxis = (rotationAxis + n).normalized();
//不能对换乘的顺序
rotationQuat = QQuaternion::fromAxisAndAngle(rotationAxis, 2.0f) * rotationQuat;
update();
}
void GLFrameBufferQt::wheelEvent(QWheelEvent *event)
{
event->accept();
//fovy越小,模型看起来越大
if(event->delta() < 0){
//鼠标向下滑动为-,这里作为zoom out
projectionFovy += 0.5f;
if(projectionFovy > 90)
projectionFovy = 90;
}else{
//鼠标向上滑动为+,这里作为zoom in
projectionFovy -= 0.5f;
if(projectionFovy < 1)
projectionFovy = 1;
}
update();
}
void GLFrameBufferQt::initScreen()
{
glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());
//着色器代码
//in输入,out输出,uniform从cpu向gpu发送
const char *vertex_str = R"(#version 450
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoords;
out vec2 TexCoords;
void main()
{
TexCoords = aTexCoords;
gl_Position = vec4(aPos.x, aPos.y, 0.0f, 1.0f);
})";
//算法选择:0默认,1反相,2灰度,3锐化,4模糊,5边缘检测
const char *fragment_str = R"(#version 450
out vec4 FragColor;
in vec2 TexCoords;
uniform int algorithm;
const float offset = 1.0f / 300.0f;
uniform sampler2D screenTexture;
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)
);
switch(algorithm)
{
case 0:{
vec3 color = texture(screenTexture, TexCoords).rgb;
FragColor = vec4(color, 1.0f);
}break;
case 1:{
vec3 color = texture(screenTexture, TexCoords).rgb;
FragColor = vec4(1.0f - color, 1.0f);
}break;
case 2:{
vec3 color = texture(screenTexture, TexCoords).rgb;
float average = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
FragColor = vec4(average, average, average, 1.0f);
}break;
case 3:{
float kernels[9] = float[](
-1, -1, -1,
-1, 9, -1,
-1, -1, -1
);
vec3 sampleTex[9];
for(int i = 0; i < 9; ++i)
sampleTex[i] = texture(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);
}break;
case 4:{
float kernels[9] = float[](
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
);
vec3 sampleTex[9];
for(int i = 0; i < 9; ++i)
sampleTex[i] = texture(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);
}break;
case 5:{
float kernels[9] = float[](
1, 1, 1,
1, -9, 1,
1, 1, 1
);
vec3 sampleTex[9];
for(int i = 0; i < 9; ++i)
sampleTex[i] = texture(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);
}break;
default:{
vec3 color = texture(screenTexture, TexCoords).rgb;
FragColor = vec4(color, 1.0f);
}break;
}
})";
//将source编译为指定类型的着色器,并添加到此着色器程序
if(!screenShaderProgram.addCacheableShaderFromSourceCode(
QOpenGLShader::Vertex,vertex_str)){
qDebug()
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?