如果不理解pbo 那先去了解概念,在此不再解释,这是我为了做融合服务器viewpointserver做的一部分工作,融合服务器的功能是将三维和流媒体,AI融合在一起,viewpointserver会直接读取三维工程的文件,同时融合rtsp视频流,将视频流作为纹理给材质,最后赋值给三维模型如fbx的表面。由于没有窗口,三维作为服务运行,里面有一项工作就是三维的抗锯齿采集。
PBO(Pixel Buffer Object)进行多重采样抗锯齿(MSAA)的一般步骤
1 初始化 OpenGL 和 MSAA 帧缓冲:
1.1开启 MSAA:
在初始化 OpenGL 时,通过设置相关参数开启多重采样抗锯齿。例如,使用 glEnable(GL_MULTISAMPLE) 函数来启用 OpenGL 的多重采样功能。
1.2 创建帧缓冲:
创建一个帧缓冲对象(FBO),用于存储多重采样后的图像数据。这可以通过 glGenFramebuffers 和 glBindFramebuffer 等函数来完成。
1.3 配置 FBO 的附件:
为 FBO 配置颜色附件和深度附件。对于颜色附件,创建一个纹理,并将其绑定到 FBO 上;对于深度附件,可以创建一个渲染缓冲对象(RBO)并绑定到 FBO。确保纹理和 RBO 的尺寸与渲染窗口的尺寸相匹配。
2 渲染到 MSAA 帧缓冲:
2.1 绑定 MSAA 帧缓冲:
在渲染场景之前,使用 glBindFramebuffer 函数将之前创建的 MSAA FBO 绑定为当前的渲染目标。这样,后续的渲染操作将把图像数据渲染到 MSAA FBO 中。
2.2 进行常规渲染:
按照正常的 OpenGL 渲染流程绘制场景中的物体。由于启用了 MSAA,OpenGL 会在每个像素内进行多个子采样,以实现抗锯齿效果。
3 使用 PBO 读取 MSAA 数据:
创建 PBO:使用 glGenBuffers 函数创建一个 PBO,并使用 glBindBuffer 函数将其绑定到 GL_PIXEL_PACK_BUFFER 目标上。然后,使用 glBufferData 函数为 PBO 分配足够的内存空间,以存储从 MSAA FBO 读取的像素数据。
读取像素数据到 PBO:在渲染完成后,使用 glReadPixels 函数将 MSAA FBO 中的像素数据读取到 PBO 中。由于 PBO 的存在,这个操作可以在后台异步进行,减少对 CPU 的阻塞。
4 处理和解析 PBO 中的数据:
4.1 映射 PBO:
使用 glMapBuffer 函数将 PBO 映射到 CPU 可访问的内存空间,以便读取和处理像素数据。这将返回一个指向 PBO 内存的指针,可以通过该指针访问像素数据。
4.2 解析像素数据:
根据需要,对 PBO 中的像素数据进行处理和解析。例如,可以将像素数据转换为图像格式,以便保存为文件或进行其他操作。在处理像素数据时,需要考虑 MSAA 的子采样信息,通常需要对多个子采样点的颜色值进行合并或平均,以得到最终的抗锯齿效果。
4.3 取消映射 PBO:
完成对 PBO 数据的处理后,使用 glUnmapBuffer 函数取消对 PBO 的映射,释放 CPU 对 PBO 内存的访问。
5 显示或使用抗锯齿后的图像:
5.1 将处理后的像素数据显示在屏幕上:
如果需要在屏幕上显示抗锯齿后的图像,可以使用 glDrawPixels 或其他相关的 OpenGL 函数将处理后的像素数据绘制到默认的帧缓冲中,然后通过交换缓冲区来显示在屏幕上。
5.2 保存为图像文件:
可以将处理后的像素数据保存为图像文件,以便后续使用。这可以通过使用图像库(如 stb_image 库)来实现,将像素数据写入图像文件中。
使用深度缓冲的例子
以下使用qt来做,qt的优点是很多都是封装好的,比较容易实现,不用引入额外的库,当然我们也可以使用glfw来做,后面会给出例子
c
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QtWidgets/QWidget>
#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Core>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLTexture>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include "ui_MainWindow.h"
QT_BEGIN_NAMESPACE
namespace Ui {
class CMainWindow;
}
QT_END_NAMESPACE
class CMainWindow : public QOpenGLWidget, protected QOpenGLFunctions_4_5_Core
{
Q_OBJECT
public:
CMainWindow(QWidget *parent = Q_NULLPTR);
~CMainWindow();
void initFBO();
protected:
void initializeGL();
void paintGL();
void resizeGL(int w, int h);
private:
QImage m_img;
GLsizei m_width = 0;
GLsizei m_height = 0;
GLsizeiptr m_dataSize = 0;
GLuint m_VBO = 0;
GLuint m_VAO = 0;
GLuint m_EBO = 0;
GLuint m_texture = 0;
GLuint m_frameBuffer = 0;
GLuint m_RBO = 0;
GLuint m_fVBO = 0;
GLuint m_fVAO = 0;
GLuint m_textureFBO = 0;
QOpenGLShaderProgram *m_programScreen = nullptr;
QOpenGLShaderProgram *m_shaderProgram = nullptr;
private:
Ui::CMainWindow *ui;
};
#endif //MAINWINDOW_H
c
#include "MainWindow.h"
CMainWindow::CMainWindow(QWidget* parent)
: QOpenGLWidget(parent), ui(new Ui::CMainWindow)
{
ui->setupUi(this);
m_img = QImage("2.jpg");//picture
m_width = m_img.width();
m_height = m_img.height();
m_dataSize = m_width * m_height * 3;
}
CMainWindow::~CMainWindow()
{
delete ui;
glDeleteVertexArrays(1, &m_VAO);
glDeleteBuffers(1, &m_VBO);
glDeleteBuffers(1, &m_EBO);
glDeleteVertexArrays(1, &m_fVAO);
glDeleteBuffers(1, &m_fVBO);
glDeleteBuffers(1, &m_frameBuffer);
glDeleteBuffers(1, &m_RBO);
}
void CMainWindow::initFBO()
{
glGenFramebuffers(1, &m_frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, m_frameBuffer);
glGenTextures(1, &m_textureFBO);
glBindTexture(GL_TEXTURE_2D, m_textureFBO);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_width, m_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, nullptr);//TODO
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);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_textureFBO, 0);
glGenRenderbuffers(1, &m_RBO);
glBindRenderbuffer(GL_RENDERBUFFER, m_RBO);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, m_width, m_height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_RBO);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
qDebug() << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" ;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
char vertexShaderSource[] =
"#version 450 core\n"
"layout (location = 0) in vec2 aPos;\n"
"layout (location = 1) in vec2 aTexCoords;\n"
"out vec2 TexCoords;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);\n"
"TexCoords = aTexCoords;\n"
"}\n";
char fragmentShaderSource[] =
"#version 450 core\n"
"out vec4 FragColor;\n"
"in vec2 TexCoords;\n"
"uniform sampler2D screenTexture;\n"
"void main()\n"
"{\n"
" FragColor = texture(screenTexture, TexCoords);\n"
"}\n";
m_programScreen = new QOpenGLShaderProgram;
m_programScreen->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
m_programScreen->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
m_programScreen->link();
m_programScreen->bind();
m_programScreen->setUniformValue("screenTexture", 1);
m_programScreen->release();
float fb_Vertices[] = {
// positions // texCoords
-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
};
glGenVertexArrays(1, &m_fVAO);
glBindVertexArray(m_fVAO);
glGenBuffers(1, &m_fVBO);
glBindBuffer(GL_ARRAY_BUFFER, m_fVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(fb_Vertices), fb_Vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)(2 * sizeof(float)));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void CMainWindow::initializeGL()
{
this->initializeOpenGLFunctions();
char vertexShaderSource[] =
"#version 450 core\n"
"layout(location = 0) in vec3 aPos;\n"
"layout(location = 1) in vec3 aColor;\n"
"layout(location = 2) in vec2 aTexcood;\n"
"out vec3 vertexColor;\n"
"out vec2 m_tex;\n"
"void main()\n"
"{\n"
" m_tex = aTexcood;\n"
" vertexColor = aColor;\n"
" gl_Position = vec4(aPos, 1.0);\n"
"}\n";
char fragmentShaderSource[] =
"#version 450 core\n"
"out vec4 FragColor;\n"
"in vec3 vertexColor;\n"
"in vec2 m_tex;\n"
"uniform sampler2D ourTexture;\n"
"void main()\n"
"{\n"
" FragColor = texture2D(ourTexture, m_tex) * vec4(vertexColor, 1.0f);\n"
"}\n";
m_shaderProgram = new QOpenGLShaderProgram;
m_shaderProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
m_shaderProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
m_shaderProgram->link();
m_shaderProgram->bind();
m_shaderProgram->setUniformValue("ourTexture", 0);
m_shaderProgram->release();
GLfloat vertices[] = {
-1.0f, -1.0f,0.0f, 0.0f,0.0f,1.0f, 0.0f,0.0f,
1.0f, -1.0f,0.0f, 0.0f,1.0f,0.0f, 1.0f,0.0f,
1.0f, 1.0f,0.0f, 1.0f,0.0f,0.0f, 1.0f,1.0f,
-1.0f, 1.0f,0.0f, 0.0f,1.0f,1.0f, 0.0f,1.0f,
};
GLuint indices[] = {
0,1,2,
0,2,3
};
glGenVertexArrays(1, &m_VAO);
glBindVertexArray(m_VAO);
glGenBuffers(1, &m_VBO);
glBindBuffer(GL_ARRAY_BUFFER, m_VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glGenBuffers(1, &m_EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glGenTextures(1, &m_texture);
glBindTexture(GL_TEXTURE_2D, m_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_width, m_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, m_img.bits());//TODO
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
//初始化FBO
initFBO();
}
void CMainWindow::paintGL()
{
glBindFramebuffer(GL_FRAMEBUFFER, m_frameBuffer);
glEnable(GL_DEPTH_TEST);
glClearColor(0.5f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
m_shaderProgram->bind();
glBindVertexArray(m_VAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_texture);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
m_shaderProgram->release();
/********** 关键之处 ***********/
GLuint fb = context()->defaultFramebufferObject();
glBindFramebuffer(GL_FRAMEBUFFER, fb);
glDisable(GL_DEPTH_TEST);
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, m_width, m_height);
m_programScreen->bind();
glBindVertexArray(m_fVAO);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, m_textureFBO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
m_programScreen->release();
}
void CMainWindow::resizeGL(int w, int h)
{
glViewport(0, 0, w, h);
}
qt中采集场景如图
下面给出glfw的例子
glfw
首先初始化 OpenGL、GLFW 和 GLEW,也可以用glad,并创建了 MSAA FBO、颜色纹理、深度 RBO 和 PBO。然后,在渲染循环中,先将场景渲染到 MSAA FBO 中,再将 FBO 中的像素数据读取到 PBO 中,并对 PBO 中的像素数据进行处理。最后,清理资源并退出程序。当然实际应用中确实需要根据具体需求进行更多的错误处理和优化。
c
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <cstdlib>
// 窗口尺寸
const int WIDTH = 800;
const int HEIGHT = 600;
// 创建 PBO
GLuint pbo;
// 创建 MSAA FBO
GLuint fbo;
// 颜色纹理
GLuint colorTexture;
// 深度 RBO
GLuint depthRBO;
void init() {
// 初始化 GLFW
if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW" << std::endl;
exit(EXIT_FAILURE);
}
// 创建窗口
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "MSAA with PBO", nullptr, nullptr);
if (!window) {
std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate();
exit(EXIT_FAILURE);
}
// 将窗口设置为当前上下文
glfwMakeContextCurrent(window);
// 初始化 GLEW
glewExperimental = GL_TRUE;
if (glewInit()!= GLEW_OK) {
std::cerr << "Failed to initialize GLEW" << std::endl;
exit(EXIT_FAILURE);
}
// 开启多重采样抗锯齿
glEnable(GL_MULTISAMPLE);
// 创建 FBO
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// 创建颜色纹理
glGenTextures(1, &colorTexture);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, colorTexture);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, WIDTH, HEIGHT, GL_TRUE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, colorTexture, 0);
// 创建深度 RBO
glGenRenderbuffers(1, &depthRBO);
glBindRenderbuffer(GL_RENDERBUFFER, depthRBO);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, WIDTH, HEIGHT);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRBO);
// 检查 FBO 是否完整
if (glCheckFramebufferStatus(GL_FRAMEBUFFER)!= GL_FRAMEBUFFER_COMPLETE) {
std::cerr << "Failed to create FBO" << std::endl;
exit(EXIT_FAILURE);
}
// 创建 PBO
glGenBuffers(1, &pbo);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
glBufferData(GL_PIXEL_PACK_BUFFER, WIDTH * HEIGHT * 4, nullptr, GL_STREAM_READ);
}
void renderScene() {
// 绑定 MSAA FBO 并渲染场景
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glViewport(0, 0, WIDTH, HEIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 在这里进行场景的绘制操作
// 解除绑定 FBO
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void processPixels() {
// 将 MSAA FBO 中的像素数据读取到 PBO
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
glReadPixels(0, 0, WIDTH, HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
// 映射 PBO 到 CPU 可访问的内存
GLubyte* pixels = (GLubyte*)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
if (pixels) {
// 在这里处理像素数据,例如保存为图像或进行其他操作
// 为了简单起见,这里只是打印一些像素信息
for (int i = 0; i < 10; i++) {
std::cout << "Pixel " << i << ": ("
<< (int)pixels[4 * i] << ", "
<< (int)pixels[4 * i + 1] << ", "
<< (int)pixels[4 * i + 2] << ", "
<< (int)pixels[4 * i + 3] << ")" << std::endl;
}
// 取消映射 PBO
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
}
}
int main() {
init();
// 渲染循环
while (!glfwWindowShouldClose(glfwGetCurrentContext())) {
renderScene();
processPixels();
// 交换缓冲区并处理事件
glfwSwapBuffers(glfwGetCurrentContext());
glfwPollEvents();
}
// 清理资源
glDeleteBuffers(1, &pbo);
glDeleteTextures(1, &colorTexture);
glDeleteRenderbuffers(1, &depthRBO);
glDeleteFramebuffers(1, &fbo);
glfwTerminate();
return 0;
}
存文件测试
c
static void saveImage(GLuint& pbo, const std::string& filename, int width, int height) {
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
// 将帧缓冲区内容读取到 PBO
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
// 映射 PBO 获取数据指针
GLubyte* pixels = (GLubyte*)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
if (pixels)
{
stbi_write_png(filename.c_str(), width, height, 4, pixels, width * 4);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
}
else
{
std::cerr << "Failed to map." << std::endl;
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
如下图所示,存的图像是倒立镜像的,实际上是正立的三角形,还没有将视频附上去,工作量有些大,等到下一个文章再写。