Skip to content

Commit

Permalink
Merge pull request #1324 from MRPT/feature/assimp-better-transparent-…
Browse files Browse the repository at this point in the history
…render

OpenGL render engine: smarter z-ordering for transparent Assimp models
  • Loading branch information
jlblancoc authored Sep 22, 2024
2 parents fb7ec66 + 59a74a8 commit 485a2ea
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 38 deletions.
80 changes: 58 additions & 22 deletions apps/SceneViewer3D/_DSceneViewerMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ const long _DSceneViewerFrame::ID_BUTTON5 = wxNewId();
const long _DSceneViewerFrame::ID_STATICLINE2 = wxNewId();
const long _DSceneViewerFrame::ID_BUTTON6 = wxNewId();
const long _DSceneViewerFrame::ID_BUTTON7 = wxNewId();
const long _DSceneViewerFrame::ID_BUTTON_SHADOWS = wxNewId();
const long _DSceneViewerFrame::ID_BUTTON8 = wxNewId();
const long _DSceneViewerFrame::ID_BUTTON9 = wxNewId();
const long _DSceneViewerFrame::ID_STATICLINE3 = wxNewId();
Expand Down Expand Up @@ -480,6 +481,17 @@ _DSceneViewerFrame::_DSceneViewerFrame(wxWindow* parent, wxWindowID id) : maxv(0
wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_TICK_MARK")), wxART_TOOLBAR));
btnOrtho->SetMargins(wxSize(5, 5));
FlexGridSizer2->Add(btnOrtho, 1, wxEXPAND, 1);

btnShadows = new wxCustomButton(
this, ID_BUTTON_SHADOWS, _(" Shadows "),
wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_TICK_MARK")), wxART_TOOLBAR),
wxDefaultPosition, wxDefaultSize, wxCUSTBUT_TOGGLE | wxCUSTBUT_BOTTOM, wxDefaultValidator,
_T("ID_BUTTON_SHADOWS"));
btnShadows->SetBitmapDisabled(
wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_TICK_MARK")), wxART_TOOLBAR));
btnShadows->SetMargins(wxSize(5, 5));
FlexGridSizer2->Add(btnShadows, 1, wxEXPAND, 1);

btnAutoplay = new wxCustomButton(
this, ID_BUTTON8, _(" Autoplay "),
wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_REMOVABLE")), wxART_TOOLBAR),
Expand Down Expand Up @@ -657,6 +669,7 @@ _DSceneViewerFrame::_DSceneViewerFrame(wxWindow* parent, wxWindowID id) : maxv(0
Bind(wxEVT_BUTTON, &svf::OnReload, this, ID_BUTTON5);
Bind(wxEVT_BUTTON, &svf::OnMenuOptions, this, ID_BUTTON6);
Bind(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, &svf::OnbtnOrthoClicked, this, ID_BUTTON7);
Bind(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, &svf::OnbtnShadowsClicked, this, ID_BUTTON_SHADOWS);
Bind(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, &svf::OnbtnAutoplayClicked, this, ID_BUTTON8);
Bind(wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, &svf::OnBtnRecordClicked, this, ID_BUTTON9);
Bind(wxEVT_BUTTON, &svf::OnAbout, this, ID_BUTTON10);
Expand Down Expand Up @@ -891,34 +904,39 @@ void _DSceneViewerFrame::OntimLoadFileCmdLineTrigger(wxTimerEvent&)
{
timLoadFileCmdLine.Stop(); // One shot only.
// Open file if passed by the command line:
if (!global_fileToOpen.empty())
if (global_fileToOpen.empty()) return;

if (mrpt::system::strCmpI(
"3Dscene", mrpt::system::extractFileExtension(global_fileToOpen, true /*ignore .gz*/)))
{
if (mrpt::system::strCmpI(
"3Dscene", mrpt::system::extractFileExtension(global_fileToOpen, true /*ignore .gz*/)))
loadFromFile(global_fileToOpen);
}
else
{
std::cout << "Filename extension does not match `3Dscene`, "
"importing as an ASSIMP model...\n";
try
{
loadFromFile(global_fileToOpen);
auto obj3D = mrpt::opengl::CAssimpModel::Create();
obj3D->loadScene(global_fileToOpen);
// obj3D->setPose(mrpt::math::TPose3D(0, 0, 0, .0_deg,
// 0._deg, 90.0_deg));
m_canvas->getOpenGLSceneRef()->insert(obj3D);

// TODO: make optional?
obj3D->split_triangles_rendering_bbox(0.25);

m_canvas->Refresh();
}
else
catch (const std::exception& e)
{
std::cout << "Filename extension does not match `3Dscene`, "
"importing as an ASSIMP model...\n";
try
{
auto obj3D = mrpt::opengl::CAssimpModel::Create();
obj3D->loadScene(global_fileToOpen);
// obj3D->setPose(mrpt::math::TPose3D(0, 0, 0, .0_deg,
// 0._deg, 90.0_deg));
m_canvas->getOpenGLSceneRef()->insert(obj3D);

m_canvas->Refresh();
}
catch (const std::exception& e)
{
std::cerr << mrpt::exception_to_str(e) << std::endl;
wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this);
}
std::cerr << mrpt::exception_to_str(e) << std::endl;
wxMessageBox(mrpt::exception_to_str(e), _("Exception"), wxOK, this);
}
}

// Enable shadows?
applyShadowsOptions();
}

void _DSceneViewerFrame::OnbtnAutoplayClicked(wxCommandEvent& event)
Expand Down Expand Up @@ -1037,6 +1055,24 @@ void _DSceneViewerFrame::OnbtnOrthoClicked(wxCommandEvent& event)
m_canvas->Refresh(false);
}

void _DSceneViewerFrame::OnbtnShadowsClicked(wxCommandEvent& event)
{
applyShadowsOptions();
m_canvas->Refresh(false);
}

void _DSceneViewerFrame::applyShadowsOptions()
{
bool shadowsEnabled = btnShadows->GetValue();
auto scene = m_canvas->getOpenGLSceneRef();
if (!scene) return;

auto vw = scene->getViewport();
if (!vw) return;

vw->enableShadowCasting(shadowsEnabled);
}

void _DSceneViewerFrame::OnReload(wxCommandEvent& event)
{
if (fileExists(loadedFileName)) loadFromFile(loadedFileName);
Expand Down
5 changes: 5 additions & 0 deletions apps/SceneViewer3D/_DSceneViewerMain.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class _DSceneViewerFrame : public wxFrame
void OnClose(wxCloseEvent& event);
void OnBtnRecordClicked(wxCommandEvent& event);
void OnbtnOrthoClicked(wxCommandEvent& event);
void OnbtnShadowsClicked(wxCommandEvent& event);
void OnReload(wxCommandEvent& event);
void OnInsert3DS(wxCommandEvent& event);
void OnMenuSave(wxCommandEvent& event);
Expand Down Expand Up @@ -104,6 +105,8 @@ class _DSceneViewerFrame : public wxFrame

void OntimAutoplay(wxTimerEvent& event);

void applyShadowsOptions();

//(*Identifiers(_DSceneViewerFrame)
static const long ID_BUTTON1;
static const long ID_BUTTON2;
Expand All @@ -114,6 +117,7 @@ class _DSceneViewerFrame : public wxFrame
static const long ID_STATICLINE2;
static const long ID_BUTTON6;
static const long ID_BUTTON7;
static const long ID_BUTTON_SHADOWS;
static const long ID_BUTTON8;
static const long ID_BUTTON9;
static const long ID_STATICLINE3;
Expand Down Expand Up @@ -187,6 +191,7 @@ class _DSceneViewerFrame : public wxFrame
wxMenuItem* mnuSelectionScale;
wxMenuItem* mnuImportLAS;
wxCustomButton* btnOrtho;
wxCustomButton* btnShadows;
wxStatusBar* StatusBar1;
wxMenuItem* MenuItem6;
wxStaticLine* StaticLine3;
Expand Down
7 changes: 6 additions & 1 deletion doc/source/doxygen-docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
\page changelog Change Log

# Version 2.14.1: UNRELEASED
(None yet)
- Changes in apps:
- SceneViewer3D: New button to enable shadow casting.
- Changes in libraries:
- \ref mrpt_opengl_grp:
- New method mrpt::opengl::CAssimpModel::split_triangles_rendering_bbox() to enable a new feature in Assimp 3D models: splitting into smaller triangle sets for correct z-ordering of semitransparent objects; e.g. required for trees with masked leaves.
- SkyBox shader changed its internal ID so it gets rendered before potentially transparent elements, fixing render artifacts.

# Version 2.14.0: Released Sep 15th, 2024
- Changes in libraries:
Expand Down
11 changes: 11 additions & 0 deletions libs/opengl/include/mrpt/opengl/CAssimpModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,16 @@ class CAssimpModel :

mrpt::math::TBoundingBoxf internalBoundingBoxLocal() const override;

/** Enable (or disable if set to .0f) a feature in which textured triangles
* are split into different renderizable smaller objects.
* This is required only for semitransparent objects with overlaping regions.
*/
void split_triangles_rendering_bbox(const float bbox_size);
[[nodiscard]] float split_triangles_rendering_bbox() const
{
return m_split_triangles_rendering_bbox;
}

struct TInfoPerTexture
{
// indices in \a m_texturedObjects. string::npos for non-initialized
Expand Down Expand Up @@ -152,6 +162,7 @@ class CAssimpModel :
mutable std::vector<CSetOfTexturedTriangles::Ptr> m_texturedObjects;
bool m_verboseLoad = false;
bool m_ignoreMaterialColor = false;
float m_split_triangles_rendering_bbox = .0f;

void recursive_render(
const aiScene* sc,
Expand Down
18 changes: 9 additions & 9 deletions libs/opengl/include/mrpt/opengl/DefaultShaders.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,19 @@ struct DefaultShaderID
static constexpr shader_id_t POINTS = 0;
static constexpr shader_id_t WIREFRAME = 1;
static constexpr shader_id_t TEXT = 2;
static constexpr shader_id_t TRIANGLES_LIGHT = 3;
static constexpr shader_id_t TEXTURED_TRIANGLES_LIGHT = 4;
static constexpr shader_id_t TRIANGLES_NO_LIGHT = 5;
static constexpr shader_id_t TEXTURED_TRIANGLES_NO_LIGHT = 6;
static constexpr shader_id_t TRIANGLES_LIGHT = 10;
static constexpr shader_id_t TEXTURED_TRIANGLES_LIGHT = 11;
static constexpr shader_id_t TRIANGLES_NO_LIGHT = 12;
static constexpr shader_id_t TEXTURED_TRIANGLES_NO_LIGHT = 13;

// Shadow generation 1st/2nd pass shaders:
static constexpr shader_id_t TRIANGLES_SHADOW_1ST = 10;
static constexpr shader_id_t TRIANGLES_SHADOW_2ND = 11;
static constexpr shader_id_t TEXTURED_TRIANGLES_SHADOW_1ST = 12;
static constexpr shader_id_t TEXTURED_TRIANGLES_SHADOW_2ND = 13;
static constexpr shader_id_t TRIANGLES_SHADOW_1ST = 20;
static constexpr shader_id_t TRIANGLES_SHADOW_2ND = 21;
static constexpr shader_id_t TEXTURED_TRIANGLES_SHADOW_1ST = 22;
static constexpr shader_id_t TEXTURED_TRIANGLES_SHADOW_2ND = 23;

// Special effects:
static constexpr shader_id_t SKYBOX = 20;
static constexpr shader_id_t SKYBOX = 5; // render *before* potentially transparent triangles
static constexpr shader_id_t DEBUG_TEXTURE_TO_SCREEN = 30;
static constexpr shader_id_t NONE = 31; //!< Skip rendering
};
Expand Down
76 changes: 74 additions & 2 deletions libs/opengl/src/CAssimpModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#endif

#include <mrpt/core/lock_helper.h>
#include <mrpt/core/round.h>
#include <mrpt/opengl/opengl_api.h>
#include <mrpt/serialization/CArchive.h>
#include <mrpt/system/filesystem.h>
Expand Down Expand Up @@ -273,8 +274,65 @@ void CAssimpModel::onUpdateBuffers_all()

process_textures(m_assimp_scene->scene);

// Model -> 3D primitives:
const auto transf = mrpt::poses::CPose3D();
recursive_render(m_assimp_scene->scene, m_assimp_scene->scene->mRootNode, transf, re);

// Handle split:
if (m_split_triangles_rendering_bbox > .0f)
{
const float voxel = m_split_triangles_rendering_bbox;

ASSERT_EQUAL_(m_texturedObjects.size(), m_textureIdMap.size());
const std::vector<CSetOfTexturedTriangles::Ptr> origTexturedObjects = m_texturedObjects;
m_texturedObjects.clear();

std::unordered_map<int64_t, std::map<size_t /*textId*/, CSetOfTexturedTriangles::Ptr>>
newTextObjs;

for (size_t textId = 0; textId < m_textureIdMap.size(); textId++)
{
const auto& glTris = origTexturedObjects.at(textId);
const std::vector<mrpt::opengl::TTriangle>& iTris = glTris->shaderTexturedTrianglesBuffer();
for (const auto& t : iTris)
{
const auto midPt = 0.333f * (t.vertices[0].xyzrgba.pt + t.vertices[1].xyzrgba.pt +
t.vertices[2].xyzrgba.pt);
// hash for "voxels":
const int64_t vxlCoord = mrpt::round(midPt.x / voxel) + //
mrpt::round(midPt.y / voxel) * 10'000 + //
mrpt::round(midPt.z / voxel) * 100'000'000;

auto& trgObj = newTextObjs[vxlCoord][textId];
if (!trgObj)
{
// Create
trgObj = CSetOfTexturedTriangles::Create();

// Set representative point: the MOST FUNDAMENTAL property
// to ensure proper z ordering for transparent rendering:
trgObj->setLocalRepresentativePoint(midPt);

// copy texture
const auto& texRGB = glTris->getTextureImage();
const auto& texAlpha = glTris->getTextureAlphaImage();
if (!texAlpha.isEmpty())
trgObj->assignImage(texRGB, texAlpha);
else
trgObj->assignImage(texRGB);
}
// Add triangle:
trgObj->insertTriangle(t);
}
}

// replace list of objects:
m_texturedObjects.clear();
for (const auto& m : newTextObjs)
for (const auto& [k, v] : m.second) //
m_texturedObjects.push_back(v);
} // end: split into subobjects:

#endif
}

Expand All @@ -297,14 +355,15 @@ void CAssimpModel::enqueueForRenderRecursive(
mrpt::opengl::enqueueForRendering(lst, state, rq, wholeInView, is1stShadowMapPass);
}

uint8_t CAssimpModel::serializeGetVersion() const { return 2; }
uint8_t CAssimpModel::serializeGetVersion() const { return 3; }
void CAssimpModel::serializeTo(mrpt::serialization::CArchive& out) const
{
writeToStreamRender(out);
#if (MRPT_HAS_OPENGL_GLUT || MRPT_HAS_EGL) && MRPT_HAS_ASSIMP
const bool empty = m_assimp_scene->scene == nullptr;
out << empty;
out << m_modelPath; // v2
out << m_modelPath; // v2
out << m_split_triangles_rendering_bbox; // v3
if (!empty)
{
#if 0
Expand Down Expand Up @@ -348,6 +407,8 @@ void CAssimpModel::serializeFrom(mrpt::serialization::CArchive& in, uint8_t vers
{
const bool empty = in.ReadAs<bool>();
in >> m_modelPath;
if (version >= 3) in >> m_split_triangles_rendering_bbox;

if (!empty)
{
const auto blobSize = in.ReadAs<uint32_t>();
Expand Down Expand Up @@ -439,6 +500,7 @@ void CAssimpModel::after_load_model()
// Evaluate overall bbox:
{
aiVector3D scene_min, scene_max;
ASSERT_(m_assimp_scene->scene);
get_bounding_box(m_assimp_scene->scene, &scene_min, &scene_max);
m_bbox_min.x = scene_min.x;
m_bbox_min.y = scene_min.y;
Expand All @@ -460,6 +522,16 @@ auto CAssimpModel::internalBoundingBoxLocal() const -> mrpt::math::TBoundingBoxf
return mrpt::math::TBoundingBoxf::FromUnsortedPoints(m_bbox_min, m_bbox_max);
}

void CAssimpModel::split_triangles_rendering_bbox(const float bbox_size)
{
m_split_triangles_rendering_bbox = bbox_size;

#if (MRPT_HAS_OPENGL_GLUT || MRPT_HAS_EGL) && MRPT_HAS_ASSIMP
CRenderizable::notifyChange();
if (m_assimp_scene->scene) after_load_model();
#endif
}

bool CAssimpModel::traceRay(
[[maybe_unused]] const mrpt::poses::CPose3D& o, [[maybe_unused]] double& dist) const
{
Expand Down
8 changes: 4 additions & 4 deletions libs/ros2bridge/src/point_cloud2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ bool mrpt::ros2bridge::fromROS(const sensor_msgs::msg::PointCloud2& msg, CSimple
{
const unsigned char* msg_data = row_data + col * msg.point_step;

float x, y, z;
float x = 0, y = 0, z = 0;
get_float_from_field(x_field, msg_data, x);
get_float_from_field(y_field, msg_data, y);
get_float_from_field(z_field, msg_data, z);
Expand Down Expand Up @@ -165,7 +165,7 @@ bool mrpt::ros2bridge::fromROS(const sensor_msgs::msg::PointCloud2& msg, CPoints
{
const unsigned char* msg_data = row_data + col * msg.point_step;

float x, y, z, i;
float x = 0, y = 0, z = 0, i = 0;
get_float_from_field(x_field, msg_data, x);
get_float_from_field(y_field, msg_data, y);
get_float_from_field(z_field, msg_data, z);
Expand Down Expand Up @@ -212,15 +212,15 @@ bool mrpt::ros2bridge::fromROS(const sensor_msgs::msg::PointCloud2& msg, CPoints
{
const unsigned char* msg_data = row_data + col * msg.point_step;

float x, y, z;
float x = 0, y = 0, z = 0;
get_float_from_field(x_field, msg_data, x);
get_float_from_field(y_field, msg_data, y);
get_float_from_field(z_field, msg_data, z);
obj.setPointFast(idx, x, y, z);

if (i_field)
{
float i;
float i = 0;
get_float_from_field(i_field, msg_data, i);
obj.setPointIntensity(idx, i);
}
Expand Down

0 comments on commit 485a2ea

Please sign in to comment.