diff --git a/geometry/render/render_engine.h b/geometry/render/render_engine.h
index 29c725ff14d4..3248900d33a1 100644
--- a/geometry/render/render_engine.h
+++ b/geometry/render/render_engine.h
@@ -334,7 +334,12 @@ class RenderEngine : public ShapeReifier {
are encoded with unsigned bytes in the range [0, 255] per channel or
_double-valued_ such that each channel is encoded with a double in the range
[0, 1]. Conversion to RenderLabel is only supported from byte-valued color
- values. */
+ values.
+
+ These utilities are provided as a _convenience_ to derived classes. Derived
+ classes are not required to encode labels as colors in the same way. They are
+ only obliged to return label images with proper label values according to
+ the documented semantics. */
//@{
/** Transforms the given byte-valued RGB color value into its corresponding
diff --git a/geometry/render_gltf_client/BUILD.bazel b/geometry/render_gltf_client/BUILD.bazel
index 21e422a024f4..6e3054a52a4a 100644
--- a/geometry/render_gltf_client/BUILD.bazel
+++ b/geometry/render_gltf_client/BUILD.bazel
@@ -188,6 +188,7 @@ drake_cc_library(
srcs = ["test/internal_sample_image_data.cc"],
hdrs = ["test/internal_sample_image_data.h"],
deps = [
+ "//geometry/render:render_label",
"//systems/sensors:image",
],
)
@@ -235,7 +236,6 @@ drake_cc_binary(
testonly = True,
srcs = ["test/server_vtk_backend.cc"],
deps = [
- "//geometry/render:render_label",
"//geometry/render/shaders:depth_shaders",
"//geometry/render_vtk:factory",
"//geometry/render_vtk:internal_render_engine_vtk",
diff --git a/geometry/render_gltf_client/internal_render_engine_gltf_client.cc b/geometry/render_gltf_client/internal_render_engine_gltf_client.cc
index 2d00e9f02b75..55590c44a8a2 100644
--- a/geometry/render_gltf_client/internal_render_engine_gltf_client.cc
+++ b/geometry/render_gltf_client/internal_render_engine_gltf_client.cc
@@ -347,14 +347,19 @@ void RenderEngineGltfClient::DoRenderLabelImage(
ImageRgba8U colored_label_image(width, height);
render_client_->LoadColorImage(image_path, &colored_label_image);
+ // By convention (see render_gltf_client_doxygen.h), server-only artifacts are
+ // colored white to indicate the "don't care" semantic.
// Convert from RGB to Label.
+ const ColorI kDontCare{255, 255, 255};
ColorI color;
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
color.r = colored_label_image.at(x, y)[0];
color.g = colored_label_image.at(x, y)[1];
color.b = colored_label_image.at(x, y)[2];
- label_image_out->at(x, y)[0] = RenderEngine::LabelFromColor(color);
+ label_image_out->at(x, y)[0] = color == kDontCare
+ ? render::RenderLabel::kDontCare
+ : RenderEngine::LabelFromColor(color);
}
}
diff --git a/geometry/render_gltf_client/render_gltf_client_doxygen.h b/geometry/render_gltf_client/render_gltf_client_doxygen.h
index 6a19eeaef5af..b21a9de57254 100644
--- a/geometry/render_gltf_client/render_gltf_client_doxygen.h
+++ b/geometry/render_gltf_client/render_gltf_client_doxygen.h
@@ -50,6 +50,8 @@ its own.
- [Allowed Image Response Types](#allowed-image-response-types)
- [Notes on glTF Camera Specification](#notes-on-gltf-camera-specification)
- [Notes on Communicating Errors](#notes-on-communicating-errors)
+- [Notes on Rendering Label Images](#notes-on-rendering-label-images)
+- [Existing Server Implementations](#existing-server-implementations)
- [Developing your own Server](#developing-your-own-server)
Server API
@@ -253,12 +255,12 @@ The client accepts the following image types from a server render:
interpret this rendering as units of millimeters. Pixels at their maximum
value (2¹⁶-1) will be interpreted as kTooFar (i.e., infinity).
- When `image_type="label"`, the server may return:
- - An RGB (3 channel) unsigned char PNG image. The client will interpret
- this rendering as a colored label image and convert to the final label
- image.
- - An RGBA (4 channel) unsigned char PNG image. The client will interpret
- this rendering as a colored label image and convert to the final label
- image.
+ - An RGB (3 channel) unsigned char PNG image.
+ - An RGBA (4 channel) unsigned char PNG image.
+ - Note: The client will interpret this rendering as a colored label image
+ and convert to the final label image. See
+ [this section](#notes-on-rendering-label-images) for other requirements for
+ returning a label image.
Notes on glTF Camera Specification
@@ -353,6 +355,44 @@ required, the user of the server will have no hints as to what is going wrong
with the client-server communication. When the file response is provided, this
information will be included in the exception message produced by the client.
+Notes on Rendering Label Images
+
+
+Renderers typically can't render objects with "labels". Drake encodes the labels
+associated with geometries as unique colors and provides those colors to the
+server as attributes on the meshes. Thus, the label output from any server will
+be an RGB or RGBA PNG.
+
+__All renderable artifacts that exist _only_ in the server -- that are not part
+of the Drake-provided glTF -- must be colored white (RGB=(255, 255, 255))__.
+These server-only renderable artifacts include:
+
+ - The background color.
+ - Any geometries that the server loads (e.g., walls in a room, environment
+ images, etc.).
+
+When producing the final label output, the client will interpret this particular
+RGB value as render::RenderLabel::kDontCare. This means that a remote server
+will never report a pixel with the render::RenderLabel::kEmpty value.
+
+For an image to be a proper color-encoded label image, the only pixel values in
+the image must be one of the recognized label encodings. This may require
+special render configurations. Any configurations that can introduce color
+variation must be disabled. That includes (but is not limited to) the following
+render features:
+
+ - Anti-aliasing (multi-sampling)
+ - Lighting
+ - Color transformations on the rendered image
+
+Existing Server Implementations
+
+
+[drake-blender] is a glTF render server using [Blender] as the backend.
+
+[drake-blender]: https://github.com/RobotLocomotion/drake-blender
+[Blender]: https://www.blender.org
+
Developing your own Server
diff --git a/geometry/render_gltf_client/test/integration_test.py b/geometry/render_gltf_client/test/integration_test.py
index c3332af5f706..17e6282e69bd 100644
--- a/geometry/render_gltf_client/test/integration_test.py
+++ b/geometry/render_gltf_client/test/integration_test.py
@@ -261,6 +261,13 @@ def test_integration(self):
)
self.assert_error_fraction_less(depth_diff, INVALID_PIXEL_FRACTION)
+ # By convention, where RenderEngineVtk uses RenderLabel::kEmpty
+ # (32766), RenderEngineGltfClient uses RenderLabel::kDontCare
+ # (32764). The values are hard-coded intentionally to avoid having
+ # the large dependency of pydrake. Keep the values in sync if we
+ # ever change the implementation of RenderLabel.
+ # Make them match to facilitate comparison.
+ vtk_label[vtk_label == 32766] = 32764
label_diff = (
np.absolute(vtk_label - client_label) > LABEL_PIXEL_THRESHOLD
)
diff --git a/geometry/render_gltf_client/test/internal_render_engine_gltf_client_test.cc b/geometry/render_gltf_client/test/internal_render_engine_gltf_client_test.cc
index 3b0fa1c777ad..b11d5920d5b2 100644
--- a/geometry/render_gltf_client/test/internal_render_engine_gltf_client_test.cc
+++ b/geometry/render_gltf_client/test/internal_render_engine_gltf_client_test.cc
@@ -281,7 +281,9 @@ TEST_F(RenderEngineGltfClientTest, DoRenderLabelImage) {
// Make sure the temporary directory is / is not being cleaned up.
CheckExpectedFiles(engine.temp_directory(), cleanup, ".png");
- // Make sure the image got loaded as expected.
+ // Make sure the image got loaded as expected. Note that it also tests
+ // whether a server-returned white pixel is properly converted to
+ // render::RenderLabel::kDontCare.
EXPECT_EQ(label_image, CreateTestLabelImage());
}
}
diff --git a/geometry/render_gltf_client/test/internal_sample_image_data.cc b/geometry/render_gltf_client/test/internal_sample_image_data.cc
index 9df5fa94b3e8..292365f8d214 100644
--- a/geometry/render_gltf_client/test/internal_sample_image_data.cc
+++ b/geometry/render_gltf_client/test/internal_sample_image_data.cc
@@ -3,6 +3,7 @@
#include
#include
+#include "drake/geometry/render/render_label.h"
#include "drake/systems/sensors/image.h"
namespace drake {
@@ -59,7 +60,7 @@ ImageDepth32F CreateTestDepthImage() {
ImageLabel16I CreateTestLabelImage() {
ImageLabel16I test_label_image{kTestImageWidth, kTestImageHeight};
using T = ImageLabel16I::T;
- std::vector image_data{0, 1, 2, 3, 4, 5};
+ std::vector image_data{0, 1, 2, 3, 4, render::RenderLabel::kDontCare};
int p = 0;
for (int y = 0; y < 2; ++y) {
for (int x = 0; x < 3; ++x) {
diff --git a/geometry/render_gltf_client/test/internal_sample_image_data.h b/geometry/render_gltf_client/test/internal_sample_image_data.h
index edefead3d35f..6e6c4d228fff 100644
--- a/geometry/render_gltf_client/test/internal_sample_image_data.h
+++ b/geometry/render_gltf_client/test/internal_sample_image_data.h
@@ -39,12 +39,14 @@ systems::sensors::ImageRgba8U CreateTestColorImage(bool pad_alpha);
systems::sensors::ImageDepth32F CreateTestDepthImage();
/* Recreates an ImageLabel16I image that should be exactly the same as the
- loaded test_colored_label_rgba_8U.png after it has been converted.
+ loaded test_colored_label_rgba_8U.png after it has been converted. Note that
+ the value of the sixth pixel is set to render::RenderLabel::kDontCare to test
+ the special conversion of white color (see render_gltf_client_doxygen.h).
┌─────────────────┬─────────────────┬─────────────────┐
│ 0 │ 1 │ 2 │
├─────────────────┼─────────────────┼─────────────────┤
- │ 3 │ 4 │ 5 │
+ │ 3 │ 4 │ kDontCare │
└─────────────────┴─────────────────┴─────────────────┘
Returns an image (width=3, height=2) where every label value is unique. */
diff --git a/geometry/render_gltf_client/test/server_vtk_backend.cc b/geometry/render_gltf_client/test/server_vtk_backend.cc
index f02ef3be3009..2926700e3724 100644
--- a/geometry/render_gltf_client/test/server_vtk_backend.cc
+++ b/geometry/render_gltf_client/test/server_vtk_backend.cc
@@ -274,12 +274,9 @@ int DoMain() {
}
}
} else { // FLAGS_image_type == "label"
- // TODO(zachfang): We need to find a workaround and document it, so that no
- // server implementations need to hard-code these magic numbers.
- /* NOTE: This is hard-coded to be the value that
- RenderEngine::GetColorDFromLabel(RenderLabel::kEmpty) would produce. If
- that value changes, this should change to match. */
- renderer->SetBackground(254.0 / 255.0, 127.0 / 255.0, 0.0);
+ // Following the client-server API (see render_gltf_client_doxygen.h), the
+ // background of a label image should be set to white.
+ renderer->SetBackground(1.0, 1.0, 1.0);
// Same as RenderEngineVtk, label actors have lighting disabled. Labels
// have already been encoded as geometry materials in the glTF. By
diff --git a/geometry/render_gltf_client/test/test_colored_label_rgba_8U.png b/geometry/render_gltf_client/test/test_colored_label_rgba_8U.png
index 8f2632d540c1..d87c983a8e39 100644
Binary files a/geometry/render_gltf_client/test/test_colored_label_rgba_8U.png and b/geometry/render_gltf_client/test/test_colored_label_rgba_8U.png differ