在Qt中使用OpenGL显示大量点(点云)
在Qt中高效显示大量点(点云),可以使用QOpenGLWidget结合现代OpenGL技术。
一、基本实现步骤
1. 创建QOpenGLWidget子类
#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>class PointCloudViewer : public QOpenGLWidget, protected QOpenGLFunctions_4_5_Core
{Q_OBJECT
public:explicit PointCloudViewer(QWidget *parent = nullptr);~PointCloudViewer();void setPointCloud(const QVector<QVector3D> &points, const QVector<QVector3D> &colors = {});protected:void initializeGL() override;void resizeGL(int w, int h) override;void paintGL() override;private:QOpenGLShaderProgram *m_program;QOpenGLBuffer m_vbo;QMatrix4x4 m_projection;QVector<QVector3D> m_points;QVector<QVector3D> m_colors;int m_pointCount;
};
2. 实现类方法
PointCloudViewer::PointCloudViewer(QWidget *parent): QOpenGLWidget(parent), m_program(nullptr), m_pointCount(0)
{QSurfaceFormat format;format.setSamples(4);format.setVersion(3, 3);format.setProfile(QSurfaceFormat::CoreProfile);setFormat(format);
}PointCloudViewer::~PointCloudViewer()
{makeCurrent();m_vbo.destroy();delete m_program;doneCurrent();
}void PointCloudViewer::setPointCloud(const QVector<QVector3D> &points, const QVector<QVector3D> &colors)
{m_points = points;m_colors = colors.isEmpty() ? QVector<QVector3D>(points.size(), QVector3D(1,1,1)) : colors;m_pointCount = points.size();if (isValid()) {makeCurrent();// 将数据上传到GPUm_vbo.bind();m_vbo.allocate(m_pointCount * (3 + 3) * sizeof(float));float *data = static_cast<float*>(m_vbo.map(QOpenGLBuffer::WriteOnly));for (int i = 0; i < m_pointCount; ++i) {data[i*6 + 0] = m_points[i].x();data[i*6 + 1] = m_points[i].y();data[i*6 + 2] = m_points[i].z();data[i*6 + 3] = m_colors[i].x();data[i*6 + 4] = m_colors[i].y();data[i*6 + 5] = m_colors[i].z();}m_vbo.unmap();m_vbo.release();doneCurrent();update();}
}void PointCloudViewer::initializeGL()
{initializeOpenGLFunctions();glClearColor(0.1f, 0.1f, 0.1f, 1.0f);// 创建着色器程序m_program = new QOpenGLShaderProgram(this);m_program->addShaderFromSourceCode(QOpenGLShader::Vertex,"#version 330 core\n""layout(location = 0) in vec3 position;\n""layout(location = 1) in vec3 color;\n""uniform mat4 projection;\n""out vec3 fragColor;\n""void main() {\n"" gl_Position = projection * vec4(position, 1.0);\n"" fragColor = color;\n""}");m_program->addShaderFromSourceCode(QOpenGLShader::Fragment,"#version 330 core\n""in vec3 fragColor;\n""out vec4 outColor;\n""void main() {\n"" outColor = vec4(fragColor, 1.0);\n""}");m_program->link();// 创建VBOm_vbo.create();m_vbo.setUsagePattern(QOpenGLBuffer::StaticDraw);if (!m_points.isEmpty()) {setPointCloud(m_points, m_colors);}
}void PointCloudViewer::resizeGL(int w, int h)
{m_projection.setToIdentity();m_projection.perspective(45.0f, GLfloat(w) / h, 0.01f, 100.0f);
}void PointCloudViewer::paintGL()
{glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);if (m_pointCount == 0) return;m_program->bind();m_program->setUniformValue("projection", m_projection);m_vbo.bind();glEnableVertexAttribArray(0);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(1);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));glPointSize(2.0f);glDrawArrays(GL_POINTS, 0, m_pointCount);m_vbo.release();m_program->release();
}
二、优化大量点云显示
对于非常大的点云(数十万或数百万点),可以考虑以下优化:
1. 使用顶点缓冲对象(VBO)和顶点数组对象(VAO)
cpp
// 在类定义中添加
QOpenGLVertexArrayObject m_vao;// 在initializeGL中初始化VAO
m_vao.create();
m_vao.bind();// 在paintGL中使用VAO
m_vao.bind();
glDrawArrays(GL_POINTS, 0, m_pointCount);
m_vao.release();
2. 使用几何着色器调整点大小(可选)
cpp
// 添加几何着色器
m_program->addShaderFromSourceCode(QOpenGLShader::Geometry,"#version 330 core\n""layout(points) in;\n""layout(triangle_strip, max_vertices = 4) out;\n""uniform mat4 projection;\n""uniform float pointSize;\n""in vec3 fragColor[];\n""out vec3 gColor;\n""void main() {\n"" gColor = fragColor[0];\n"" vec4 pos = gl_in[0].gl_Position;\n"" gl_Position = pos + vec4(-pointSize, -pointSize, 0, 0);\n"" EmitVertex();\n"" gl_Position = pos + vec4(pointSize, -pointSize, 0, 0);\n"" EmitVertex();\n"" gl_Position = pos + vec4(-pointSize, pointSize, 0, 0);\n"" EmitVertex();\n"" gl_Position = pos + vec4(pointSize, pointSize, 0, 0);\n"" EmitVertex();\n"" EndPrimitive();\n""}");
3. 实现LOD(细节层次)渲染
对于极大点云,可以根据视距动态调整显示的点的密度:
cpp
void PointCloudViewer::paintGL()
{// ...其他代码...// 根据距离计算采样率float lodFactor = calculateLODFactor(); // 实现此函数根据相机距离返回0-1值if (lodFactor < 0.3f) {// 高细节:显示所有点glDrawArrays(GL_POINTS, 0, m_pointCount);} else {// 低细节:每N个点显示一个int step = static_cast<int>(1.0f + lodFactor * 10);glDrawArrays(GL_POINTS, 0, m_pointCount / step);}// ...其他代码...
}
4. 使用计算着色器进行点云处理(OpenGL 4.3+)
对于高级应用,可以使用计算着色器在GPU上进行点云处理。
5. 使用顶点缓冲对象(VBO)和顶点数组对象(VAO)实现代码
PointCloudViewer.h
#ifndef POINTCLOUDVIEWER_H
#define POINTCLOUDVIEWER_H#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>class PointCloudViewer : public QOpenGLWidget, protected QOpenGLFunctions_4_5_Core
{Q_OBJECT
public:explicit PointCloudViewer(QWidget *parent = nullptr);~PointCloudViewer();void setPointCloud(const QVector<QVector3D> &points, const QVector<QVector3D> &colors = {});protected:void initializeGL() override;void resizeGL(int w, int h) override;void paintGL() override;private:QOpenGLShaderProgram *m_program;QOpenGLVertexArrayObject m_vao;QOpenGLBuffer m_vbo;QMatrix4x4 m_projection;QVector<QVector3D> m_points;QVector<QVector3D> m_colors;int m_pointCount;bool m_dataUploaded;
};#endif // POINTCLOUDVIEWER_H
PointCloudViewer.cpp
#include "pointcloudviewer.h"
#include <QDebug>PointCloudViewer::PointCloudViewer(QWidget *parent): QOpenGLWidget(parent), m_program(nullptr), m_pointCount(0)
{/*QSurfaceFormat format;format.setSamples(4);format.setVersion(3, 3);// 移除下面这行或改为CompatibilityProfileformat.setProfile(QSurfaceFormat::CoreProfile);//format.setProfile(QSurfaceFormat::CompatibilityProfile);setFormat(format);*/m_dataUploaded = false;m_projection.setToIdentity();
}PointCloudViewer::~PointCloudViewer()
{makeCurrent();m_vbo.destroy();delete m_program;doneCurrent();
}void PointCloudViewer::setPointCloud(const QVector<QVector3D> &points, const QVector<QVector3D> &colors)
{m_points = points;m_colors = colors.isEmpty() ? QVector<QVector3D>(points.size(), QVector3D(1,1,1)) : colors;m_pointCount = points.size();m_dataUploaded = false;if (isValid()) {makeCurrent();// 将数据上传到GPUm_vao.bind();m_vbo.bind();m_vbo.allocate(m_pointCount * (3 + 3) * sizeof(float));float *data = static_cast<float*>(m_vbo.map(QOpenGLBuffer::WriteOnly));for (int i = 0; i < m_pointCount; ++i) {data[i*6 + 0] = m_points[i].x();data[i*6 + 1] = m_points[i].y();data[i*6 + 2] = m_points[i].z();data[i*6 + 3] = m_colors[i].x();data[i*6 + 4] = m_colors[i].y();data[i*6 + 5] = m_colors[i].z();}m_vbo.unmap();m_vbo.release();m_vao.release();m_dataUploaded = true;doneCurrent();update();}
}void PointCloudViewer::initializeGL()
{initializeOpenGLFunctions();glClearColor(0.1f, 0.1f, 0.1f, 1.0f);glEnable(GL_DEPTH_TEST);glEnable(GL_PROGRAM_POINT_SIZE);// 创建VAOm_vao.create();m_vao.bind();// 创建VBOm_vbo.create();m_vbo.setUsagePattern(QOpenGLBuffer::StaticDraw);m_vbo.bind();// 设置顶点属性glEnableVertexAttribArray(0);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(1);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));m_vbo.release();m_vao.release();// 创建着色器程序m_program = new QOpenGLShaderProgram(this);m_program->addShaderFromSourceCode(QOpenGLShader::Vertex,"#version 450 core\n""layout (location=0) in vec3 position;\n""layout (location=1) in vec3 color;\n""uniform mat4 projection;\n""out vec3 fragSetColor;\n""void main() {\n"" gl_Position = projection * vec4(position, 1.0);\n"" fragSetColor = color;\n"" gl_PointSize = 2.0;\n" // 直接在着色器中设置点大小"}");m_program->addShaderFromSourceCode(QOpenGLShader::Fragment,"#version 150 core\n""in vec3 fragSetColor;\n""out vec4 FragColor;\n""void main() {\n"" FragColor = vec4(fragSetColor, 1.0);\n""}");if(!m_program->link()) {qDebug() << "Shader link error:" << m_program->log();}if (!m_points.isEmpty()) {setPointCloud(m_points, m_colors);}
}void PointCloudViewer::resizeGL(int w, int h)
{m_projection.setToIdentity();m_projection.perspective(45.0f, GLfloat(w) / h, 0.01f, 100.0f);
}void PointCloudViewer::paintGL()
{glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);if (!m_dataUploaded || m_pointCount == 0) return;m_program->bind();m_program->setUniformValue("projection", m_projection);m_vao.bind();glDrawArrays(GL_POINTS, 0, m_pointCount);m_vao.release();m_program->release();
}
三、使用方法
cpp
// 在主窗口中使用
MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent)
{PointCloudViewer *viewer = new PointCloudViewer(this);setCentralWidget(viewer);// 生成示例点云QVector<QVector3D> points;QVector<QVector3D> colors;for (int i = 0; i < 100000; ++i) {points.append(QVector3D((float)rand()/RAND_MAX * 2 - 1,(float)rand()/RAND_MAX * 2 - 1,(float)rand()/RAND_MAX * 2 - 1));colors.append(QVector3D((float)rand()/RAND_MAX,(float)rand()/RAND_MAX,(float)rand()/RAND_MAX));}viewer->setPointCloud(points, colors);
}
四、注意事项
-
对于极大点云(>1百万点),考虑使用点云库如PCL进行预处理
-
确保OpenGL上下文版本足够高(至少3.3)
-
在渲染前检查点数量,避免空渲染
-
考虑添加相机控制和交互功能
-
对于专业应用,可能需要实现点云拾取、着色等功能