在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();
        
        // 将数据上传到GPU
        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();
        
        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();
    
    // 创建VBO
    m_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);
    // 移除下面这行或改为CompatibilityProfile
    format.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();

        // 将数据上传到GPU
        m_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);

    // 创建VAO
    m_vao.create();
    m_vao.bind();

    // 创建VBO
    m_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. 对于极大点云(>1百万点),考虑使用点云库如PCL进行预处理

  2. 确保OpenGL上下文版本足够高(至少3.3)

  3. 在渲染前检查点数量,避免空渲染

  4. 考虑添加相机控制和交互功能

  5. 对于专业应用,可能需要实现点云拾取、着色等功能

相关推荐
二进制人工智能1 天前
【OpenGL学习】(四)统一着色和插值着色
c++·opengl
byxdaz12 天前
Qt OpenGL 3D 编程入门
qt·opengl
byxdaz14 天前
Qt OpenGL 相机实现
opengl
二进制人工智能1 个月前
【OpenGL学习】(二)OpenGL渲染简单图形
c++·opengl
六bring个六1 个月前
qtcreater配置opencv
c++·qt·opencv·计算机视觉·图形渲染·opengl
爱看书的小沐1 个月前
【小沐学GIS】基于C++绘制二维瓦片地图2D Map(QT、OpenGL、GIS)
c++·qt·gis·opengl·glfw·glut·二维地图
六bring个六1 个月前
图形渲染+事件处理最终版
c++·qt·图形渲染·opengl
星火撩猿1 个月前
OpenGl实战笔记(3)基于qt5.15.2+mingw64+opengl实现光照变化效果
笔记·qt·opengl·光照效果
星火撩猿1 个月前
OpenGl实战笔记(2)基于qt5.15.2+mingw64+opengl实现纹理贴图
笔记·qt·opengl·纹理贴图