在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. 对于专业应用,可能需要实现点云拾取、着色等功能

相关推荐
扶尔魔ocy3 天前
【qt openGL入门笔记】FPS相机
qt·opengl
扶尔魔ocy3 天前
qt openGL入门笔记】FPS相机和轨道相机
qt·opengl
扶尔魔ocy5 天前
【qt openGL入门笔记】基础光照
qt·opengl
扶尔魔ocy5 天前
【qt openGL入门笔记】坐标系统和摄像机
qt·opengl
茉莉玫瑰花茶8 天前
OpenGL [ 纹理 ]
开发语言·c++·opengl
mmsx20 天前
Android 地图十万要素不卡顿:空间网格 + 渐进式加载的移动端实践
android·大数据·opengl
mmsx20 天前
Android 地图几万要素卡成幻灯片?顶点缓冲批上传与显存复用
android·opengl·地图·栅格
郝学胜-神的一滴1 个月前
C++11 工程级应用 10:减少拷贝,让容器跑得更快
服务器·开发语言·c++·游戏引擎·opengl
又吹风_Bassy1 个月前
WhatsCanvas教程-第九章:窗口呈现与交互
canvas·opengl·skia·2d渲染库·nanovg·whatscanvas·2d图形库
又吹风_Bassy1 个月前
WhatsCanvas教程-第三章:画笔 Paint 详解
canvas·opengl·skia·2d渲染库·nanovg·whatscanvas·2d图形库