LiangliangNan / Easy3D

A lightweight, easy-to-use, and efficient C++ library for processing and rendering 3D data
GNU General Public License v3.0
1.37k stars 245 forks source link

Rendering issue when rotating camera with small point values #95

Closed laxnpander closed 3 years ago

laxnpander commented 3 years ago

Hey hey,

I am observing an issue where my point cloud disappears when I rotate the camera. You can see it here:

https://drive.google.com/file/d/1SoI5SyAFgEVBdmUBhTXE7bAtJU1xQpvk/view?usp=sharing

My point coordinates are all very small. Somewhere between -2.0 and 2.0. I could imagine this might cause this issue? Some floating point error thing maybe? Do you have any suggestions how to fix it? I tried multiplying all coordinates with a fixed scaling factor, but it only got worse.

Thanks for your suggestions and best regards,

Alex

LiangliangNan commented 3 years ago

Do you have multiple point clouds or 3D models loaded into the same viewer? In the case of multiple models, it might be that the viewing frustum is defined by another 3D model. If you have a single point cloud, can you load it using one of the examples in Easy3D and see if it has the same issue. If so, it would be good to send me your point cloud and let me know which example demonstrates the issue.

laxnpander commented 3 years ago

It should be only one point cloud, but it is empty at start and then updated in real-time. Maybe this causes the issue? I saved the point cloud to binary, though when I load it into Tutorial_201 it looks fine:

https://drive.google.com/file/d/110vPx1T_s_0Qogr7Ixjf_1QS1H45sA13/view?usp=sharing

Here is the relevant code section:

Easy3d::Easy3d()
 : m_is_running(false),
   m_cloud(new easy3d::PointCloud),
   m_viewer(new easy3d::Viewer("Test")),
   m_draw_cloud(nullptr)
{
  easy3d::logging::initialize(true, true, true, "default", 0);

  m_viewer->add_model(m_cloud);

  m_draw_cloud = m_cloud->renderer()->get_points_drawable("vertices");
  m_draw_cloud->set_impostor_type(easy3d::PointsDrawable::PLAIN); // draw points as spheres.
  m_draw_cloud->set_point_size(4.0f);    // set point size
  m_draw_cloud->set_visible(true);
}

void Easy3d::updateLandmarks(const std::vector<Landmark::WPtr> &landmarks)
{
  for (const auto& it : landmarks)
  {
    auto lm = it.lock();
    if (lm)
      m_cloud->add_vertex(easy3d::vec3((float)lm->x(), (float)lm->y(), (float)lm->z()));
  }

  auto vertices = m_cloud->get_vertex_property<easy3d::vec3>("v:point");
  m_draw_cloud->update_vertex_buffer(vertices.vector());
  m_viewer->update();
}
LiangliangNan commented 3 years ago

Indeed. The bounding box of the object was used to ensure that the entire model is visible in the view frustum (and Easy3D also uses this to maximize the accuracy of the Z-buffer). Since your model is dynamic, the bounding box of the object also changes. To make it work for dynamic scenes, you need to update the scene radius by calling camera()->setSceneRadius(...) before m_viewer->update().

In your case, the following

    m_viewer->camera()->setSceneRadius( 
           std::max(
                           distance(m_viewer->camera()->sceneCenter(), box.min_point()),
                           distance(m_viewer->camera()->sceneCenter(), box.max_point())
                          )
            );

where box is the updated bounding box of the model.

To avoid computation overhead, you can have a member variable maintaining the bounding box of the model, e.g., Box3 bbox_;. So every time when a new point is added, the new bounding can be updated by simply bbox_.add_point(p) .

BTW (not related to this issue, but I think it is better to mention it), Easy3D is not optimized for dynamic objects: calling m_draw_cloud->update_vertex_buffer(vertices.vector()); will send ALL points from CPU to GPU. This is completely fine for small point clouds (e.g., < 1M) and the updating of the rendering can be done in real-time. But, if you have huge point clouds (e.g., hundreds of millions of points or even more, very common for reconstructing large urban scenes), transferring ALL data to GPU might take some noticeable time. Thus updating vertex buffer every time when a single point is added to the model may become a bottleneck for applications that require real-time performance). In this case, you can create separate point clouds to store the new points (e.g., every point cloud stores 100k points). This way, only a small number of points are sent to GPU when a point is added. When you accumulate a certain number (e.g., every 10 or 50) of point clouds, you merge them into a single one.

laxnpander commented 3 years ago

@LiangliangNan Thanks, that did it! It's perfectly fine that it does not perform that well in dynamic scenes. I use it mostly for debugging purposes. I need quick visualizations for small to medium sized point clouds without having to write 100 lines of code. And easy3d does that outstandingly well!