QT 使用OpenGL显示并查看点云图
效果图
概述
OpenglWidget
继承自QOpenGLWidget
,QOpenGLFunctions
,它具有OpenGL的功能,并且可以绘制OpenGL图形MinimumBoundBox
类用于计算点云的最小包围盒(轴对齐包围盒,AABB),可以帮助确定视图的缩放级别,或者在用户进行平移和旋转操作时保持点云在视图内- 这两个类结合起来,前者提供了渲染和交互的框架,后者用于理解和限定点云的空间范围
功能点
- 不依赖其他库和插件
- 计算点云的最小包围盒
- 可旋转(左键),平移(右键),复位(空格)查看点云
- 根据Z轴深度进行伪颜色处理
代码分析
读取点云文件
- 从文件如pcd文件中获取点云数据,一般来说点云文件是如下格式,但是若是文件有rgb颜色信息,那么后续也不需要使用z轴深度去进行伪颜色处理,这里就使用
QVector4D
保存好颜色信息,传递给后续使用。
void PointCloudPage::onStart()
{
std::vector<QVector3D> cloud;
QString qfile = "文件路径";
cloud = ReadVec3PointCloudASC(qfile);
m_openglWidget->showPointCloud(cloud);
}
std::vector<QVector3D> PointCloudPage::ReadVec3PointCloudASC(QString path)
{
std::vector<QVector3D> cloud;
QFile file(path);
if (!file.open(QFile::ReadOnly | QIODevice::Text))
{
qDebug() << "There is no asc file";
return cloud;
}
QTextStream in(&file);
QString ramData = in.readAll();
QStringList list = ramData.split("\n");
QStringList listline;
cloud.resize(list.count() - 1);
for (int i = 0; i < list.count() - 1; i++)
{
listline = list.at(i).split(" ");
if (listline.size() >= 3)
{
cloud[i].setX((listline.at(0).toFloat()));
cloud[i].setY((listline.at(1).toFloat()));
cloud[i].setZ((listline.at(2).toFloat()));
}
}
return cloud;
}
着色器
- 着色器是现代图形编程的基础,它们提供了对渲染过程的精细控制,使得开发者能够创建更加丰富和高效的视觉效果
/// @brief 顶点着色器
static const char *vertexShaderSource =
"attribute highp vec3 posAttr;\n"
"attribute lowp vec4 colAttr;\n"
"varying lowp vec4 col;\n"
"uniform highp mat4 matrix;\n"
"void main() {\n"
" col=colAttr;\n"
" gl_Position=matrix * vec4(posAttr,1.0f);\n"
"}\n";
/// @brief 片段着色器
static const char *fragmentShaderSource =
"varying lowp vec4 col;\n"
"void main() {\n"
" gl_FragColor = col;\n"
"}\n";
bool OpenglWidget::InitShader()
{
bool success = true;
success &= m_Program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
success &= m_Program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
success &= m_Program->link();
GetShaderUniformPara();
return success;
}
图形绘制
makeCurrent
进行任何OpenGL操作之前必须调用的,glDrawElements
绘制坐标轴,在计算正交投影矩阵时考虑当前窗口的宽高比,防止点云图跟随窗口大小而产生形变。
void OpenglWidget::paintGL()
{
makeCurrent();
m_Program->bind();
glClearColor(m_backgroundColor.x(), m_backgroundColor.y(), m_backgroundColor.z(), m_backgroundColor.w());
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BITS);
m_VAO->bind();
setMatrixUniform();
glDrawArrays(GL_POINTS, 6, (GLsizei)m_PointsVertex.size() - 6);
if (m_bShowAxis)
{
glDrawElements(GL_LINES, 6, GL_UNSIGNED_INT, 0);
}
m_VAO->release();
m_Program->release();
}
void OpenglWidget::setMatrixUniform()
{
QMatrix4x4 matrix = QMatrix4x4();
QMatrix4x4 matrixPerspect = QMatrix4x4();
QMatrix4x4 matrixView = QMatrix4x4();
QMatrix4x4 matrixModel = QMatrix4x4();
QVector3D minPos = (m_box.getMinPoint() - m_box.getCenterPoint());
QVector3D maxPos = (m_box.getMaxPoint() - m_box.getCenterPoint());
float maxAxis;
maxAxis = qAbs(qMax(qMax(m_box.depth(), m_box.width()), m_box.height()));
float aspectRatio = width() * 1.0f / height();
// TODO 计算正交投影矩阵时考虑当前窗口的宽高比
matrixPerspect.ortho(-aspectRatio * maxAxis, aspectRatio * maxAxis, -maxAxis, maxAxis, -2 * maxAxis, 2 * maxAxis);
matrixView.lookAt(QVector3D(0, 0, maxAxis), QVector3D(0.0, 0.0, -1), QVector3D(0.0, 1.0, 0.0));
matrixView.translate(m_lineMove.x(), m_lineMove.y(), m_lineMove.z());
matrixModel.rotate(m_rotate);
matrixModel.scale(m_scale);
matrix = matrixPerspect * matrixView * matrixModel;
m_Program->setUniformValue(m_matrixUniform, matrix);
}
图形变换
- 通过各种事件控制,旋转(左键),平移(右键),复位(空格)
void OpenglWidget::mousePressEvent(QMouseEvent *e)
{
if (e->buttons() & Qt::LeftButton || e->buttons() & Qt::MidButton)
{
setMouseTracking(true);
m_lastPoint = QVector2D(e->localPos());
// 开始拖拽时显示坐标系
m_bShowAxis = true;
repaint();
}
if (e->button() == Qt::RightButton)
{
// 记录右键按下的初始位置
m_rightButtonInitialPos = QVector2D(e->localPos());
}
}
void OpenglWidget::mouseMoveEvent(QMouseEvent *e)
{
if (e->buttons() & Qt::LeftButton)
{
Rotate(QVector2D(m_lastPoint), QVector2D(e->localPos()));
}
if (e->buttons() & Qt::RightButton)
{
// 使用右键按下的初始位置和当前位置来计算移动
LineMove(m_rightButtonInitialPos, QVector2D(e->localPos()));
// 更新初始位置为当前位置,以便下一次移动计算
m_rightButtonInitialPos = QVector2D(e->localPos());
}
m_lastPoint = QVector2D(e->localPos());
repaint();
}
void OpenglWidget::mouseReleaseEvent(QMouseEvent *e)
{
setMouseTracking(false);
// 松开鼠标时隐藏坐标系
m_bShowAxis = false;
repaint();
}
void OpenglWidget::wheelEvent(QWheelEvent *e)
{
if (e->delta() > 0)
{
modelZoomInOrOut(true);
}
else
{
modelZoomInOrOut(false);
}
}
void OpenglWidget::keyPressEvent(QKeyEvent *e)
{
if (e->key() == Qt::Key_Space)
{
ResetView();
}
QWidget::keyPressEvent(e);
}
最小包围盒
- 这里使用的AABB轴对齐包围盒,这是最简单的一种包围盒,,计算速度快,但可能不是最紧凑的。初始化时
calculateMinBoundingBox
函数接收点云数据,并计算出这些点构成的最小包围盒。
bool MinimumBoundBox::calculateMinBoundingBox(const std::vector<QVector3D> &cloud)
{
zerolize();
int size = (int)cloud.size();
if (size == 0)
{
return false;
}
else if (size == 1)
{
firstPoint(cloud[0]);
return false;
}
else
{
bool bfirst = false;
for (int i = 0; i < size; i++)
{
if (!bfirst)
{
if (isValid(cloud[i]))
{
firstPoint(cloud[i]);
bfirst = true;
}
}
else
{
nextPoint(cloud[i]);
}
}
m_center = QVector3D(midX(), midY(), midZ());
}
return true;
}
伪颜色
- 一般来说点云是无色彩的,为了更加直观的展示,我们可以使用根据z轴深度进行颜色处理。
void OpenglWidget::gray2Pseudocolor(const QVector3D pos, float color[4])
{
float fmin = m_box.getMinPoint().z();
float fmax = m_box.getMaxPoint().z();
int colortemp = (int)(((fmax - pos.z()) / (fmax - fmin)) * 255);
int r, g, b;
if (colortemp >= 0 && colortemp < 64)
{
r = 0;
g = 254 - 4 * colortemp;
b = 255;
}
else if (colortemp >= 64 && colortemp < 128)
{
r = 0;
g = 4 * colortemp - 254;
b = 510 - 4 * colortemp;
}
else if (colortemp >= 128 && colortemp < 192)
{
r = 4 * colortemp - 510;
g = 255;
b = 0;
}
else if (colortemp >= 192 && colortemp <= 255)
{
r = 255;
g = 1022 - 4 * colortemp;
b = 0;
}
else
{
r = 255;
g = 255;
b = 255;
}
color[0] = r * 1.0f / 255;
color[1] = g * 1.0f / 255;
color[2] = b * 1.0f / 255;
color[3] = 1.0f;
}
原文地址:https://blog.csdn.net/weixin_49065061/article/details/145222257
免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!