在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入门笔记】windows编译MinGW版本的osgearth(2.10.1)
qt·opengl·osg
扶尔魔ocy4 天前
【QT OpenGL】知识点编目及传送门
qt·opengl·osg
扶尔魔ocy17 天前
【qt openGL入门笔记】OSG实现飞机模型在网格图上飞行并进行态势标绘(四)
qt·opengl·osg
:mnong18 天前
Web 三维可视化技术对比:ParaView Web vs 本项目 wasm + Three.js
前端·webgl·opengl·opencl·科学可视化
扶尔魔ocy19 天前
【qt openGL入门笔记】OSG实现显示飞机模型飞行的点迹(二)
qt·opengl·osg
扶尔魔ocy19 天前
【qt openGL入门笔记】OSG实现飞机模型在网格图上飞行(三)
opengl·osg
扶尔魔ocy20 天前
【qt openGL入门笔记】OSG实现飞机模型在网格图上飞行
qt·opengl·osg
扶尔魔ocy24 天前
【qt openGL入门笔记】FPS相机
qt·opengl
扶尔魔ocy24 天前
qt openGL入门笔记】FPS相机和轨道相机
qt·opengl
扶尔魔ocy1 个月前
【qt openGL入门笔记】基础光照
qt·opengl