效果图
概述
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;
}
着色器
着色器是现代图形编程的基础,它们提供了对渲染过程的精细控制,使得开发者能够创建更加丰富和高效的视觉效果
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" ;
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 ( ) ;
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 ;
}