From 88a57a39384f3a160dcb2ebae8134562deda3555 Mon Sep 17 00:00:00 2001 From: Sean Curtis Date: Wed, 13 Nov 2024 12:37:52 -0800 Subject: [PATCH] RenderEngineVtk can be forced into PBR mode Adds a new parameter to RenderEngineVtkParams. Rather than waiting for the engine's lighting model to get promoted based on its content, it can be pushed into that mode upon construction. This also fixes a bug in which cloned instances forgot whether or not phong materials should be automatically promoted to PBR. --- .../render_vtk/internal_render_engine_vtk.cc | 8 +- .../render_vtk/render_engine_vtk_params.h | 15 +++ .../test/internal_render_engine_vtk_test.cc | 93 ++++++++++++++++++- 3 files changed, 110 insertions(+), 6 deletions(-) diff --git a/geometry/render_vtk/internal_render_engine_vtk.cc b/geometry/render_vtk/internal_render_engine_vtk.cc index 3ece2d76b161..a5c24434ea19 100644 --- a/geometry/render_vtk/internal_render_engine_vtk.cc +++ b/geometry/render_vtk/internal_render_engine_vtk.cc @@ -186,6 +186,11 @@ RenderEngineVtk::RenderEngineVtk(const RenderEngineVtkParams& parameters) for (auto& pipeline : pipelines_) { pipeline = make_unique(backend); } + + // If it has been explicitly requested, we'll start with PBR. Otherwise, we'll + // defer to the rules for promotion to PBR. + use_pbr_materials_ = parameters.force_to_pbr; + // Only populate the fallback lights if we haven't specified an environment // map. // Until we introduce CubeMap, the default texture (NullTexture) should be @@ -515,7 +520,8 @@ RenderEngineVtk::RenderEngineVtk(const RenderEngineVtk& other) make_unique(other.pipelines_[2]->backend)}}, default_diffuse_{other.default_diffuse_}, default_clear_color_{other.default_clear_color_}, - fallback_lights_(other.fallback_lights_) { + fallback_lights_(other.fallback_lights_), + use_pbr_materials_(other.use_pbr_materials_) { InitializePipelines(); for (const auto& [id, source_props] : other.props_) { diff --git a/geometry/render_vtk/render_engine_vtk_params.h b/geometry/render_vtk/render_engine_vtk_params.h index cf8cecf3be05..4b1f16dda4c5 100644 --- a/geometry/render_vtk/render_engine_vtk_params.h +++ b/geometry/render_vtk/render_engine_vtk_params.h @@ -103,6 +103,7 @@ struct RenderEngineVtkParams { a->Visit(DRAKE_NVP(exposure)); a->Visit(DRAKE_NVP(cast_shadows)); a->Visit(DRAKE_NVP(shadow_map_size)); + a->Visit(DRAKE_NVP(force_to_pbr)); a->Visit(DRAKE_NVP(gltf_extensions)); a->Visit(DRAKE_NVP(backend)); } @@ -212,6 +213,20 @@ struct RenderEngineVtkParams { shadow maps. */ int shadow_map_size{256}; + /** RenderEngineVtk can use one of two illumination models: Phong or + Physically based rendering (PBR). It defaults to Phong. However, it + automatically switches to PBR if: + + - an environment map is added, or + - a glTF mesh is added. + + If `force_to_pbr` is set to true, it switches the engine to use PBR + regardless of the scene's contents. + + Be aware, switching to PBR will lead to a qualitative change in rendered + images even if literally nothing else changes. */ + bool force_to_pbr{false}; + /** Map from the name of a glTF extension (e.g., "KHR_materials_sheen") to render engine settings related to that extension. */ string_map gltf_extensions{ diff --git a/geometry/render_vtk/test/internal_render_engine_vtk_test.cc b/geometry/render_vtk/test/internal_render_engine_vtk_test.cc index 9f08654003c4..1f93422ad464 100644 --- a/geometry/render_vtk/test/internal_render_engine_vtk_test.cc +++ b/geometry/render_vtk/test/internal_render_engine_vtk_test.cc @@ -1956,17 +1956,19 @@ TEST_F(RenderEngineVtkTest, EnvironmentMap) { } } -// RenderEngineVtk promotes all materials to be PBR materials on two conditions: +// RenderEngineVtk promotes all materials to be PBR materials on either of +// several conditions: // // 1. Any geometry with intrinsic PBR materials is introduced (e.g., a glTF) // 2. An environment map is introduced. +// 3. RenderEngineVtkParams::force_to_pbr is set to `true`. // -// (1) has been shown in TEST_F(RenderEngineVtkTest, EnvironmentMap). The +// (1) is confirmed by this test. +// (2) Is confirmed by TEST_F(RenderEngineVtkTest, EnvironmentMap). The // sphere there has a typical phong material and the fact that it gets // illuminated based on the environment shows PBR promotion. -// -// This test we'll simply confirm that the introduction of a glTF shows an -// illumination change without any other step (indicating material promotion). +// (3) Is confirmed in TEST_F(RenderEngineVtkTest, +// PbrMaterialSettingSurvivesCloning). TEST_F(RenderEngineVtkTest, PbrMaterialPromotion) { auto test_sphere_color = [this](int caller_line_number, const TestColor expected_color, @@ -2030,6 +2032,87 @@ TEST_F(RenderEngineVtkTest, PbrMaterialPromotion) { } } +// Materials get promoted from Phong to PBR based on various conditions. When +// a source engine gets promoted and then cloned, the clone should retain that +// logic that subsequent geometries added to the clone should also use a PBR +// material. +// This test also serves as the test for RenderEngineVtkParams::force_to_pbr as +// a promotion criterion. +TEST_F(RenderEngineVtkTest, PbrMaterialSettingSurvivesCloning) { + // First test the efficacy of `force_to_pbr`. + // Two otherwise identical engines with the same geometries should have images + // that don't match if one is PBR and the other is phong. + RenderEngineVtk phong_renderer(RenderEngineVtkParams{}); + RenderEngineVtk pbr_renderer(RenderEngineVtkParams{.force_to_pbr = true}); + // TODO(20002) There is known conflict when we alternate renderings between + // a RenderEngine and its clone. So, we're setting up this "back up" render + // engine that should be identical to pbr_renderer. When the issue is + // resolved, we'll be able to use pbr_renderer in place of this one to + // create the reference images. + RenderEngineVtk renderer_20002(RenderEngineVtkParams{.force_to_pbr = true}); + + // Pose the camera so we can see three sides of the cubes. + const RotationMatrixd R_WR(math::RollPitchYawd(-0.75 * M_PI, 0, M_PI_4)); + const RigidTransformd X_WR(R_WR, + R_WR * -Vector3d(0, 0, 1.5 * kDefaultDistance)); + + InitializeRenderer(X_WR, /* add_terrain = */ true, &phong_renderer); + InitializeRenderer(X_WR, /* add_terrain = */ true, &pbr_renderer); + InitializeRenderer(X_WR, /* add_terrain = */ true, &renderer_20002); + + const fs::path obj_path = + FindResourceOrThrow("drake/geometry/render/test/meshes/rainbow_box.obj"); + const Mesh cube(obj_path); + PerceptionProperties props; + props.AddProperty("label", "id", RenderLabel(17)); + const GeometryId id = GeometryId::get_new_id(); + + phong_renderer.RegisterVisual(id, cube, props, RigidTransformd(), false); + pbr_renderer.RegisterVisual(id, cube, props, RigidTransformd(), false); + renderer_20002.RegisterVisual(id, cube, props, RigidTransformd(), false); + + ImageRgba8U reference_image(kWidth, kHeight); + Render(__LINE__, "phong", &phong_renderer, nullptr, &reference_image, nullptr, + nullptr); + ImageRgba8U pbr_image(kWidth, kHeight); + Render(__LINE__, "pbr", &pbr_renderer, nullptr, &pbr_image, nullptr, nullptr); + + // The images don't match because the `force_to_pbr` changed the light model. + EXPECT_NE(reference_image, pbr_image); + + // Make sure the back up renderer matches. + Render(0, "", &renderer_20002, nullptr, &reference_image, nullptr, nullptr); + EXPECT_EQ(reference_image, pbr_image); + + // Test post-cloning logic. + unique_ptr clone_base = pbr_renderer.Clone(); + RenderEngineVtk* clone = static_cast(clone_base.get()); + ImageRgba8U clone_image(kWidth, kHeight); + // TODO(20002): The known issue of clone vs source render engine fighting + // can be *mitigated* by performing a throw-away rendering. So, we'll do an + // extra rendering and then do one for real. + Render(0, "", clone, nullptr, &clone_image, nullptr, nullptr); + Render(__LINE__, "clone", clone, nullptr, &clone_image, nullptr, nullptr); + + // The cloned engine still renders images with PBR materials. + EXPECT_EQ(clone_image, pbr_image); + + // Now, we'll add a new geometry and confirm that the new geometry gets + // promoted to PBR in both images. + const GeometryId ball_id = GeometryId::get_new_id(); + const Sphere sphere(0.5); + const RigidTransformd X_WS(Vector3d(0, 0, 1)); + props.AddProperty("phong", "diffuse", Rgba(1, 1, 1)); + renderer_20002.RegisterVisual(ball_id, sphere, props, X_WS, false); + clone->RegisterVisual(ball_id, sphere, props, X_WS, false); + + Render(__LINE__, "pbr with ball", &renderer_20002, nullptr, &pbr_image, + nullptr, nullptr); + Render(__LINE__, "clone with ball", clone, nullptr, &clone_image, nullptr, + nullptr); + EXPECT_EQ(clone_image, pbr_image); +} + namespace { // Defines the relationship between two adjacent pixels in a rendering of a box.