Skip to content

Commit

Permalink
Make RenderMaterial optional in RenderMesh (#21289)
Browse files Browse the repository at this point in the history
  • Loading branch information
xuchenhan-tri authored Apr 11, 2024
1 parent 37254ee commit b32107d
Show file tree
Hide file tree
Showing 9 changed files with 262 additions and 173 deletions.
13 changes: 3 additions & 10 deletions geometry/geometry_state.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1882,12 +1882,6 @@ void GeometryState<T>::RegisterDrivenPerceptionMesh(GeometryId geometry_id) {
DRAKE_DEMAND(geometry.is_deformable());
DRAKE_DEMAND(geometry.has_perception_role());
const PerceptionProperties& properties = *geometry.perception_properties();
// TODO(xuchenhan-tri): Each render engine customizes its own default diffuse
// color. By setting a default value here, we are subverting the engine,
// preventing it from applying its own logic. Lack of a diffuse color should
// propagate all the way down to the engine for the engine to resolve.
const auto default_rgba = properties.GetPropertyOrDefault(
"phong", "diffuse", Rgba{1.0, 1.0, 1.0, 1.0});

const VolumeMesh<double>* control_mesh_ptr = geometry.reference_mesh();
DRAKE_DEMAND(control_mesh_ptr != nullptr);
Expand All @@ -1902,11 +1896,10 @@ void GeometryState<T>::RegisterDrivenPerceptionMesh(GeometryId geometry_id) {
// control volume mesh as the render mesh.
driven_meshes.emplace_back(internal::MakeDrivenSurfaceMesh(control_mesh));
render_meshes.emplace_back(MakeRenderMeshFromTriangleSurfaceMesh(
driven_meshes.back().triangle_surface_mesh(), properties,
default_rgba));
driven_meshes.back().triangle_surface_mesh(), properties));
} else {
render_meshes = internal::LoadRenderMeshesFromObj(render_meshes_file,
properties, default_rgba);
render_meshes =
internal::LoadRenderMeshesFromObj(render_meshes_file, properties, {});
for (const internal::RenderMesh& render_mesh : render_meshes) {
driven_meshes.emplace_back(MakeTriangleSurfaceMesh(render_mesh),
control_mesh);
Expand Down
68 changes: 38 additions & 30 deletions geometry/render/render_material.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ namespace internal {

using drake::internal::DiagnosticPolicy;

RenderMaterial MakeDiffuseMaterial(const Rgba& diffuse) {
RenderMaterial result;
result.diffuse = diffuse;
return result;
}

void MaybeWarnForRedundantMaterial(const GeometryProperties& props,
std::string_view mesh_name,
const DiagnosticPolicy& policy) {
Expand All @@ -39,11 +45,10 @@ void MaybeWarnForRedundantMaterial(const GeometryProperties& props,
}
}

RenderMaterial MakeMeshFallbackMaterial(const GeometryProperties& props,
const std::filesystem::path& mesh_path,
const Rgba& default_diffuse,
const DiagnosticPolicy& policy,
UvState uv_state) {
std::optional<RenderMaterial> MaybeMakeMeshFallbackMaterial(
const GeometryProperties& props, const std::filesystem::path& mesh_path,
const std::optional<Rgba>& default_diffuse, const DiagnosticPolicy& policy,
UvState uv_state) {
// If a material is indicated *at all* in the properties, that
// defines the material.
if (props.HasProperty("phong", "diffuse") ||
Expand All @@ -60,35 +65,38 @@ RenderMaterial MakeMeshFallbackMaterial(const GeometryProperties& props,
return DefineMaterial(props, Rgba(1, 1, 1), policy, uv_state);
}

std::optional<RenderMaterial> default_result{std::nullopt};
if (default_diffuse.has_value()) {
default_result = MakeDiffuseMaterial(*default_diffuse);
}
if (mesh_path.empty()) {
return default_result;
}
// Checks for foo.png for mesh filename foo.*. This the legacy behavior we
// want to do away with.
RenderMaterial material;

// This is the fall-through condition; the final priority in the protocol.
material.diffuse = default_diffuse;
std::filesystem::path alt_texture_path(mesh_path);
alt_texture_path.replace_extension("png");
// If we find foo.png but can't access it, we'll *silently* treat it like
// we didn't find it. No value in warning for a behavior we're cutting.
if (!std::ifstream(alt_texture_path).is_open()) {
return default_result;
}

if (!mesh_path.empty()) {
std::filesystem::path alt_texture_path(mesh_path);
alt_texture_path.replace_extension("png");
// If we find foo.png but can't access it, we'll *silently* treat it like
// we didn't find it. No value in warning for a behavior we're cutting.
if (std::ifstream(alt_texture_path).is_open()) {
if (uv_state == UvState::kFull) {
material.diffuse_map = alt_texture_path;
} else {
policy.Warning(fmt::format(
"A png file of the same name as the mesh has been found ('{}'), "
"but the mesh doesn't define {} texture coordinates. The map will "
"be omitted leaving a flat white color.",
alt_texture_path.string(),
uv_state == UvState::kNone ? "any" : "a complete set of"));
}
// A white color will leave the texture unmodulated. In the case where
// we couldn't apply the texture, flat white also corresponds to the
// warning message.
material.diffuse = Rgba(1, 1, 1);
}
RenderMaterial material;
if (uv_state == UvState::kFull) {
material.diffuse_map = alt_texture_path;
} else {
policy.Warning(fmt::format(
"A png file of the same name as the mesh has been found ('{}'), "
"but the mesh doesn't define {} texture coordinates. The map will "
"be omitted leaving a flat white color.",
alt_texture_path.string(),
uv_state == UvState::kNone ? "any" : "a complete set of"));
}
// A white color will leave the texture unmodulated. In the case where
// we couldn't apply the texture, flat white also corresponds to the
// warning message.
material.diffuse = Rgba(1, 1, 1);
return material;
}

Expand Down
26 changes: 21 additions & 5 deletions geometry/render/render_material.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <filesystem>
#include <optional>

#include "drake/common/diagnostic_policy.h"
#include "drake/geometry/geometry_properties.h"
Expand Down Expand Up @@ -29,6 +30,16 @@ struct RenderMaterial {
bool from_mesh_file{false};
};

/* Creates a RenderMaterial with the specified diffuse color.
Consumers of RenderMesh can call this function to produce an untextured
RenderMaterial with the prescribed diffuse color when presented with RenderMesh
instances that do not come with their own material definitions.
@param diffuse The RGBA color to be used as the diffuse color for the
material. */
RenderMaterial MakeDiffuseMaterial(const Rgba& diffuse);

/* Dispatches a warning to the given diagnostic policy if the props contain a
material definition. It is assumed an intrinsic material has already been found
for the named mesh. */
Expand All @@ -38,7 +49,8 @@ void MaybeWarnForRedundantMaterial(

/* If a mesh definition doesn't include a single material (e.g., as in an .mtl
file for an .obj mesh), this function applies a cascading priority for
otherwise defining a material for the mesh.
potentially defining a material. Failing everything in the priority list, it
will return std::nullopt.
The material is defined with the following protocol:
Expand All @@ -48,17 +60,21 @@ void MaybeWarnForRedundantMaterial(
- Otherwise, if an image can be located with a "compatible name" (e.g.,
foo.png for a mesh foo.obj), a material with an unmodulated texture is
created.
- Finally, a diffuse material is created with the given default_diffuse
color value.
- Otherwise, if a default_diffuse value is provided, a material is created
with the given default_diffuse color value.
- Finally, if no material is defined, std::nullopt is returned. In such a
case, a consumer of the returned mesh can generate its own material using
its default diffuse color with MakeDiffuseMaterial(). Such a material would
be compliant with the heuristic defined in @ref geometry_materials.
References to textures will be included in the material iff they can be read
and the `uv_state` is full. Otherwise, a warning will be dispatched.
@pre The mesh (named by `mesh_filename`) is a valid mesh and did not have an
acceptable material definition). */
RenderMaterial MakeMeshFallbackMaterial(
std::optional<RenderMaterial> MaybeMakeMeshFallbackMaterial(
const GeometryProperties& props, const std::filesystem::path& mesh_path,
const Rgba& default_diffuse,
const std::optional<Rgba>& default_diffuse,
const drake::internal::DiagnosticPolicy& policy, UvState uv_state);

/* Creates a RenderMaterial from the given set of geometry properties. If no
Expand Down
22 changes: 10 additions & 12 deletions geometry/render/render_mesh.cc
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ RenderMaterial MakeMaterialFromMtl(const tinyobj::material_t& mat,

vector<RenderMesh> LoadRenderMeshesFromObj(
const std::filesystem::path& obj_path, const GeometryProperties& properties,
const Rgba& default_diffuse, const DiagnosticPolicy& policy) {
const std::optional<Rgba>& default_diffuse,
const DiagnosticPolicy& policy) {
tinyobj::ObjReaderConfig config;
config.triangulate = true;
config.vertex_color = false;
Expand Down Expand Up @@ -264,8 +265,8 @@ vector<RenderMesh> LoadRenderMeshesFromObj(
DRAKE_DEMAND(positions.size() == uvs.size());

/* Now we need to partition the prepped geometry data. Each material used
will lead to a unique `RenderMesh` and `RenderMaterial`. Note: the obj may
have declared distinct *objects*. We are erasing that distinction as
will lead to a unique `RenderMesh` and possibly a `RenderMaterial`. Note: the
obj may have declared distinct *objects*. We are erasing that distinction as
irrelevant for rendering the mesh as a rigid structure. */
vector<RenderMesh> meshes;
for (const auto& [mat_index, tri_indices] : material_triangles) {
Expand All @@ -279,7 +280,7 @@ vector<RenderMesh> LoadRenderMeshesFromObj(
if (mat_index == -1) {
/* This is the default material. No material was assigned to the faces.
We'll apply the fallback logic. */
mesh_data.material = MakeMeshFallbackMaterial(
mesh_data.material = MaybeMakeMeshFallbackMaterial(
properties, obj_path, default_diffuse, policy, mesh_data.uv_state);
} else {
mesh_data.material =
Expand Down Expand Up @@ -332,11 +333,10 @@ vector<RenderMesh> LoadRenderMeshesFromObj(

RenderMesh MakeRenderMeshFromTriangleSurfaceMesh(
const TriangleSurfaceMesh<double>& mesh,
const GeometryProperties& properties, const Rgba& default_diffuse,
const DiagnosticPolicy& policy) {
const GeometryProperties& properties, const DiagnosticPolicy& policy) {
RenderMesh result;
result.material = MakeMeshFallbackMaterial(properties, "", default_diffuse,
policy, UvState::kNone);
result.material =
MaybeMakeMeshFallbackMaterial(properties, "", {}, policy, UvState::kNone);
const int vertex_count = mesh.num_vertices();
const int triangle_count = mesh.num_triangles();
result.positions.resize(vertex_count, 3);
Expand Down Expand Up @@ -367,8 +367,7 @@ RenderMesh MakeRenderMeshFromTriangleSurfaceMesh(

RenderMesh MakeFacetedRenderMeshFromTriangleSurfaceMesh(
const TriangleSurfaceMesh<double>& mesh,
const GeometryProperties& properties, const Rgba& default_diffuse,
const DiagnosticPolicy& policy) {
const GeometryProperties& properties, const DiagnosticPolicy& policy) {
// The simple solution is to create a *new* mesh where every triangle has its
// own vertices and then pass to MakeRenderMeshFromTriangleSurfaceMesh().
// If this ever becomes an onerous burden, we can do that directly into the
Expand All @@ -386,8 +385,7 @@ RenderMesh MakeFacetedRenderMeshFromTriangleSurfaceMesh(
}
const TriangleSurfaceMesh<double> faceted(std::move(triangles),
std::move(vertices));
return MakeRenderMeshFromTriangleSurfaceMesh(faceted, properties,
default_diffuse, policy);
return MakeRenderMeshFromTriangleSurfaceMesh(faceted, properties, policy);
}

TriangleSurfaceMesh<double> MakeTriangleSurfaceMesh(
Expand Down
29 changes: 22 additions & 7 deletions geometry/render/render_mesh.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <filesystem>
#include <optional>
#include <vector>

#include <Eigen/Dense>
Expand Down Expand Up @@ -37,8 +38,12 @@ struct RenderMesh {
meaningful. */
UvState uv_state{UvState::kNone};

/* The specification of the material associated with this mesh data. */
RenderMaterial material;
/* The specification of the material associated with this mesh data.
`material` may be undefined (`std::nullopt`). Why it is undefined depends on
the origin of the `RenderMesh`. The consumer of the mesh is free to define
the material as they see fit by defining an arbitrary bespoke material or
using a utility like MakeDiffuseMaterial(). */
std::optional<RenderMaterial> material;
};

// TODO(SeanCurtis-TRI): All of this explanation, and general guidance for what
Expand All @@ -59,6 +64,16 @@ struct RenderMesh {
- Specifying a texture but failing to provide texture coordinates.
- etc.
For faces with no material referenced in the obj file, this function creates a
RenderMesh that may or may not have a material by using the fallback logic. If
a `default_diffuse` value is provided, then a RenderMaterial is guaranteed to
be created. If no `default_diffuse` is provided, then the RenderMesh _may_ have
no material assigned. See MaybeMakeMeshFallbackMaterial() for more details. In
particular, if no material is assigned, then all heuristics in
MaybeMakeMeshFallbackMaterial() have already been attempted, and the only way
for the consumer of the RenderMesh to obtain a material is through
MakeDiffuseMaterial().
Note: This API is similar to ReadObjToTriangleSurfaceMesh, but it differs in
several ways:
Expand Down Expand Up @@ -90,10 +105,10 @@ struct RenderMesh {
they are present. */
std::vector<RenderMesh> LoadRenderMeshesFromObj(
const std::filesystem::path& obj_path, const GeometryProperties& properties,
const Rgba& default_diffuse,
const std::optional<Rgba>& default_diffuse,
const drake::internal::DiagnosticPolicy& policy = {});

/* Constructs a render mesh from a triangle surface mesh.
/* Constructs a render mesh (without material) from a triangle surface mesh.
The normal of a vertex v is computed using area-weighted normals of the
triangles containing vertex v. In particular, for a watertight mesh, this will
Expand All @@ -103,21 +118,21 @@ std::vector<RenderMesh> LoadRenderMeshesFromObj(
UvState will be set to UvState::kNone and all uv coordinates are set to zero.
The material of the resulting render mesh is created using the protocol in
MakeMeshFallbackMaterial() with the given `properties` and `default_diffuse`.
MaybeMakeMeshFallbackMaterial() with the given `properties`.
The returned RenderMesh has the same number of positions, vertex normals, and
uv coordinates as `mesh.num_vertices()`. */
RenderMesh MakeRenderMeshFromTriangleSurfaceMesh(
const TriangleSurfaceMesh<double>& mesh,
const GeometryProperties& properties, const Rgba& default_diffuse,
const GeometryProperties& properties,
const drake::internal::DiagnosticPolicy& policy = {});

/* A variant of MakeRenderMeshFromTriangleSurfaceMesh(). In this case, the
RenderMesh is guaranteed to effectively have per-face normals so it renders
as a faceted mesh. */
RenderMesh MakeFacetedRenderMeshFromTriangleSurfaceMesh(
const TriangleSurfaceMesh<double>& mesh,
const GeometryProperties& properties, const Rgba& default_diffuse,
const GeometryProperties& properties,
const drake::internal::DiagnosticPolicy& policy = {});

/* Converts from RenderMesh to TriangleSurfaceMesh. Only connectivity and
Expand Down
Loading

0 comments on commit b32107d

Please sign in to comment.