Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the GLTF writer to support writing textured models #6101

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a09c8bc
Fix for Issue #5924:
dbs4261 Feb 20, 2023
01fdc2e
Updated CHANGELOG.md with info about fixing Issue #5924
dbs4261 Feb 20, 2023
6e156c1
Updates to work with changes to the translation of tensor mesh materials
dbs4261 Apr 13, 2023
1ffe35b
Added code to break a TriangleMesh into one mesh per material in a tr…
dbs4261 Feb 21, 2023
c763d91
Added tcb::span dependency to support std::span behaivor until c++20 …
dbs4261 Feb 23, 2023
6c06ce6
Add Model.cpp to the build this has code to separate a TriangleMesh b…
dbs4261 Feb 28, 2023
339fde8
Updated TinyGLTF version
dbs4261 Feb 28, 2023
23d5f6d
Updated FileGLTF and ModelIO to export a TriangleMeshModel to GLTF in…
dbs4261 Feb 28, 2023
55e7ed4
Added a test program that should be removed before merging.
dbs4261 Feb 28, 2023
e65cb0e
Fix to export textures in a GLB file
dbs4261 Mar 1, 2023
104b77c
Added extension for unlit materials to GLTF exporter
dbs4261 Apr 12, 2023
465a073
Renamed functions to match with existing naming scheme. Added docstri…
dbs4261 Apr 20, 2023
c1a9de6
Corrected a copy vs reference error
dbs4261 Apr 20, 2023
3a036bd
Added support for the KHR_materials_unlit extension in the GLTF Model…
dbs4261 Apr 20, 2023
dffeff1
Added python bindings to creating a TriangleMeshModel from a Triangle…
dbs4261 Apr 20, 2023
9ef2a54
Formatter changes
dbs4261 Apr 21, 2023
58708c3
Fixed bug where iterators weren't dereferenced when checking if more …
dbs4261 Apr 26, 2023
83f693a
Fixed bug in small meshes where most accessors need to be aligned to …
dbs4261 Apr 29, 2023
0d12154
Fixed bug in material conversion that would segfault when no texture …
dbs4261 Apr 29, 2023
65ecead
FINALLY fixed issue with per-vertex UV conversion. It was not checkin…
dbs4261 Apr 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions 3rdparty/find_dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -1889,6 +1889,14 @@ else()
set(BUILD_WEBRTC_COMMENT "//")
endif()

# tcb::span
include(${Open3D_3RDPARTY_DIR}/tcbspan/tcbspan.cmake)
open3d_import_3rdparty_library(3rdparty_tcbspan
INCLUDE_DIRS ${TCB_SPAN_INCLUDE_DIRS}
LIBRARIES ${TCB_SPAN_LIBRARIES}
)
list(APPEND Open3D_3RDPARTY_PRIVATE_TARGETS_FROM_CUSTOM Open3D::3rdparty_tcbspan)

# Compactify list of external modules.
# This must be called after all dependencies are processed.
list(REMOVE_DUPLICATES Open3D_3RDPARTY_EXTERNAL_MODULES)
Expand Down
28 changes: 28 additions & 0 deletions 3rdparty/tcbspan/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
cmake_minimum_required(VERSION 3.8)
project(span LANGUAGES CXX)

option(BUILD_RAYCAST_APP "If the raycast test app should be built" OFF)

include(GNUInstallDirs)

add_library(span INTERFACE)
add_library(tcb::span ALIAS span)
target_include_directories(span INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/tcb/>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/>
)

install(TARGETS span EXPORT tcbspan-targets)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/tcb/span.hpp
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/tcbspan/
)

install(EXPORT tcbspan-targets
FILE tcbspan-targets.cmake
NAMESPACE tcb::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/tcbspan/
)

install(FILES LICENSE_1_0.txt
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/tcbspan/
)
24 changes: 24 additions & 0 deletions 3rdparty/tcbspan/tcbspan.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
include(ExternalProject)

set(TCB_SPAN_LIB_NAME tcbspan)

ExternalProject_Add(
ext_tcbspan
PREFIX tcbspan
GIT_REPOSITORY https://github.com/tcbrindle/span.git
GIT_TAG 836dc6a0efd9849cb194e88e4aa2387436bb079b
GIT_SHALLOW TRUE
DOWNLOAD_DIR "${OPEN3D_THIRD_PARTY_DOWNLOAD_DIR}/tcbspan"
UPDATE_COMMAND ""
PATCH_COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/tcbspan/CMakeLists.txt <SOURCE_DIR>
CMAKE_ARGS
${ExternalProject_CMAKE_ARGS_hidden}
-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
)
ExternalProject_Get_Property(ext_tcbspan DOWNLOAD_DIR)
message(WARNING "TCBSpan source dir ${DOWNLOAD_DIR}")

ExternalProject_Get_Property(ext_tcbspan INSTALL_DIR)
set(TCB_SPAN_INCLUDE_DIRS ${INSTALL_DIR}/include/) # "/" is critical.
set(STDGPU_LIBRARIES tcb::span)
4 changes: 2 additions & 2 deletions 3rdparty/tinygltf/tinygltf.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ include(ExternalProject)
ExternalProject_Add(
ext_tinygltf
PREFIX tinygltf
URL https://github.com/syoyo/tinygltf/archive/72f4a55edd54742bca1a71ade8ac70afca1d3f07.tar.gz
URL_HASH SHA256=9e848dcf0ec7dcb352ced782aea32064a63a51b3c68ed14c68531e08632a2d90
URL https://github.com/syoyo/tinygltf/archive/refs/tags/v2.8.3.tar.gz
URL_HASH SHA256=fbef83ef47dbc6d1662103b54ea54f05a753ddff7a11d80b9fe0cd306ab5d4d2
DOWNLOAD_DIR "${OPEN3D_THIRD_PARTY_DOWNLOAD_DIR}/tinygltf"
UPDATE_COMMAND ""
CONFIGURE_COMMAND ""
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* Fix raycasting scene: Allow setting of number of threads that are used for building a raycasting scene
* Fix Python bindings for CUDA device synchronization, voxel grid saving (PR #5425)
* Support msgpack versions without cmake
* Changed TriangleMesh to store materials in a list so they can be accessed by the material index (PR #5938)

## 0.13

Expand Down
2 changes: 1 addition & 1 deletion cpp/open3d/geometry/TriangleMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -853,7 +853,7 @@ class TriangleMesh : public MeshBase {
std::unordered_map<std::string, Image> additionalMaps;
};

std::unordered_map<std::string, Material> materials_;
std::vector<std::pair<std::string, Material>> materials_;

/// List of material ids.
std::vector<int> triangle_material_ids_;
Expand Down
128 changes: 128 additions & 0 deletions cpp/open3d/io/ModelIO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,102 @@

#include <unordered_map>

#include "open3d/geometry/TriangleMesh.h"
#include "open3d/utility/FileSystem.h"
#include "open3d/utility/Logging.h"
#include "open3d/utility/ProgressBar.h"
#include "open3d/visualization/rendering/Model.h"

namespace open3d {
namespace io {
namespace detail {

bool HasPerVertexUVs(const geometry::TriangleMesh& mesh) {
std::vector<Eigen::Vector2d> vertex_uvs(mesh.vertices_.size(),
Eigen::Vector2d(-1, -1));
for (std::size_t tidx = 0; tidx < mesh.triangles_.size(); ++tidx) {
const auto& triangle = mesh.triangles_[tidx];
for (int i = 0; i < 3; ++i) {
const auto& tri_uv = mesh.triangle_uvs_[3 * tidx + i];
if (vertex_uvs[triangle(i)] == Eigen::Vector2d(-1, -1)) {
vertex_uvs[triangle(i)] = tri_uv;
continue;
}
if (vertex_uvs[triangle(i)] != tri_uv) {
return false;
}
}
}
return true;
}

std::pair<geometry::TriangleMesh, std::vector<Eigen::Vector2d>>
MeshWithPerVertexUVs(const geometry::TriangleMesh& mesh) {
if (!mesh.HasTriangleUvs()) {
return {mesh, {}};
}
if (mesh.HasAdjacencyList()) {
utility::LogWarning(
"[MeshWithPerVertexUVs] This mesh contains "
"an adjacency list that are not handled in this function");
}
geometry::TriangleMesh out = mesh;

std::unordered_map<int, std::vector<int>> vertex_remap;
std::vector<Eigen::Vector2d> vertex_uvs;
const Eigen::Vector2d InvalidUV(-1, -1);
vertex_uvs.resize(out.vertices_.size(), InvalidUV);

// Code to remap a vertex
auto remap_vert = [&mesh, &out, &vertex_uvs,
&vertex_remap](std::size_t tidx, int i){
Eigen::Vector3i& triangle = out.triangles_[tidx];
vertex_uvs.emplace_back(out.triangle_uvs_[3 * tidx + i]);
out.vertices_.emplace_back(out.vertices_[triangle(i)]);
if (mesh.HasVertexColors()) {
out.vertex_colors_.emplace_back(out.vertex_colors_[triangle(i)]);
}
if (mesh.HasVertexNormals()) {
out.vertex_normals_.emplace_back(out.vertex_normals_[triangle(i)]);
}
vertex_remap[triangle(i)].emplace_back(out.vertices_.size() - 1);
triangle(i) = static_cast<int>(out.vertices_.size() - 1);
assert(out.triangles_[tidx](i) == int(out.vertices_.size() - 1));
};

for (std::size_t tidx = 0; tidx < out.triangles_.size(); ++tidx) {
Eigen::Vector3i& triangle = out.triangles_[tidx];
for (int i = 0; i < 3; ++i) {
if (vertex_uvs[triangle(i)] == InvalidUV) {
vertex_uvs[triangle(i)] = out.triangle_uvs_[3 * tidx + i];
} else if (vertex_uvs[triangle(i)] !=
out.triangle_uvs_[3 * tidx + i]) {
if (vertex_remap.count(triangle(i)) > 0) {
for (int remap_vidx : vertex_remap[triangle(i)]) {
if (vertex_uvs[remap_vidx] ==
out.triangle_uvs_[3 * tidx + i]) {
triangle(i) = remap_vidx;
break;
}
remap_vert(tidx, i);
}
} else {
remap_vert(tidx, i);
}
}
}
}
assert(out.vertices_.size() == vertex_uvs.size());
for (std::size_t tidx = 0; tidx < out.triangles_.size(); ++tidx) {
for (int i = 0; i < 3; ++i) {
assert(out.triangle_uvs_[3 * tidx + i] ==
vertex_uvs[out.triangles_[tidx](i)]);
}
}
return {out, vertex_uvs};
}

} // namespace detail

bool ReadModelUsingAssimp(const std::string& filename,
visualization::rendering::TriangleMeshModel& model,
Expand All @@ -34,5 +124,43 @@ bool ReadTriangleModel(const std::string& filename,
return ReadModelUsingAssimp(filename, model, params);
}

bool WriteTriangleModel(
const std::string& filename,
const visualization::rendering::TriangleMeshModel& model) {
const std::string ext =
utility::filesystem::GetFileExtensionInLowerCase(filename);
// Validate model for output
for (const auto& mesh_info : model.meshes_) {
// if (!HasPerVertexUVs(*mesh_info.mesh)) {
// utility::LogWarning(
// "Cannot export model because mesh {} needs "
// "to be converted to have per-vertex uvs instead "
// "of per-triangle uvs",
// mesh_info.mesh_name);
// return false;
// }
auto mat_it = std::minmax_element(
mesh_info.mesh->triangle_material_ids_.begin(),
mesh_info.mesh->triangle_material_ids_.end());
if (*mat_it.first != *mat_it.second) {
utility::LogWarning(
"Cannot export model because mesh {} has more "
"than one material",
mesh_info.mesh_name);
return false;
}
}

if (ext == "gltf" || ext == "glb") {
return WriteTriangleModelToGLTF(filename, model);
} else {
utility::LogWarning(
"Unsupported file format {}. "
"Currently only gltf and glb are supported",
ext);
return false;
}
}

} // namespace io
} // namespace open3d
31 changes: 31 additions & 0 deletions cpp/open3d/io/ModelIO.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@

#pragma once

#include <Eigen/Core>
#include <functional>
#include <string>

namespace open3d {
namespace geometry {
class TriangleMesh;
} // namespace geometry
namespace visualization {
namespace rendering {
struct TriangleMeshModel;
Expand All @@ -19,6 +23,24 @@ struct TriangleMeshModel;

namespace io {

namespace detail {

/**
* Creates a mesh with a texture coordinate per vertex for IO
* @note TriangleMesh's with adjacency lists are not supported by this function
* and the output mesh will not have any adjacency information transferred
* @param mesh The mesh with texture coordinaets to be converted
* @return A pair containing a new mesh, and a vector texture coordinates
* of the same length as the vector of vertices. The per face texture
* in the returned mesh have also been updated.
*/
std::pair<geometry::TriangleMesh, std::vector<Eigen::Vector2d>>
MeshWithPerVertexUVs(const geometry::TriangleMesh& mesh);

bool HasPerVertexUVs(const geometry::TriangleMesh& mesh);

} // namespace detail

struct ReadTriangleModelOptions {
/// Print progress to stdout about loading progress.
/// Also see \p update_progress if you want to have your own progress
Expand All @@ -34,5 +56,14 @@ bool ReadTriangleModel(const std::string& filename,
visualization::rendering::TriangleMeshModel& model,
ReadTriangleModelOptions params = {});

bool WriteTriangleModel(
const std::string& filename,
const visualization::rendering::TriangleMeshModel& model);

// Implemented in FileGLTF.cpp
bool WriteTriangleModelToGLTF(
const std::string& filename,
const visualization::rendering::TriangleMeshModel& mesh_model);

} // namespace io
} // namespace open3d
11 changes: 6 additions & 5 deletions cpp/open3d/io/file_format/FileASSIMP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,12 +237,13 @@ bool ReadTriangleMeshUsingASSIMP(
}

// Now load the materials
mesh.materials_.resize(scene->mNumMaterials);
for (size_t i = 0; i < scene->mNumMaterials; ++i) {
auto* mat = scene->mMaterials[i];

// create material structure to match this name
auto& mesh_material =
mesh.materials_[std::string(mat->GetName().C_Str())];
// Set the material structure to match this name
auto& mesh_material = mesh.materials_[i].second;
mesh.materials_[i].first = mat->GetName().C_Str();

using MaterialParameter =
geometry::TriangleMesh::Material::MaterialParameter;
Expand Down Expand Up @@ -277,9 +278,9 @@ bool ReadTriangleMeshUsingASSIMP(

// For legacy visualization support
if (mesh_material.albedo) {
mesh.textures_.push_back(*mesh_material.albedo->FlipVertical());
mesh.textures_.emplace_back(*mesh_material.albedo->FlipVertical());
} else {
mesh.textures_.push_back(geometry::Image());
mesh.textures_.emplace_back();
}
}

Expand Down
Loading