如何使用OpenGL画出ROS rviz那样的点云可视化效果

【请尊重原创!转载和引用文章内容务必注明出处!未经许可上传到某文库或其他收费阅读/下载网站赚钱的必追究责任!】

ROS rviz可以将点云以多种形式渲染出来比较漂亮,尤其是根据intensity渲染点云不同的色彩和亮度的功能比较好, 比PCL和mayavi等提供的可视化API画的点云效果好看多了,缺点是你想在既有rviz工具里对点云效果增加功能处理或渲染效果的话不得不去读rviz很多的代码后去做修改,如果一知半解就会改出问题来,如果自己实现用OpenGL画点云的代码,想怎么改都行,但是很难找得到有资料介绍rviz里点云的效果(主要是比较好看的rainbow)到底是怎么画出来的,于是花了些空余时间先琢磨用集成到Qt里的OpenGL简单的用一种颜色画出点云(这时看到的是类似PCL或mayavi画点云的效果,比较单一呆板,无法表现出和intensity有关的颜色和亮度的不同),然后读rviz的源码找到了画rainbow效果的实现代码移植到点云的点着色程序中,解决掉在移植过程中出现的不符合GLSL语言语法规则的错误一些错误后(例如GLSL对数据类型转换有较C++更强的要求),画出来的效果和rviz一样了。

这里列出一些主要的关键代码,下面是点云数据准备和渲染的主要程序:

class opengl_widget : public QOpenGLWidget, QOpenGLFunctions_4_5_Core
{
	Q_OBJECT

    public:
	   opengl_widget(QWidget* parent);
	   ~opengl_widget();

    protected:
	   void initializeGL();
	   void paintGL();
	   void resizeGL(int width, int height);
    
    private:
       QOpenGLShaderProgram glsp_point;
       unsigned int pointVBO;
       unsigned int pointVAO;
       unsigned int vertexCount;
       unsigned int pointCount;
       std::vector<float> pointData;
       float max_intensity;
       float min_intensity;

    ...

...
   unsigned int opengl_widget::preparePoints(std::vector<float>& pointData)
   {
      glGenVertexArrays(1, &pointVAO);
      glBindVertexArray(pointVAO);
      glGenBuffers(1, &pointVBO);
      glBindBuffer(GL_ARRAY_BUFFER, pointVBO);
      glBufferData(GL_ARRAY_BUFFER, pointData.size() * sizeof(float), &pointData[0], GL_STATIC_DRAW);
      glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
      glEnableVertexAttribArray(0);
      glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(3 * sizeof(float)));
      glEnableVertexAttribArray(1);
      glBindBuffer(GL_ARRAY_BUFFER, 0);
      glBindVertexArray(0);
      return pointData.size() / 4;
   }
   opengl_widget::opengl_widget(QWidget* parent)
    : QOpenGLWidget(parent)
   {
      ...
      pointData = read_bag(bag_path, max_intensity, min_intensity);
   }
   void opengl_widget::initializeGL()
   {
      initializeOpenGLFunctions();
      ...
      glsp_point.addShaderFromSourceFile(QOpenGLShader::Fragment, "glsp_point.fs");
      glsp_point.addShaderFromSourceFile(QOpenGLShader::Vertex, "glsp_point.vs");
      glsp_point.link();
      pointCount = preparePoints(pointData);
   }
   void opengl_widget::paintGL()
   {
      ...
      glsp_point.bind();
      ...
      glsp_point.setUniformValue("max_intensity", max_intensity);
      glsp_point.setUniformValue("min_intensity", min_intensity);
      glBindVertexArray(pointVAO);
      glPointSize(2.0f);
      glDrawArrays(GL_POINTS, 0, pointCount);
      ...
   }

下面分别是点云vertex和fragment着色程序,是画出rainbow效果的关键:

#version 450 core

layout (location = 0) in vec3 pos;
layout (location = 1) in float intensity;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform float max_intensity;
uniform float min_intensity;

out vec3 vtxColor;

vec3 getRainbowColor(float value)
{
  // this is HSV color palette with hue values going only from 0.0 to 0.833333.
  vec3 color = vec3(0.0f,0.0f,0.0f);
  value = min(value, 1.0f);
  value = max(value, 0.0f);

  float h = value * 5.0f + 1.0f;
  int i = int(floor(h));
  float f = h - i;
  if (!bool(i & 1))
    f = 1 - f; // if i is even
  float n = 1.0f - f;

  if (i <= 1)
    color[0] = n, color[1] = 0, color[2] = 1;
  else if (i == 2)
    color[0] = 0, color[1] = n, color[2] = 1;
  else if (i == 3)
    color[0] = 0, color[1] = 1, color[2] = n;
  else if (i == 4)
    color[0] = n, color[1] = 1, color[2] = 0;
  else if (i >= 5)
    color[0] = 1, color[1] = n, color[2] = 0;
  return color;
}

void main()
{
    gl_Postion = projection * view * model * vec4(pos, 1.0);
    float diff_intensity = max_intensity - min_intensity;
    float normalized_intensity = (intensity - min_intensity) / diff_intensity;
    float value = 1.0 - normalized_intensity;  // use rainbow
    vtxColor = getRainbowColor(value);
}

上面的根据点云的intensity值计算rainbow色彩的计算算法是从rviz的代码修改来的,作归一化用的max_intensity和min_intensity是当前帧点云的最大/最小强度值,是通过uniform变量传入的。每个点云对应的vertex的xyz和intensity值的传入格式是在preparePoints()里定义的。

#version 450 core

out vec4 fragColor;

in vec3 vtxColor;

void main()
{
    fragColor = vec4(vtxColor, 1.0f);
}

画出来的效果是这样:

效果和rviz里一样了,在增加些针对鼠标事件触发的对点云图的缩放和翻转之类的控制,实现

复制代码
opengl_widget::mouseMoveEvent()、opengl_widget::mousePressEvent()、opengl_widget::mouseReleaseEvent()、opengl_widget::wheelEvent()等事件函数,得到的最终效果就和rviz里的效果一样了,而且自己的代码实现可以很方便进一步增加更复杂的处理功能或效果展示。
相关推荐
闲暇部落4 小时前
Android OpenGL ES详解——几何着色器
opengl·法线·法向量·几何着色器
Code-world-14 天前
Ubuntu 的 ROS 操作系统安装与测试
linux·ubuntu·ros·noetic ninjemy
刘好念5 天前
[OpenGL]使用OpenGL实现硬阴影效果
c++·计算机图形学·opengl
闲暇部落6 天前
Android OpenGL ES详解——纹理:纹理过滤GL_NEAREST和GL_LINEAR的区别
opengl·texture·linear·纹理过滤·nearest·邻近过滤·线性过滤
饮啦冰美式7 天前
ROS2humble版本使用colcon构建包
ros
凌云行者7 天前
OpenGL入门005——使用Shader类管理着色器
c++·cmake·opengl
凌云行者7 天前
OpenGL入门006——着色器在纹理混合中的应用
c++·cmake·opengl
knighthood20017 天前
解决:ros进行gazebo仿真,rviz没有显示传感器数据
c++·ubuntu·ros
knighthood20019 天前
ros中仿真编写launch时robot_state_publisher,output参数
c++·ubuntu·ros
凌云行者10 天前
OpenGL入门004——使用EBO绘制矩形
c++·cmake·opengl