From df8f231168f006c96a3f4542771a15e58baf9f98 Mon Sep 17 00:00:00 2001 From: sentientstardust Date: Fri, 29 May 2026 20:33:33 +0100 Subject: [PATCH] Use round-trip opacity for TD calibration to improve accuracy - Use trilinear sampling for color LUT --- .../pigment-painter/pigment_painter_mixer.cpp | 49 +++++++++++++++++-- src/libslic3r/ColorSolver.cpp | 46 ++++++++++++----- src/libslic3r/ColorSolver.hpp | 12 +++-- src/libslic3r/TextureMappingContoning.cpp | 10 ++-- src/libslic3r/TextureMappingContoning.hpp | 1 + src/slic3r/GUI/MMUPaintedTexturePreview.cpp | 11 +++-- src/slic3r/GUI/Plater.cpp | 32 ++++++------ 7 files changed, 120 insertions(+), 41 deletions(-) diff --git a/deps_src/pigment-painter/pigment_painter_mixer.cpp b/deps_src/pigment-painter/pigment_painter_mixer.cpp index 2f31167ba8e..a35cc64c95d 100644 --- a/deps_src/pigment-painter/pigment_painter_mixer.cpp +++ b/deps_src/pigment-painter/pigment_painter_mixer.cpp @@ -137,11 +137,8 @@ const LutImage *lut_image() return image.loaded ? &image : nullptr; } -std::array sample_lut(const LutImage &image, const std::array &color, int z_offset) +std::array sample_lut_texel(const LutImage &image, int x, int y, int z) { - const int x = int(std::floor(clamp01(color[0]) * float(k_lut_dimen - 1))); - const int y = int(std::floor(clamp01(color[1]) * float(k_lut_dimen - 1))); - const int z = int(std::floor(clamp01(color[2]) * float(k_lut_dimen - 1))) + z_offset; const int col = z % image.row_size; const int row = z / image.row_size; const int ix = x + col * k_lut_dimen; @@ -155,6 +152,50 @@ std::array sample_lut(const LutImage &image, const std::array lerp_color(const std::array &a, const std::array &b, float t) +{ + return { + a[0] + (b[0] - a[0]) * t, + a[1] + (b[1] - a[1]) * t, + a[2] + (b[2] - a[2]) * t + }; +} + +std::array sample_lut(const LutImage &image, const std::array &color, int z_offset) +{ + const float xf = clamp01(color[0]) * float(k_lut_dimen - 1); + const float yf = clamp01(color[1]) * float(k_lut_dimen - 1); + const float zf = clamp01(color[2]) * float(k_lut_dimen - 1); + + const int x0 = int(std::floor(xf)); + const int y0 = int(std::floor(yf)); + const int z0 = int(std::floor(zf)); + const int x1 = std::min(x0 + 1, k_lut_dimen - 1); + const int y1 = std::min(y0 + 1, k_lut_dimen - 1); + const int z1 = std::min(z0 + 1, k_lut_dimen - 1); + + const float tx = xf - float(x0); + const float ty = yf - float(y0); + const float tz = zf - float(z0); + + const std::array c000 = sample_lut_texel(image, x0, y0, z0 + z_offset); + const std::array c100 = sample_lut_texel(image, x1, y0, z0 + z_offset); + const std::array c010 = sample_lut_texel(image, x0, y1, z0 + z_offset); + const std::array c110 = sample_lut_texel(image, x1, y1, z0 + z_offset); + const std::array c001 = sample_lut_texel(image, x0, y0, z1 + z_offset); + const std::array c101 = sample_lut_texel(image, x1, y0, z1 + z_offset); + const std::array c011 = sample_lut_texel(image, x0, y1, z1 + z_offset); + const std::array c111 = sample_lut_texel(image, x1, y1, z1 + z_offset); + + const std::array c00 = lerp_color(c000, c100, tx); + const std::array c10 = lerp_color(c010, c110, tx); + const std::array c01 = lerp_color(c001, c101, tx); + const std::array c11 = lerp_color(c011, c111, tx); + const std::array c0 = lerp_color(c00, c10, ty); + const std::array c1 = lerp_color(c01, c11, ty); + return lerp_color(c0, c1, tz); +} + std::array color_to_pigment(const LutImage &image, const std::array &color) { const std::array sampled = sample_lut(image, color, 0); diff --git a/src/libslic3r/ColorSolver.cpp b/src/libslic3r/ColorSolver.cpp index c7e5c260b19..8359f594c00 100644 --- a/src/libslic3r/ColorSolver.cpp +++ b/src/libslic3r/ColorSolver.cpp @@ -344,7 +344,8 @@ ColorSolverNearestResult nearest_color_solver_candidates_perceptual(const Candid std::array mix_ordered_stack_with_buffers(const std::vector> &colors_with_background, std::vector &weights, const std::vector &surface_to_deep, - const std::vector &layer_opacities) + const std::vector &layer_opacities, + float surface_scatter) { if (colors_with_background.empty() || surface_to_deep.empty()) return colors_with_background.empty() ? std::array{ { 0.f, 0.f, 0.f } } : colors_with_background.back(); @@ -352,13 +353,20 @@ std::array mix_ordered_stack_with_buffers(const std::vector= component_count) continue; - const float opacity = + float opacity = size_t(component_idx) < layer_opacities.size() && std::isfinite(layer_opacities[size_t(component_idx)]) ? std::clamp(layer_opacities[size_t(component_idx)], 1e-4f, 0.9999f) : 0.5f; + if (surface_layer) { + opacity = std::clamp(safe_surface_scatter + (1.f - safe_surface_scatter) * opacity, 1e-4f, 0.9999f); + surface_layer = false; + } weights[size_t(component_idx)] += transmission * opacity; transmission *= 1.f - opacity; if (transmission <= 1e-5f) @@ -533,7 +541,8 @@ void append_ordered_stack_candidate(ColorSolverOrderedStackCandidateSet &c std::vector &weights, const std::vector &surface_to_deep, const std::vector &layer_opacities, - int simulated_stack_depth) + int simulated_stack_depth, + float surface_scatter) { std::vector simulated_surface_to_deep; const std::vector *mix_stack = &surface_to_deep; @@ -542,7 +551,7 @@ void append_ordered_stack_candidate(ColorSolverOrderedStackCandidateSet &c mix_stack = &simulated_surface_to_deep; } const std::array mixed = - mix_ordered_stack_with_buffers(colors_with_background, weights, *mix_stack, layer_opacities); + mix_ordered_stack_with_buffers(colors_with_background, weights, *mix_stack, layer_opacities, surface_scatter); const std::array perceptual = oklab_from_srgb(mixed); candidates.rgbs.emplace_back(mixed[0]); candidates.rgbs.emplace_back(mixed[1]); @@ -615,7 +624,8 @@ std::array mix_color_solver_ordered_stack(const std::vector &surface_to_deep, const std::vector &layer_opacities, const std::array &background_rgb, - ColorSolverMixModel mix_model) + ColorSolverMixModel mix_model, + float surface_scatter) { (void) mix_model; if (component_colors.empty() || surface_to_deep.empty()) @@ -624,7 +634,7 @@ std::array mix_color_solver_ordered_stack(const std::vector> colors = component_colors; colors.emplace_back(background_rgb); std::vector weights; - return mix_ordered_stack_with_buffers(colors, weights, surface_to_deep, layer_opacities); + return mix_ordered_stack_with_buffers(colors, weights, surface_to_deep, layer_opacities, surface_scatter); } std::array color_solver_oklab_from_srgb(const std::array &rgb) @@ -757,7 +767,8 @@ std::string color_solver_ordered_stack_candidate_cache_key(const std::vectorsecond; @@ -927,7 +946,8 @@ const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates stack_depth, simulated_stack_depth, candidate_limit, - stack_item_limit)).first->second; + stack_item_limit, + surface_scatter)).first->second; } std::vector solve_color_solver_ordered_stack_for_target( diff --git a/src/libslic3r/ColorSolver.hpp b/src/libslic3r/ColorSolver.hpp index 634492e3259..b9d2ae1d2f0 100644 --- a/src/libslic3r/ColorSolver.hpp +++ b/src/libslic3r/ColorSolver.hpp @@ -109,7 +109,8 @@ std::array mix_color_solver_ordered_stack(const std::vector &surface_to_deep, const std::vector &layer_opacities, const std::array &background_rgb, - ColorSolverMixModel mix_model); + ColorSolverMixModel mix_model, + float surface_scatter = 0.f); std::array color_solver_oklab_from_srgb(const std::array &rgb); std::array color_solver_srgb_from_oklab(const std::array &oklab); @@ -134,7 +135,8 @@ std::string color_solver_ordered_stack_candidate_cache_key(const std::vector> &component_colors, const std::vector &layer_opacities, @@ -143,7 +145,8 @@ ColorSolverOrderedStackCandidateSet build_color_solver_ordered_stack_candidates( int stack_depth, int simulated_stack_depth = 0, size_t candidate_limit = 0, - size_t stack_item_limit = 0); + size_t stack_item_limit = 0, + float surface_scatter = 0.f); const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates( ColorSolverOrderedStackCandidateCache &cache, const std::vector> &component_colors, @@ -153,7 +156,8 @@ const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates int stack_depth, int simulated_stack_depth = 0, size_t candidate_limit = 0, - size_t stack_item_limit = 0); + size_t stack_item_limit = 0, + float surface_scatter = 0.f); std::vector solve_color_solver_ordered_stack_for_target( const ColorSolverOrderedStackCandidateSet &candidates, const std::array &target_rgb, diff --git a/src/libslic3r/TextureMappingContoning.cpp b/src/libslic3r/TextureMappingContoning.cpp index c4150bcf267..33edd7de683 100644 --- a/src/libslic3r/TextureMappingContoning.cpp +++ b/src/libslic3r/TextureMappingContoning.cpp @@ -18,6 +18,7 @@ namespace { constexpr float OPAQUE_CONTONING_TD_THRESHOLD_MM = 0.5f; constexpr float INFERRED_BLACK_TD_MM = 0.1f; +constexpr float CONTONING_SURFACE_SCATTER = 0.12f; constexpr size_t MAX_TD_ORDERED_CONTONING_CANDIDATES = 2500000; constexpr size_t MAX_TD_ORDERED_CONTONING_STACK_ITEMS = 20000000; @@ -114,7 +115,7 @@ float layer_opacity_from_td(float td_mm, float layer_height_mm) { const float safe_td = std::clamp(td_mm, 0.01f, 50.f); const float safe_layer_height = std::clamp(layer_height_mm, 0.01f, 2.f); - return std::clamp(1.f - std::pow(0.05f, safe_layer_height / safe_td), 1e-4f, 0.9999f); + return std::clamp(1.f - std::pow(0.05f, 2.f * safe_layer_height / safe_td), 1e-4f, 0.9999f); } float effective_transmission_distance_for_component(const std::vector &component_ids, @@ -354,6 +355,7 @@ TextureMappingContoningSolver::TextureMappingContoningSolver(const TextureMappin m_layer_height_mm = std::isfinite(layer_height_mm) && layer_height_mm > 0.f ? layer_height_mm : 0.2f; if (!std::isfinite(m_layer_height_mm) || m_layer_height_mm <= 0.f) m_layer_height_mm = 0.2f; + m_surface_scatter = CONTONING_SURFACE_SCATTER; component_ids.erase(std::remove_if(component_ids.begin(), component_ids.end(), [&config](unsigned int id) { return id == 0 || id > config.filament_colour.values.size(); @@ -480,7 +482,8 @@ std::optional> TextureMappingContoningSolver::stack_rgb( surface_to_deep, m_component_layer_opacity, m_background_rgb, - ColorSolverMixModel::PigmentPainter); + ColorSolverMixModel::PigmentPainter, + m_surface_scatter); } void TextureMappingContoningSolver::arrange_stack_for_light_path(std::vector &bottom_to_top, @@ -579,7 +582,8 @@ TextureMappingContoningStack TextureMappingContoningSolver::solve(const std::arr depth, visible_depth, MAX_TD_ORDERED_CONTONING_CANDIDATES, - MAX_TD_ORDERED_CONTONING_STACK_ITEMS); + MAX_TD_ORDERED_CONTONING_STACK_ITEMS, + m_surface_scatter); } const std::vector surface_to_deep = solve_color_solver_ordered_stack_for_target(*ordered_candidates, target_rgb, ColorSolverMode::V2); diff --git a/src/libslic3r/TextureMappingContoning.hpp b/src/libslic3r/TextureMappingContoning.hpp index 15b07d889d1..311dab4b8c9 100644 --- a/src/libslic3r/TextureMappingContoning.hpp +++ b/src/libslic3r/TextureMappingContoning.hpp @@ -65,6 +65,7 @@ private: std::vector m_effective_transmission_distances_mm; std::vector m_component_layer_opacity; float m_layer_height_mm { 0.2f }; + float m_surface_scatter { 0.f }; bool m_td_adjustment_enabled { false }; mutable std::map> m_candidates_by_depth; mutable std::shared_ptr m_ordered_candidate_cache { std::make_shared() }; diff --git a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp index 0d4a07d48b7..66383990775 100644 --- a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp +++ b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp @@ -50,6 +50,7 @@ constexpr size_t k_contoning_flat_surface_preview_max_ordered_stack_items = 2000 constexpr double k_contoning_top_surface_preview_lod_max_samples = 350000.0; constexpr const char *TEXTURE_MAPPING_BACKGROUND_COLOR_CONFIG_KEY = "texture_mapping_background_color"; constexpr float k_contoning_preview_inferred_black_td_mm = 0.1f; +constexpr float k_contoning_preview_surface_scatter = 0.12f; struct TexturePreviewMixCandidate { @@ -98,6 +99,7 @@ struct TexturePreviewSimulationSettings bool simulate_top_surface_lod = false; float top_surface_lod_pitch_mm = 0.f; float contoning_flat_surface_layer_height_mm = 0.2f; + float contoning_flat_surface_surface_scatter = k_contoning_preview_surface_scatter; std::array contoning_flat_surface_background_rgb { { 0.f, 0.f, 0.f } }; std::vector contoning_flat_surface_layer_opacities; float minimum_visibility_offset_factor = 0.f; @@ -576,7 +578,7 @@ float texture_preview_layer_opacity_from_td(float td_mm, float layer_height_mm) { const float safe_td = std::clamp(td_mm, 0.01f, 50.f); const float safe_layer_height = std::clamp(layer_height_mm, 0.01f, 2.f); - return std::clamp(1.f - std::pow(0.05f, safe_layer_height / safe_td), 1e-4f, 0.9999f); + return std::clamp(1.f - std::pow(0.05f, 2.f * safe_layer_height / safe_td), 1e-4f, 0.9999f); } std::vector contoning_flat_surface_preview_layer_opacities( @@ -2507,7 +2509,8 @@ std::array contoning_flat_surface_rgb_for_texture_preview( simulated_surface_to_deep, settings.contoning_flat_surface_layer_opacities, settings.contoning_flat_surface_background_rgb, - ColorSolverMixModel::PigmentPainter); + ColorSolverMixModel::PigmentPainter, + settings.contoning_flat_surface_surface_scatter); } } if (!candidates.empty()) { @@ -2757,6 +2760,7 @@ size_t texture_preview_simulation_signature(const ModelVolume &model_volume, } if (settings.contoning_flat_surface_td_adjustment) { mix(std::hash{}(int(std::lround(settings.contoning_flat_surface_layer_height_mm * 100000.f)))); + mix(std::hash{}(int(std::lround(settings.contoning_flat_surface_surface_scatter * 1000000.f)))); for (const float opacity : settings.contoning_flat_surface_layer_opacities) mix(std::hash{}(int(std::lround(opacity * 1000000.f)))); } @@ -2844,7 +2848,8 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig contoning_flat_surface_pattern_filaments, contoning_flat_surface_pattern_filaments, k_contoning_flat_surface_preview_max_ordered_candidates, - k_contoning_flat_surface_preview_max_ordered_stack_items) : + k_contoning_flat_surface_preview_max_ordered_stack_items, + settings.contoning_flat_surface_surface_scatter) : ColorSolverOrderedStackCandidateSet{}; const std::vector contoning_flat_surface_candidates = use_contoning_flat_surface_quantization && contoning_flat_surface_ordered_candidates.empty() ? diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b9a3b94eb06..4b6a4065a4e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2309,19 +2309,19 @@ public: filament_root->Add(m_force_sequential_filaments_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); filament_root->AddStretchSpacer(1); - auto *filament_calibration_note = - new wxStaticText(filament_page, - wxID_ANY, - _L("NOTE: TD calibration is used by overhang modulation and Contoning TD adjustment"), - wxDefaultPosition, - wxSize(FromDIP(390), -1)); - filament_calibration_note->Wrap(FromDIP(390)); - filament_root->Add(filament_calibration_note, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); - filament_page->Bind(wxEVT_SIZE, [this, filament_page, filament_calibration_note, gap](wxSizeEvent &evt) { - const int wrap_width = std::max(FromDIP(120), filament_page->GetClientSize().GetWidth() - gap * 2); - filament_calibration_note->Wrap(wrap_width); - evt.Skip(); - }); + // auto *filament_calibration_note = + // new wxStaticText(filament_page, + // wxID_ANY, + // _L("NOTE: TD calibration is used by overhang modulation and Contoning TD adjustment"), + // wxDefaultPosition, + // wxSize(FromDIP(390), -1)); + // filament_calibration_note->Wrap(FromDIP(390)); + // filament_root->Add(filament_calibration_note, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); + // filament_page->Bind(wxEVT_SIZE, [this, filament_page, filament_calibration_note, gap](wxSizeEvent &evt) { + // const int wrap_width = std::max(FromDIP(120), filament_page->GetClientSize().GetWidth() - gap * 2); + // filament_calibration_note->Wrap(wrap_width); + // evt.Skip(); + // }); auto *preview_box = new wxStaticBoxSizer(wxVERTICAL, preview_page, _L("3D Preview")); auto *preview_opacity_row = new wxBoxSizer(wxHORIZONTAL); @@ -3779,7 +3779,11 @@ private: return 0; const float layer_height = top_surface_contoning_layer_height_mm(); const float safe_strength = std::clamp(strength, 0.01f, 0.99f); - const float layers = (td_mm / layer_height) * (std::log(1.f - safe_strength) / std::log(0.05f)); + constexpr float surface_scatter = 0.12f; + if (safe_strength <= surface_scatter) + return 1; + const float remaining = std::clamp((1.f - safe_strength) / (1.f - surface_scatter), 1e-6f, 0.999999f); + const float layers = (td_mm / (2.f * layer_height)) * (std::log(remaining) / std::log(0.05f)); return std::max(1, int(std::ceil(layers))); }