Rename the fast preview to the shaded preview

Its shader files, the name they are registered under, its members and
the panel text now match what it does: shading only, no geometry.
This commit is contained in:
ExPikaPaka
2026-09-23 09:51:08 +02:00
parent d89c6add98
commit 62a45befac
14 changed files with 214 additions and 216 deletions
+11 -11
View File
@@ -323,10 +323,10 @@ checkbox) that puts triangles where the *displaced surface actually bends*, not
a linear **ramp** needs no extra vertices (linear interpolation is exact for a ramp); what needs them is a linear **ramp** needs no extra vertices (linear interpolation is exact for a ramp); what needs them is
**curvature** - the *second* derivative, not the gradient. So the extra predicate is a **chord-error** **curvature** - the *second* derivative, not the gradient. So the extra predicate is a **chord-error**
test: sample the combined displacement at the triangle's three edge midpoints *and its centroid* test: sample the combined displacement at the triangle's three edge midpoints *and its centroid*
(sampling the interior is what catches a bump sitting inside a triangle, the blind spot of an edge-only (sampling the interior is what catches a hill sitting inside a triangle, the blind spot of an edge-only
test) and take the largest departure from the flat triangle's barycentric interpolation. Refine while test) and take the largest departure from the flat triangle's barycentric interpolation. Refine while
that exceeds `chord_tolerance_mm` ("Detail (mm)"). Zero chord error on a ramp ⇒ untouched; high on a that exceeds `chord_tolerance_mm` ("Detail (mm)"). Zero chord error on a ramp ⇒ untouched; high on a
bump/ridge/noise ⇒ refined until captured. Same conformal machinery, so still crack-free. The hill/ridge/noise ⇒ refined until captured. Same conformal machinery, so still crack-free. The
per-triangle error is cached and recomputed only for the children of a split. per-triangle error is cached and recomputed only for the children of a split.
Four knobs bracket it, and all four matter: Four knobs bracket it, and all four matter:
@@ -351,9 +351,9 @@ heights are sampled lazily, so a small patch on a huge model never pays for the
### Fast preview (GPU-only, no CPU meshing) ### Fast preview (GPU-only, no CPU meshing)
`resources/shaders/{110,140}/texture_displacement_bump.{vs,fs}`, registered as `resources/shaders/{110,140}/texture_displacement_shaded.{vs,fs}`, registered as
`"texture_displacement_bump"`. Shades the *displaced* surface without moving geometry - active-layer `"texture_displacement_shaded"`. Shades the *displaced* surface without moving geometry - active-layer
only, selected from the View row, and the default when the gizmo opens (`m_use_bump_preview = true`). only, selected from the View row, and the default when the gizmo opens (`m_use_shaded_preview = true`).
Vertex format is `GLModel::Geometry::EVertexLayout::P3N3T2`: `normal.x` carries the per-vertex paint Vertex format is `GLModel::Geometry::EVertexLayout::P3N3T2`: `normal.x` carries the per-vertex paint
weight (0/1), `normal.y` flags the UV island currently being dragged, and `tex_coord` carries a weight (0/1), `normal.y` flags the UV island currently being dragged, and `tex_coord` carries a
precomputed texture UV, so it can use `GLModel` normally instead of a hand-rolled VBO/VAO manager. precomputed texture UV, so it can use `GLModel` normally instead of a hand-rolled VBO/VAO manager.
@@ -366,7 +366,7 @@ normal from screen-space derivatives of position, not from a per-vertex normal.
**Both preview meshes work in the patch's vertex space, not the mesh's.** Those agree only until a **Both preview meshes work in the patch's vertex space, not the mesh's.** Those agree only until a
*brush* stroke splits a triangle: `get_facets_strict()` then appends the split vertices, so the patch *brush* stroke splits a triangle: `get_facets_strict()` then appends the split vertices, so the patch
array is longer. `rebuild_bump_preview_mesh()` and `rebuild_uvcheck_mesh()` therefore index array is longer. `rebuild_shaded_preview_mesh()` and `rebuild_uvcheck_mesh()` therefore index
`patch.vertices` throughout. The weight buffer is rebuilt at the same cadence as the true-displacement `patch.vertices` throughout. The weight buffer is rebuilt at the same cadence as the true-displacement
preview (stroke-end/slider-release) but from the **live** `TriangleSelector` state, not the flushed model preview (stroke-end/slider-release) but from the **live** `TriangleSelector` state, not the flushed model
facets, so it does not lag by a full model round-trip. facets, so it does not lag by a full model round-trip.
@@ -395,7 +395,7 @@ the bake's.
captures the true on-screen rate of change however the chart is stretched. This path is also what makes captures the true on-screen rate of change however the chart is stretched. This path is also what makes
the fast preview follow the UV editor: move an island and its uv - hence its shading - moves with it the fast preview follow the UV editor: move an island and its uv - hence its shading - moves with it
(the mesh rebuilds on drag-end, `on_island_edited(finished)` → `rebuild_preview()` → (the mesh rebuilds on drag-end, `on_island_edited(finished)` → `rebuild_preview()` →
`rebuild_bump_preview_mesh()`). The branch is uniform and the paint weight gates by multiply, so the `rebuild_shaded_preview_mesh()`). The branch is uniform and the paint weight gates by multiply, so the
texture derivatives stay well defined. A triangle straddling a seam has a discontinuous uv → the texture derivatives stay well defined. A triangle straddling a seam has a discontinuous uv → the
`det≈0` guard skips it (a localised preview-only artifact, never in the bake). `det≈0` guard skips it (a localised preview-only artifact, never in the bake).
@@ -425,7 +425,7 @@ flow. Two uniforms exist for this: `midlevel` (parallax needs the real height, n
and `eye_model_pos` (the camera in the volume's local frame). and `eye_model_pos` (the camera in the volume's local frame).
Parallax cannot change the model's silhouette or cast shadows; the View row's Normal mode is one click Parallax cannot change the model's silhouette or cast shadows; the View row's Normal mode is one click
away for that. The LSCM path stays plain Mikkelsen bump - it has no closed-form uv, so there is no cheap away for that. The LSCM path stays plain Mikkelsen normal perturbation - it has no closed-form uv, so there is no cheap
way to re-project a marched position. One further approximation: the GPU sampler's wrap mode stands in way to re-project a marched position. One further approximation: the GPU sampler's wrap mode stands in
for `tile_enabled`/`tile_method`, so with tiling *off* the GPU repeats where the CPU returns 0 outside for `tile_enabled`/`tile_method`, so with tiling *off* the GPU repeats where the CPU returns 0 outside
`[0,1)`. `[0,1)`.
@@ -558,8 +558,8 @@ gizmo via `CommandFn`; view-only ones (Frame, Snap) it handles directly.
- `src/slic3r/GUI/UVEditorCanvas.hpp/.cpp` - the 2D UV unwrap viewer widget. - `src/slic3r/GUI/UVEditorCanvas.hpp/.cpp` - the 2D UV unwrap viewer widget.
- `src/slic3r/GUI/Plater.hpp/.cpp` - `uv_editor_canvas` member, AUI pane registration, - `src/slic3r/GUI/Plater.hpp/.cpp` - `uv_editor_canvas` member, AUI pane registration,
`get_uv_editor_canvas()`/`show_uv_editor()`. `get_uv_editor_canvas()`/`show_uv_editor()`.
- `src/slic3r/GUI/GLShadersManager.cpp` - registers `"texture_displacement_bump"`. - `src/slic3r/GUI/GLShadersManager.cpp` - registers `"texture_displacement_shaded"`.
- `resources/shaders/{110,140}/texture_displacement_bump.{vs,fs}` - the fast-preview shader. - `resources/shaders/{110,140}/texture_displacement_shaded.{vs,fs}` - the fast-preview shader.
- `src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp` - `PainterGizmoType::TEXTURE_DISPLACEMENT`. - `src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp` - `PainterGizmoType::TEXTURE_DISPLACEMENT`.
- `src/slic3r/GUI/Gizmos/GLGizmosManager.hpp/.cpp` - `EType::TextureDisplacement` registration. - `src/slic3r/GUI/Gizmos/GLGizmosManager.hpp/.cpp` - `EType::TextureDisplacement` registration.
@@ -571,7 +571,7 @@ modes (table-driven), the lowest layer ignoring its blend mode, border displace/
smoothing and its mask guarantees, and adaptive subdivision: conformality (`every_edge_used_twice` on a smoothing and its mask guarantees, and adaptive subdivision: conformality (`every_edge_used_twice` on a
partially-refined cube - an exact crack detector for a closed mesh), the target edge length actually partially-refined cube - an exact crack detector for a closed mesh), the target edge length actually
being reached, the triangle budget capping the result without opening a crack, curvature-driven being reached, the triangle budget capping the result without opening a crack, curvature-driven
refinement (a Gaussian bump refines at its centre, a linear ramp adds nothing), and the max-edge refinement (a Gaussian hill refines at its centre, a linear ramp adds nothing), and the max-edge
baseline. baseline.
`BUILD_TESTS` is `OFF` in the checked-in build cache; flip it on to run them: `BUILD_TESTS` is `OFF` in the checked-in build cache; flip it on to run them:
+2 -2
View File
@@ -79,7 +79,7 @@ radio group; **Wireframe** is an independent toggle. Hover any icon for its tool
| View | Meaning | | View | Meaning |
|------|---------| |------|---------|
| **Normal** | The true displaced geometry — exactly what **Bake** produces. Rebuilt in the background. | | **Normal** | The true displaced geometry — exactly what **Bake** produces. Rebuilt in the background. |
| **Fast** | A GPU bump-shaded approximation of the *active layer only*. No real geometry movement — quick to update, not exact. Best while tuning or dragging islands. | | **Fast** | A GPU shaded approximation of the *active layer only*. No real geometry movement — quick to update, not exact. Best while tuning or dragging islands. |
| **Checker** | A test grid painted over the unwrap so you can see stretching (squares stay square where the map isn't distorted). | | **Checker** | A test grid painted over the unwrap so you can see stretching (squares stay square where the map isn't distorted). |
| **Distortion** | A blue→green→red heatmap of how much each area is compressed or stretched in UV space. Needs the **Unwrap (LSCM)** projection. | | **Distortion** | A blue→green→red heatmap of how much each area is compressed or stretched in UV space. Needs the **Unwrap (LSCM)** projection. |
| **Wireframe** | Overlays the mesh edges (white). Independent of the view above; in **Normal** view it sits on the displaced surface. | | **Wireframe** | Overlays the mesh edges (white). Independent of the view above; in **Normal** view it sits on the displaced surface. |
@@ -315,7 +315,7 @@ Baking is the exact same algorithm as the **Normal** preview, so what you see is
- **Not enough detail?** Subdivide or Remesh before painting fine textures. - **Not enough detail?** Subdivide or Remesh before painting fine textures.
- **Inward cuts** (high Midlevel + big Depth) can self-intersect on thin walls or sharp concave - **Inward cuts** (high Midlevel + big Depth) can self-intersect on thin walls or sharp concave
corners — keep Depth modest there. corners — keep Depth modest there.
- **Fast vs Normal:** Fast preview shades a bump and shows only the active layer; use it for quick - **Fast vs Normal:** Fast preview only shades the relief and shows only the active layer; use it for quick
tuning and smooth UV dragging, but trust **Normal**/**Bake** for the exact result. tuning and smooth UV dragging, but trust **Normal**/**Bake** for the exact result.
- **Topology changes drop unbaked paint.** Subdivide-Apply, Remesh, and Simplify replace the mesh, and - **Topology changes drop unbaked paint.** Subdivide-Apply, Remesh, and Simplify replace the mesh, and
texture-displacement paint isn't remapped across that change (already-baked relief is unaffected). texture-displacement paint isn't remapped across that change (already-baked relief is unaffected).
@@ -1,6 +1,6 @@
#version 110 #version 110
// See resources/shaders/140/texture_displacement_bump.fs for full documentation; this is the // See resources/shaders/140/texture_displacement_shaded.fs for full documentation; this is the
// GLSL 1.10 compatibility variant (same logic, older syntax). // GLSL 1.10 compatibility variant (same logic, older syntax).
#define INTENSITY_CORRECTION 0.6 #define INTENSITY_CORRECTION 0.6
@@ -309,7 +309,7 @@ void main()
bool have_uv = false; bool have_uv = false;
if (use_vertex_uv) { if (use_vertex_uv) {
// Mikkelsen surface-gradient bump; see the 140 variant for the full rationale. Scale-exact // Mikkelsen surface-gradient normal perturbation; see the 140 variant for the full rationale. Scale-exact
// for a conformal LSCM map (no global 1/tiling assumption), and gated by the paint weight // for a conformal LSCM map (no global 1/tiling assumption), and gated by the paint weight
// via a multiply so the branch stays uniform (use_vertex_uv is a uniform). // via a multiply so the branch stays uniform (use_vertex_uv is a uniform).
vec2 uv = (island_active > 0.5) vec2 uv = (island_active > 0.5)
@@ -1,6 +1,6 @@
#version 110 #version 110
// See resources/shaders/140/texture_displacement_bump.vs for full documentation; this is the // See resources/shaders/140/texture_displacement_shaded.vs for full documentation; this is the
// GLSL 1.10 compatibility variant. // GLSL 1.10 compatibility variant.
uniform mat4 view_model_matrix; uniform mat4 view_model_matrix;
@@ -22,7 +22,7 @@ uniform vec2 uv_offset;
uniform bool use_vertex_uv; uniform bool use_vertex_uv;
// The in-shader projection (0 Triplanar, 1 Cylindrical, 2 Spherical) and the painted patch's frame the // The in-shader projection (0 Triplanar, 1 Cylindrical, 2 Spherical) and the painted patch's frame the
// wrapping ones wrap around, in the texture frame - the same uniforms, and the same formulas, as // wrapping ones wrap around, in the texture frame - the same uniforms, and the same formulas, as
// texture_displacement_bump.fs, so the checker reports the projection the bake will actually use. // texture_displacement_shaded.fs, so the checker reports the projection the bake will actually use.
uniform int projection_mode; uniform int projection_mode;
uniform vec3 patch_center; uniform vec3 patch_center;
uniform vec3 patch_axis; uniform vec3 patch_axis;
@@ -91,7 +91,7 @@ void main()
if (any(lessThan(clipping_planes_dots, ZERO))) if (any(lessThan(clipping_planes_dots, ZERO)))
discard; discard;
// World space anchored at the volume's origin, like the bake and the bump preview. // World space anchored at the volume's origin, like the bake and the shaded preview.
vec3 triangle_normal = normalize(cross(dFdx(world_pos.xyz), dFdy(world_pos.xyz))); vec3 triangle_normal = normalize(cross(dFdx(world_pos.xyz), dFdy(world_pos.xyz)));
vec3 tex_pos = world_pos.xyz - tex_anchor; vec3 tex_pos = world_pos.xyz - tex_anchor;
if (volume_mirrored) if (volume_mirrored)
@@ -1,7 +1,7 @@
#version 140 #version 140
// Fast, geometry-free preview of texture displacement: perturbs the *shading* normal from the // Fast, geometry-free preview of texture displacement: perturbs the *shading* normal from the
// height texture's local gradient (a bump map), faded out by the per-vertex paint weight. The // height texture's local gradient, faded out by the per-vertex paint weight. The
// true, exact result is what "Bake" produces via libslic3r/TextureDisplacement.cpp on the CPU. // true, exact result is what "Bake" produces via libslic3r/TextureDisplacement.cpp on the CPU.
// //
// The bake displaces each surface point along its normal by H = +/- depth_mm * (h(uv) - midlevel), // The bake displaces each surface point along its normal by H = +/- depth_mm * (h(uv) - midlevel),
@@ -13,7 +13,7 @@
// derivatives. Two things have to be right for the preview's apparent depth to match the bake's: // derivatives. Two things have to be right for the preview's apparent depth to match the bake's:
// the tangent frame the gradient is expressed in, and the uv->mm scale that turns a texel // the tangent frame the gradient is expressed in, and the uv->mm scale that turns a texel
// difference into a slope. Getting the scale wrong is a uniform flattening (a raw texel difference // difference into a slope. Getting the scale wrong is a uniform flattening (a raw texel difference
// is dh over one texel step, not over one mm); getting the frame wrong tilts the bump along the // is dh over one texel step, not over one mm); getting the frame wrong tilts the relief along the
// wrong axes. // wrong axes.
// //
// Two projection paths: // Two projection paths:
@@ -22,7 +22,7 @@
// formed analytically (there is a closed-form uv, so 1 uv unit is exactly tiling_scale mm). This // formed analytically (there is a closed-form uv, so 1 uv unit is exactly tiling_scale mm). This
// path also runs a parallax step before shading, see below. // path also runs a parallax step before shading, see below.
// //
// Parallax. A pure bump map perturbs shading only, so the pattern is welded to the base surface: it // Parallax. Perturbing the normal alone shades only, so the pattern is welded to the base surface: it
// does not shift as the camera orbits and it does not get any deeper as depth_mm grows, which is // does not shift as the camera orbits and it does not get any deeper as depth_mm grows, which is
// exactly when the preview stops reading as real geometry. The triplanar path therefore shades at the // exactly when the preview stops reading as real geometry. The triplanar path therefore shades at the
// point the *displaced* surface would show at this pixel rather than at the pixel's own base position. // point the *displaced* surface would show at this pixel rather than at the pixel's own base position.
@@ -56,7 +56,7 @@
// assumption, which matters because an LSCM map is conformal, not isometric: the local mm-per-uv // assumption, which matters because an LSCM map is conformal, not isometric: the local mm-per-uv
// varies across the chart, so a single global 1/tiling factor (what an earlier version used) got // varies across the chart, so a single global 1/tiling factor (what an earlier version used) got
// the apparent depth wrong. This path is also what makes the fast preview follow the UV editor: // the apparent depth wrong. This path is also what makes the fast preview follow the UV editor:
// move an island and its uv -- hence its bump -- moves with it. // move an island and its uv -- hence its relief -- moves with it.
#define INTENSITY_CORRECTION 0.6 #define INTENSITY_CORRECTION 0.6
@@ -124,7 +124,7 @@ uniform vec3 tex_anchor; // the volume's origin in world space: the tex
uniform bool use_vertex_uv; // true: sample at vertex_uv with a derived tangent frame (LSCM) uniform bool use_vertex_uv; // true: sample at vertex_uv with a derived tangent frame (LSCM)
// A 2x3 affine (columns packed as lin = (m00, m01, m10, m11), tr = (m02, m12)) applied to the uv of // A 2x3 affine (columns packed as lin = (m00, m01, m10, m11), tr = (m02, m12)) applied to the uv of
// the island currently being dragged in the UV editor (island_active > 0.5). Identity when nothing is // the island currently being dragged in the UV editor (island_active > 0.5). Identity when nothing is
// dragged, so this whole path is a no-op then. Lets a UV island drag move the bump on the model with // dragged, so this whole path is a no-op then. Lets a UV island drag move the relief on the model with
// only a uniform update // only a uniform update
uniform vec4 island_delta_lin; uniform vec4 island_delta_lin;
uniform vec2 island_delta_tr; uniform vec2 island_delta_tr;
@@ -377,7 +377,7 @@ void main()
bool have_uv = false; bool have_uv = false;
if (use_vertex_uv) { if (use_vertex_uv) {
// Precomputed-uv (LSCM) path - Mikkelsen's surface-gradient bump ("Bump Mapping // Precomputed-uv (LSCM) path - Mikkelsen's surface-gradient normal mapping ("Bump Mapping
// Unparametrized Surfaces on the GPU"). The perturbed normal is derived straight from the // Unparametrized Surfaces on the GPU"). The perturbed normal is derived straight from the
// screen-space derivatives of the *sampled height* and the position, so it is scale-exact // screen-space derivatives of the *sampled height* and the position, so it is scale-exact
// with no uv->mm assumption at all - which is the whole point here: an LSCM map is conformal, // with no uv->mm assumption at all - which is the whole point here: an LSCM map is conformal,
@@ -388,7 +388,7 @@ void main()
// use_vertex_uv is a uniform, so this whole branch is uniform control flow and the texture // use_vertex_uv is a uniform, so this whole branch is uniform control flow and the texture
// derivatives are well defined; the paint weight gates the result by a plain multiply (k) // derivatives are well defined; the paint weight gates the result by a plain multiply (k)
// rather than a per-fragment branch, keeping it that way. // rather than a per-fragment branch, keeping it that way.
// The dragged island's uv rides a uniform affine so its bump moves without a rebuild; every // The dragged island's uv rides a uniform affine so its relief moves without a rebuild; every
// other vertex (island_active == 0) samples its baked uv unchanged. // other vertex (island_active == 0) samples its baked uv unchanged.
vec2 uv = (island_active > 0.5) vec2 uv = (island_active > 0.5)
? vec2(dot(island_delta_lin.xy, vertex_uv), dot(island_delta_lin.zw, vertex_uv)) + island_delta_tr ? vec2(dot(island_delta_lin.xy, vertex_uv), dot(island_delta_lin.zw, vertex_uv)) + island_delta_tr
@@ -12,7 +12,7 @@ uniform vec4 clipping_plane;
in vec3 v_position; in vec3 v_position;
// GLModel's P3N3T2 layout (position + normal + texcoord), reused so this mesh builds and renders // GLModel's P3N3T2 layout (position + normal + texcoord), reused so this mesh builds and renders
// like any other GLModel rather than needing a bespoke vertex buffer. The two spare channels carry // like any other GLModel rather than needing a bespoke vertex buffer. The two spare channels carry
// what the bump preview actually needs per vertex: // what the shaded preview actually needs per vertex:
// v_normal.x -- the active layer's paint weight, 0 (untouched) or 1 (painted). // v_normal.x -- the active layer's paint weight, 0 (untouched) or 1 (painted).
// v_normal.y -- 1 for a vertex of the island currently being dragged in the UV editor, else 0. // v_normal.y -- 1 for a vertex of the island currently being dragged in the UV editor, else 0.
// The fragment shader applies island_delta to those vertices' uv, so a UV drag is a // The fragment shader applies island_delta to those vertices' uv, so a UV drag is a
@@ -8,7 +8,7 @@
// where the texture actually lands. // where the texture actually lands.
// mode 1 - Distortion heatmap: the per-vertex area-distortion carried in `distortion`, blue // mode 1 - Distortion heatmap: the per-vertex area-distortion carried in `distortion`, blue
// (compressed) -> green (ideal) -> red (stretched). // (compressed) -> green (ideal) -> red (stretched).
// Both are lit with the same cheap two-light diffuse the bump preview uses, so the surface still // Both are lit with the same cheap two-light diffuse the shaded preview uses, so the surface still
// reads as 3D. // reads as 3D.
#define INTENSITY_CORRECTION 0.6 #define INTENSITY_CORRECTION 0.6
@@ -31,7 +31,7 @@ uniform vec2 uv_offset;
uniform bool use_vertex_uv; uniform bool use_vertex_uv;
// The in-shader projection (0 Triplanar, 1 Cylindrical, 2 Spherical) and the painted patch's frame the // The in-shader projection (0 Triplanar, 1 Cylindrical, 2 Spherical) and the painted patch's frame the
// wrapping ones wrap around, in the texture frame - the same uniforms, and the same formulas, as // wrapping ones wrap around, in the texture frame - the same uniforms, and the same formulas, as
// texture_displacement_bump.fs, so the checker reports the projection the bake will actually use. // texture_displacement_shaded.fs, so the checker reports the projection the bake will actually use.
uniform int projection_mode; uniform int projection_mode;
uniform vec3 patch_center; uniform vec3 patch_center;
uniform vec3 patch_axis; uniform vec3 patch_axis;
@@ -103,7 +103,7 @@ void main()
if (any(lessThan(clipping_planes_dots, ZERO))) if (any(lessThan(clipping_planes_dots, ZERO)))
discard; discard;
// World space anchored at the volume's origin, like the bake and the bump preview. // World space anchored at the volume's origin, like the bake and the shaded preview.
vec3 triangle_normal = normalize(cross(dFdx(world_pos.xyz), dFdy(world_pos.xyz))); vec3 triangle_normal = normalize(cross(dFdx(world_pos.xyz), dFdy(world_pos.xyz)));
vec3 tex_pos = world_pos.xyz - tex_anchor; vec3 tex_pos = world_pos.xyz - tex_anchor;
if (volume_mirrored) if (volume_mirrored)
+6 -6
View File
@@ -134,7 +134,7 @@ struct DecodedTextureCache
}; };
DecodedTextureCache g_decoded_texture_cache; DecodedTextureCache g_decoded_texture_cache;
// The smoothed copy is cached too, one per image: the thumbnail, the bump-preview height texture, the // The smoothed copy is cached too, one per image: the thumbnail, the shaded-preview height texture, the
// colour texture, the projector texture, the UV editor's background and the preview job all ask for // colour texture, the projector texture, the UV editor's background and the preview job all ask for
// the same (image, smoothing) pair in the same frame while the Smoothing slider moves, and each of them // the same (image, smoothing) pair in the same frame while the Smoothing slider moves, and each of them
// blurring its own copy is what froze the UI. Keyed like the raw cache; a different smoothing value // blurring its own copy is what froze the UI. Keyed like the raw cache; a different smoothing value
@@ -428,10 +428,10 @@ V2Resolution recommend_v2_resolution(const indexed_triangle_set
const std::vector<TextureDisplacementLayer> &layers, const std::vector<TextureDisplacementLayer> &layers,
const Transform3d &volume_to_world) const Transform3d &volume_to_world)
{ {
// bumpmesh.com's defaults on model load: edge = diagonal / 250 in [0.05, 5] mm, budget 750 k. A // The defaults on model load: edge = diagonal / 250 in [0.05, 5] mm, budget 750 k. A texture-driven
// texture-driven variant (BumpMesh's smart resolution) was measured to give better walls on step // variant (resolution from the texture's own detail) was measured to give better walls on step
// textures at 2-10x the bake time and up to 2 M output triangles; the user preferred the site's // textures at 2-10x the bake time and up to 2 M output triangles, and was not worth that; these
// defaults. The texel size and sharpness are still reported for the panel. // defaults stayed. The texel size and sharpness are still reported for the panel.
constexpr double EDGE_MIN = 0.05, EDGE_MAX = 5.0, DIAG_DIVISOR = 250.0; constexpr double EDGE_MIN = 0.05, EDGE_MAX = 5.0, DIAG_DIVISOR = 250.0;
constexpr int BUDGET_K = 750; constexpr int BUDGET_K = 750;
@@ -3480,7 +3480,7 @@ indexed_triangle_set subdivide_mesh_adaptive(const indexed_triangle_set &mesh,
// How far the *displaced* surface departs from the flat triangle, sampled across the WHOLE // How far the *displaced* surface departs from the flat triangle, sampled across the WHOLE
// triangle - the three edge midpoints and the centroid - not just one edge midpoint. Sampling the // triangle - the three edge midpoints and the centroid - not just one edge midpoint. Sampling the
// interior is what catches a bump that sits inside a triangle (the blind spot of an edge-only // interior is what catches a hill that sits inside a triangle (the blind spot of an edge-only
// test). Cached per triangle: it can only change when the triangle is split, and then both // test). Cached per triangle: it can only change when the triangle is split, and then both
// children are fresh entries. // children are fresh entries.
std::vector<float> tri_err; std::vector<float> tri_err;
+7 -8
View File
@@ -420,10 +420,9 @@ struct TextureDisplacementOptions
} }
}; };
// How much detail a height texture carries, as BumpMesh's smart resolution measures it: central // How much detail a height texture carries: central differences of the grey image, the mean gradient
// differences of the grey image, the mean gradient and the share of texels steeper than 30 grey // and the share of texels steeper than 30 grey levels, mapped to how many texels one mesh edge may
// levels, mapped to how many texels one mesh edge may span (1 for a hard-edged image, 4 for a smooth // span (1 for a hard-edged image, 4 for a smooth one). Cached per image, like the decode.
// one). Cached per image, like the decode.
struct TextureDetail struct TextureDetail
{ {
float mean_gradient = 0.f; float mean_gradient = 0.f;
@@ -438,8 +437,8 @@ struct TextureDetail
TextureDetail analyze_texture_detail(const TextureDisplacementLayer &layer); TextureDetail analyze_texture_detail(const TextureDisplacementLayer &layer);
// The default pipeline's automatic resolution and budget, when the options leave them at "auto": // The default pipeline's automatic resolution and budget, when the options leave them at "auto":
// bumpmesh.com's defaults - edge = the model's world-space diagonal / 250, clamped to [0.05, 5] mm and // edge = the model's world-space diagonal / 250, clamped to [0.05, 5] mm and rounded up to 0.01;
// rounded up to 0.01; budget 750 k. The texel size of the finest layer and its sharpness class are // budget 750 k. The texel size of the finest layer and its sharpness class are
// reported alongside for the panel. `edge_mm` is 0 for an empty mesh. // reported alongside for the panel. `edge_mm` is 0 for an empty mesh.
struct V2Resolution struct V2Resolution
{ {
@@ -946,10 +945,10 @@ indexed_triangle_set subdivide_mesh_uniform(const indexed_triangle_set &mesh, fl
// departs from the flat triangle by more than `chord_tolerance_mm`, measured as the max // departs from the flat triangle by more than `chord_tolerance_mm`, measured as the max
// over the three edge midpoints AND the centroid of |sampled displacement - the flat // over the three edge midpoints AND the centroid of |sampled displacement - the flat
// triangle's barycentric interpolation|. Sampling the interior, not just edge midpoints, // triangle's barycentric interpolation|. Sampling the interior, not just edge midpoints,
// is what catches a bump that sits inside a triangle. This is a *curvature* test: it is // is what catches a hill that sits inside a triangle. This is a *curvature* test: it is
// exactly zero on a plane or a linear ramp (barycentric interpolation is exact there, so // exactly zero on a plane or a linear ramp (barycentric interpolation is exact there, so
// those stay coarse - the case a gradient criterion would over-refine) and large on a // those stay coarse - the case a gradient criterion would over-refine) and large on a
// bump/ridge/noise. // hill/ridge/noise.
// `min_edge_length_mm` is a hard floor under both: no triangle whose longest edge is already at or // `min_edge_length_mm` is a hard floor under both: no triangle whose longest edge is already at or
// below it is ever refined, which is also what guarantees termination across a sharp texture step // below it is ever refined, which is also what guarantees termination across a sharp texture step
// (where the chord error never falls below the tolerance no matter how fine the mesh gets). // (where the chord error never falls below the tolerance no matter how fine the mesh gets).
+2 -2
View File
@@ -104,8 +104,8 @@ std::pair<bool, std::string> GLShadersManager::init()
valid &= append_shader("mm_gouraud", { prefix + "mm_gouraud.vs", prefix + "mm_gouraud.fs" }, { "FLIP_TRIANGLE_NORMALS"sv }); valid &= append_shader("mm_gouraud", { prefix + "mm_gouraud.vs", prefix + "mm_gouraud.fs" }, { "FLIP_TRIANGLE_NORMALS"sv });
else else
valid &= append_shader("mm_gouraud", { prefix + "mm_gouraud.vs", prefix + "mm_gouraud.fs" }); valid &= append_shader("mm_gouraud", { prefix + "mm_gouraud.vs", prefix + "mm_gouraud.fs" });
// Fast bump-map preview for the texture displacement gizmo (see libslic3r/TextureDisplacement.hpp). // Fast shaded preview for the texture displacement gizmo (see libslic3r/TextureDisplacement.hpp).
valid &= append_shader("texture_displacement_bump", { prefix + "texture_displacement_bump.vs", prefix + "texture_displacement_bump.fs" }); valid &= append_shader("texture_displacement_shaded", { prefix + "texture_displacement_shaded.vs", prefix + "texture_displacement_shaded.fs" });
// UV-check overlay for the same gizmo: a procedural checker or a distortion heatmap over the // UV-check overlay for the same gizmo: a procedural checker or a distortion heatmap over the
// painted patch, to sanity-check the unwrap. // painted patch, to sanity-check the unwrap.
valid &= append_shader("texture_displacement_uvcheck", { prefix + "texture_displacement_uvcheck.vs", prefix + "texture_displacement_uvcheck.fs" }); valid &= append_shader("texture_displacement_uvcheck", { prefix + "texture_displacement_uvcheck.vs", prefix + "texture_displacement_uvcheck.fs" });
@@ -79,7 +79,7 @@ constexpr int THUMBNAIL_MAX_PX = 128;
// Everything above is about drawing a ~48 px panel row, and none of it applies to the height texture // Everything above is about drawing a ~48 px panel row, and none of it applies to the height texture
// the fast-preview *shader* samples: that one is magnified across the model, not minified into a // the fast-preview *shader* samples: that one is magnified across the model, not minified into a
// row, and every texel it loses is relief the preview cannot show. It gets its own upload at (up to) // row, and every texel it loses is relief the preview cannot show. It gets its own upload at (up to)
// this size, so the bump preview reads the same height field the bake does instead of a 128 px box // this size, so the shaded preview reads the same height field the bake does instead of a 128 px box
// blur of it - which is what made Fast look flatter and softer than the result it was previewing. // blur of it - which is what made Fast look flatter and softer than the result it was previewing.
constexpr int HEIGHT_TEX_MAX_PX = 2048; constexpr int HEIGHT_TEX_MAX_PX = 2048;
@@ -224,11 +224,11 @@ constexpr int PALETTE_LUT_EDGE = 24;
// Ceiling on the printable palette, which bounds that fill cost (and the shader's uniform array). // Ceiling on the printable palette, which bounds that fill cost (and the shader's uniform array).
constexpr int PALETTE_MAX_ENTRIES = 64; constexpr int PALETTE_MAX_ENTRIES = 64;
// Ceiling on the filaments the palette's entries can refer to (the bump shader's filament_rgb[]); // Ceiling on the filaments the palette's entries can refer to (the shaded preview shader's filament_rgb[]);
// mmu segmentation stops at Extruder16 anyway. // mmu segmentation stops at Extruder16 anyway.
constexpr int PALETTE_MAX_FILAMENTS = 16; constexpr int PALETTE_MAX_FILAMENTS = 16;
// sRGB (0..1) <-> CIELAB, D65. Exactly what the bump shader's srgb_to_lab() computes, so the CPU // sRGB (0..1) <-> CIELAB, D65. Exactly what the preview shader's srgb_to_lab() computes, so the CPU
// quantizer, the mixed-palette entries and the per-fragment preview all match in the same space. // quantizer, the mixed-palette entries and the per-fragment preview all match in the same space.
// Not slic3r/Utils/ColorSpaceConvert: its RGB2Lab wants 0..1 but its Lab2RGB hands back linear // Not slic3r/Utils/ColorSpaceConvert: its RGB2Lab wants 0..1 but its Lab2RGB hands back linear
// values on a 0..100 scale, and the earlier code fed the former 0..255 and divided the latter by 255 - // values on a 0..100 scale, and the earlier code fed the former 0..255 and divided the latter by 255 -
@@ -390,10 +390,10 @@ void GLGizmoTextureDisplacement::on_shutdown()
{ {
m_parent.toggle_model_objects_visibility(true); m_parent.toggle_model_objects_visibility(true);
m_preview_glmodel.reset(); m_preview_glmodel.reset();
m_bump_preview_glmodel.reset(); m_shaded_preview_glmodel.reset();
m_paint_overlay_glmodel.reset(); m_paint_overlay_glmodel.reset();
m_paint_overlay_dirty = false; m_paint_overlay_dirty = false;
// Any preview still in flight is superseded: bumping the shared counter makes it abort at its next // Any preview still in flight is superseded: raising the shared counter makes it abort at its next
// progress poll, and its completion handler then finds nothing to do. // progress poll, and its completion handler then finds nothing to do.
m_preview_generation->fetch_add(1); m_preview_generation->fetch_add(1);
m_preview_job_pending = false; m_preview_job_pending = false;
@@ -410,9 +410,9 @@ void GLGizmoTextureDisplacement::on_shutdown()
m_subdivide_editing = false; m_subdivide_editing = false;
m_subdivide_preview_tris = -1; m_subdivide_preview_tris = -1;
m_subdivide_preview_glmodel.reset(); m_subdivide_preview_glmodel.reset();
m_bump_active_chart = -1; m_shaded_active_chart = -1;
m_bump_active_face.clear(); m_shaded_active_face.clear();
m_bump_island_delta = Eigen::Matrix<float, 2, 3>::Identity(); m_shaded_island_delta = Eigen::Matrix<float, 2, 3>::Identity();
m_island_drag_active = false; m_island_drag_active = false;
m_island_move_set.clear(); m_island_move_set.clear();
m_adjust_texture_mode = false; m_adjust_texture_mode = false;
@@ -461,39 +461,38 @@ void GLGizmoTextureDisplacement::render_painter_gizmo()
// so it wins the depth test on the coincident (unpainted) surface - keeping the familiar // so it wins the depth test on the coincident (unpainted) surface - keeping the familiar
// enforcer/blocker highlight for precise brush editing there. Where the surface has actually // enforcer/blocker highlight for precise brush editing there. Where the surface has actually
// been displaced, the raised preview geometry legitimately occludes the flat overlay - that // been displaced, the raised preview geometry legitimately occludes the flat overlay - that
// visible bump is itself the "this is painted" indicator in that area. // visible relief is itself the "this is painted" indicator in that area.
// //
// The bump preview is different: it never actually moves geometry (it's a shading trick), so // The shaded preview is different: it never actually moves geometry (it only shades), so
// its depth is identical to the overlay's *everywhere*, not just in the unpainted area - the // its depth is identical to the overlay's *everywhere*, not just in the unpainted area - the
// depth-biased opaque overlay would win the depth test across the whole surface and hide the bump // depth-biased opaque overlay would win the depth test across the whole surface and hide the relief
// shading entirely. So render_triangles() is skipped for it. What is *not* skipped is // shading entirely. So render_triangles() is skipped for it. What is *not* skipped is
// render_paint_overlay(): leaving the bump shading as the only paint feedback meant a stroke that // render_paint_overlay(): leaving the shading as the only paint feedback meant a stroke that
// erased paint, or added it with no texture picked, changed nothing on screen until the whole // erased paint, or added it with no texture picked, changed nothing on screen until the whole
// preview rebuilt at stroke end - and in the true-displacement view the opaque overlay is hidden // preview rebuilt at stroke end - and in the true-displacement view the opaque overlay is hidden
// by the raised surface for the same reason. The translucent tint covers both cases. // by the raised surface for the same reason. The translucent tint covers both cases.
// Coalesced bump rebuild from an in-progress UV island drag (see on_island_edited): done here, at // Coalesced shaded-preview rebuild from an in-progress UV island drag (see on_island_edited): done here, at
// most once per drawn frame, rather than synchronously in the UV canvas's mouse-move handler. // most once per drawn frame, rather than synchronously in the UV canvas's mouse-move handler.
if (m_use_bump_preview && m_bump_preview_dirty) { if (m_use_shaded_preview && m_shaded_preview_dirty) {
rebuild_bump_preview_mesh(); rebuild_shaded_preview_mesh();
m_bump_preview_dirty = false; m_shaded_preview_dirty = false;
} }
// Same coalescing for the paint tint, but on its own flag: a stroke marks this every mouse move // Same coalescing for the paint tint, but on its own flag: a stroke marks this every mouse move
// (see on_mouse()) and it only costs the painted patch, whereas the bump mesh also carries every // (see on_mouse()) and it only costs the painted patch
// unpainted triangle of the volume and stays on the stroke-end cadence.
if (m_paint_overlay_dirty) { if (m_paint_overlay_dirty) {
rebuild_paint_overlay(); rebuild_paint_overlay();
m_paint_overlay_dirty = false; m_paint_overlay_dirty = false;
} }
rebuild_other_paint_overlay(); // a no-op unless another layer's paint, the active layer or the preview changed rebuild_other_paint_overlay(); // a no-op unless another layer's paint, the active layer or the preview changed
// is_initialized() alone is not enough: render_bump_preview_mesh() also needs an active layer // is_initialized() alone is not enough: render_shaded_preview_mesh() also needs an active layer
// with a decoded texture and a compiled shader, and bails silently without them. Hiding the real // with a decoded texture and a compiled shader, and bails silently without them. Hiding the real
// volume for a bump pass that then draws nothing is what made the model vanish - most obviously // volume for a shaded pass that then draws nothing is what made the model vanish - most obviously
// with zero layers, but equally with a layer that has no texture picked yet. // with zero layers, but equally with a layer that has no texture picked yet.
const bool use_bump = m_use_bump_preview && m_bump_preview_glmodel.is_initialized() && bump_preview_ready(); const bool use_shaded = m_use_shaded_preview && m_shaded_preview_glmodel.is_initialized() && shaded_preview_ready();
const bool use_true_preview = !use_bump && m_preview_glmodel.is_initialized(); const bool use_true_preview = !use_shaded && m_preview_glmodel.is_initialized();
// In Checker/Distortion mode the UV-check overlay *is* the surface visualization the user is // In Checker/Distortion mode the UV-check overlay *is* the surface visualization the user is
// looking at, so the opaque paint-selection highlight must not be drawn on top of it - same // looking at, so the opaque paint-selection highlight must not be drawn on top of it - same
// reasoning as skipping it for the bump preview (see bug #12). Without this the painted area // reasoning as skipping it for the shaded preview (see bug #12). Without this the painted area
// covers the checker/heatmap and it can't be seen. // covers the checker/heatmap and it can't be seen.
const bool show_paint_overlay = m_uv_check_mode == UVCheckMode::None; const bool show_paint_overlay = m_uv_check_mode == UVCheckMode::None;
@@ -501,14 +500,14 @@ void GLGizmoTextureDisplacement::render_painter_gizmo()
// put it back. Getting this wrong leaves an invisible model, so it is decided once, here, rather // put it back. Getting this wrong leaves an invisible model, so it is decided once, here, rather
// than per branch below. // than per branch below.
m_parent.toggle_model_objects_visibility(true); m_parent.toggle_model_objects_visibility(true);
if (use_bump || use_true_preview) { if (use_shaded || use_true_preview) {
if (ModelVolume *mv = texture_volume()) if (ModelVolume *mv = texture_volume())
m_parent.toggle_model_objects_visibility(false, m_c->selection_info()->model_object(), m_parent.toggle_model_objects_visibility(false, m_c->selection_info()->model_object(),
m_c->selection_info()->get_active_instance(), mv); m_c->selection_info()->get_active_instance(), mv);
} }
if (use_bump) { if (use_shaded) {
render_bump_preview_mesh(); render_shaded_preview_mesh();
} else if (use_true_preview) { } else if (use_true_preview) {
render_preview_mesh(); render_preview_mesh();
@@ -527,11 +526,11 @@ void GLGizmoTextureDisplacement::render_painter_gizmo()
if (show_paint_overlay) if (show_paint_overlay)
render_paint_overlay(m_other_paint_glmodel); render_paint_overlay(m_other_paint_glmodel);
// The translucent paint tint. Needed in the bump view because the opaque highlight above is // The translucent paint tint. Needed in the shaded view because the opaque highlight above is
// skipped there, and in the true-displacement view because the displaced surface rises *above* // skipped there, and in the true-displacement view because the displaced surface rises *above*
// the undisplaced overlay geometry and hides it exactly where the relief is strongest - in both // the undisplaced overlay geometry and hides it exactly where the relief is strongest - in both
// cases leaving an erase stroke with no visible effect until the next full preview rebuild. // cases leaving an erase stroke with no visible effect until the next full preview rebuild.
if (show_paint_overlay && (use_bump || use_true_preview)) if (show_paint_overlay && (use_shaded || use_true_preview))
render_paint_overlay(m_paint_overlay_glmodel); render_paint_overlay(m_paint_overlay_glmodel);
// The UV editor's island selection, shown on the model. Polled here rather than pushed: the pane // The UV editor's island selection, shown on the model. Polled here rather than pushed: the pane
@@ -1123,9 +1122,9 @@ std::vector<Vec2f> GLGizmoTextureDisplacement::compute_layer_corner_uvs(const in
return corner; return corner;
} }
void GLGizmoTextureDisplacement::rebuild_bump_preview_mesh() void GLGizmoTextureDisplacement::rebuild_shaded_preview_mesh()
{ {
m_bump_preview_glmodel.reset(); m_shaded_preview_glmodel.reset();
const ModelVolume *mv = texture_volume(); const ModelVolume *mv = texture_volume();
if (mv == nullptr || m_triangle_selectors.empty()) if (mv == nullptr || m_triangle_selectors.empty())
@@ -1141,13 +1140,13 @@ void GLGizmoTextureDisplacement::rebuild_bump_preview_mesh()
// No "patch.vertices.size() == mesh vertex count" check here, and that is the point: a *brush* // No "patch.vertices.size() == mesh vertex count" check here, and that is the point: a *brush*
// stroke splits triangles, so the selector appends split vertices and the patch array is longer // stroke splits triangles, so the selector appends split vertices and the patch array is longer
// than the mesh's. An earlier version bailed out on that as "shouldn't happen", which meant the // than the mesh's. An earlier version bailed out on that as "shouldn't happen", which meant the
// bump model was never built while brushing and render_painter_gizmo() silently fell back to the // shaded model was never built while brushing and render_painter_gizmo() silently fell back to the
// Normal (true-displacement) preview - Fast looked broken for brush and fine for Face/Connected // Normal (true-displacement) preview - Fast looked broken for brush and fine for Face/Connected
// area, because only the brush splits. Everything below indexes the patch's own vertex array, so // area, because only the brush splits. Everything below indexes the patch's own vertex array, so
// the extra vertices are simply carried through. // the extra vertices are simply carried through.
// Unpainted triangles, so the surrounding surface still renders (the render path hides the real // Unpainted triangles, so the surrounding surface still renders (the render path hides the real
// model in bump mode). get_facets_strict() returns the same vertex array whatever state is asked. // model in shaded mode). get_facets_strict() returns the same vertex array whatever state is asked.
const indexed_triangle_set rest = m_triangle_selectors[0]->get_facets_strict(EnforcerBlockerType::NONE); const indexed_triangle_set rest = m_triangle_selectors[0]->get_facets_strict(EnforcerBlockerType::NONE);
// For LSCM we hand the shader the finished per-vertex texture uv (island placement + tiling/ // For LSCM we hand the shader the finished per-vertex texture uv (island placement + tiling/
@@ -1159,22 +1158,22 @@ void GLGizmoTextureDisplacement::rebuild_bump_preview_mesh()
// carry its own chart's UVs - see compute_layer_corner_uvs(). // carry its own chart's UVs - see compute_layer_corner_uvs().
const TextureDisplacementLayer *active = active_layer(); const TextureDisplacementLayer *active = active_layer();
std::vector<Vec2f> corner_uv = active != nullptr ? compute_layer_corner_uvs(patch, *active) : std::vector<Vec2f>{}; std::vector<Vec2f> corner_uv = active != nullptr ? compute_layer_corner_uvs(patch, *active) : std::vector<Vec2f>{};
m_bump_preview_uses_vertex_uv = corner_uv.size() == patch.indices.size() * 3; m_shaded_preview_uses_vertex_uv = corner_uv.size() == patch.indices.size() * 3;
if (!m_bump_preview_uses_vertex_uv) if (!m_shaded_preview_uses_vertex_uv)
corner_uv.clear(); corner_uv.clear();
m_bump_projection_mode = (active != nullptr && !m_bump_preview_uses_vertex_uv) ? m_shaded_projection_mode = (active != nullptr && !m_shaded_preview_uses_vertex_uv) ?
layer_projection_frame(patch, *active, m_bump_patch_center, m_bump_patch_axis) : 0; layer_projection_frame(patch, *active, m_shaded_patch_center, m_shaded_patch_axis) : 0;
// Which triangles the in-flight UV drag moves. Computed here, against the very patch this mesh is // Which triangles the in-flight UV drag moves. Computed here, against the very patch this mesh is
// built from, so the flags can never be indexed by a different triangle count than they were sized // built from, so the flags can never be indexed by a different triangle count than they were sized
// for (the drag starts from the flushed facet data, a brush stroke changes the live selector). // for (the drag starts from the flushed facet data, a brush stroke changes the live selector).
compute_bump_active_faces(m_bump_active_chart >= 0 ? m_island_move_set : std::vector<int>{}, patch.indices.size()); compute_shaded_active_faces(m_shaded_active_chart >= 0 ? m_island_move_set : std::vector<int>{}, patch.indices.size());
// Colour is quantized per *fragment* in the shader now (see the .fs), so this mesh carries no // Colour is quantized per *fragment* in the shader now (see the .fs), so this mesh carries no
// colour of its own - the palette and the colour texture are uniforms, and every pixel matches the // colour of its own - the palette and the colour texture are uniforms, and every pixel matches the
// image rather than the facet it landed on. What the *bake* will produce, at facet resolution, is // image rather than the facet it landed on. What the *bake* will produce, at facet resolution, is
// what the Normal view shows. // what the Normal view shows.
m_bump_preview_palette = (active != nullptr && active->color_enabled) ? cached_palette() m_shaded_preview_palette = (active != nullptr && active->color_enabled) ? cached_palette()
: std::vector<PaletteEntry>{}; : std::vector<PaletteEntry>{};
GLModel::Geometry init_data; GLModel::Geometry init_data;
@@ -1186,7 +1185,7 @@ void GLGizmoTextureDisplacement::rebuild_bump_preview_mesh()
// painted face of a raw cube has no strictly-interior vertex (all 8 are shared), so per-vertex // painted face of a raw cube has no strictly-interior vertex (all 8 are shared), so per-vertex
// weighting would either bleed onto the neighbours (boundary weight 1) or vanish outright (boundary // weighting would either bleed onto the neighbours (boundary weight 1) or vanish outright (boundary
// weight 0, which is what made a single face show nothing). Duplicating vertices costs no shading // weight 0, which is what made a single face show nothing). Duplicating vertices costs no shading
// quality here because the bump shader takes its surface normal from screen-space derivatives of // quality here because the preview shader takes its surface normal from screen-space derivatives of
// position (dFdx/dFdy), not from a per-vertex normal. normal.y flags the UV-editor island being // position (dFdx/dFdy), not from a per-vertex normal. normal.y flags the UV-editor island being
// dragged so the shader can move just that island via the island_delta uniform. // dragged so the shader can move just that island via the island_delta uniform.
const size_t tri_total = patch.indices.size() + rest.indices.size(); const size_t tri_total = patch.indices.size() + rest.indices.size();
@@ -1198,10 +1197,10 @@ void GLGizmoTextureDisplacement::rebuild_bump_preview_mesh()
const stl_triangle_vertex_indices &tri = its.indices[f]; const stl_triangle_vertex_indices &tri = its.indices[f];
// One value for the whole triangle: island_active is an interpolated varying, so the three // One value for the whole triangle: island_active is an interpolated varying, so the three
// corners have to agree or the shader moves part of a triangle and not the rest. // corners have to agree or the shader moves part of a triangle and not the rest.
const float act = (painted && f < m_bump_active_face.size() && m_bump_active_face[f]) ? 1.f : 0.f; const float act = (painted && f < m_shaded_active_face.size() && m_shaded_active_face[f]) ? 1.f : 0.f;
for (int i = 0; i < 3; ++i) { for (int i = 0; i < 3; ++i) {
const int idx = tri[i]; const int idx = tri[i];
const Vec2f uv = (painted && m_bump_preview_uses_vertex_uv) ? corner_uv[f * 3 + size_t(i)] const Vec2f uv = (painted && m_shaded_preview_uses_vertex_uv) ? corner_uv[f * 3 + size_t(i)]
: Vec2f::Zero(); : Vec2f::Zero();
init_data.add_vertex(its.vertices[size_t(idx)], Vec3f(weight, act, 0.f), uv); init_data.add_vertex(its.vertices[size_t(idx)], Vec3f(weight, act, 0.f), uv);
} }
@@ -1209,50 +1208,50 @@ void GLGizmoTextureDisplacement::rebuild_bump_preview_mesh()
vcount += 3; vcount += 3;
} }
}; };
emit_triangles(patch, 1.f, true); // painted -> bumped, and coloured by the shader emit_triangles(patch, 1.f, true); // painted -> shaded as relief, and coloured by the shader
// Untouched surface: flat, so it still shows but isn't bumped - and uncoloured, which is what the // Untouched surface: flat, so it still shows but carries no relief - and uncoloured, which is what the
// bake leaves it as (EnforcerBlockerType::NONE, i.e. the volume's own filament). // bake leaves it as (EnforcerBlockerType::NONE, i.e. the volume's own filament).
emit_triangles(rest, 0.f, false); emit_triangles(rest, 0.f, false);
m_bump_preview_glmodel.init_from(std::move(init_data)); m_shaded_preview_glmodel.init_from(std::move(init_data));
// GLModel::render() unconditionally re-sets the shader's "uniform_color" from this internal // GLModel::render() unconditionally re-sets the shader's "uniform_color" from this internal
// color field right before drawing (see GLModel.cpp) - setting the uniform manually in // color field right before drawing (see GLModel.cpp) - setting the uniform manually in
// render_bump_preview_mesh() would just get overwritten by it, so it must be set here instead. // render_shaded_preview_mesh() would just get overwritten by it, so it must be set here instead.
// GLModel::Geometry defaults to BLACK, which is exactly what showed up before this was added. // GLModel::Geometry defaults to BLACK, which is exactly what showed up before this was added.
m_bump_preview_glmodel.set_color(GLVolume::NEUTRAL_COLOR); m_shaded_preview_glmodel.set_color(GLVolume::NEUTRAL_COLOR);
// The mesh now reflects the islands' current placement, so any live drag delta is measured from // The mesh now reflects the islands' current placement, so any live drag delta is measured from
// here: reset it to identity and record the dragged island's baked transform. // here: reset it to identity and record the dragged island's baked transform.
m_bump_island_delta = Eigen::Matrix<float, 2, 3>::Identity(); m_shaded_island_delta = Eigen::Matrix<float, 2, 3>::Identity();
const TextureDisplacementLayer *al = active_layer(); const TextureDisplacementLayer *al = active_layer();
if (m_bump_active_chart >= 0 && al != nullptr) { if (m_shaded_active_chart >= 0 && al != nullptr) {
const std::vector<Eigen::Matrix<float, 2, 3>> xf = uv_editor_island_transforms(*al); const std::vector<Eigen::Matrix<float, 2, 3>> xf = uv_editor_island_transforms(*al);
m_bump_baked_active_xf = (size_t(m_bump_active_chart) < xf.size()) ? xf[size_t(m_bump_active_chart)] m_shaded_baked_active_xf = (size_t(m_shaded_active_chart) < xf.size()) ? xf[size_t(m_shaded_active_chart)]
: Eigen::Matrix<float, 2, 3>::Identity(); : Eigen::Matrix<float, 2, 3>::Identity();
} else { } else {
m_bump_baked_active_xf = Eigen::Matrix<float, 2, 3>::Identity(); m_shaded_baked_active_xf = Eigen::Matrix<float, 2, 3>::Identity();
} }
} }
void GLGizmoTextureDisplacement::compute_bump_active_faces(const std::vector<int> &charts, size_t patch_face_count) void GLGizmoTextureDisplacement::compute_shaded_active_faces(const std::vector<int> &charts, size_t patch_face_count)
{ {
m_bump_active_face.clear(); m_shaded_active_face.clear();
if (charts.empty() || patch_face_count == 0) if (charts.empty() || patch_face_count == 0)
return; return;
const PatchUnwrap &u = m_uv_editor_unwrap; const PatchUnwrap &u = m_uv_editor_unwrap;
if (u.source_face.size() != u.indices.size()) if (u.source_face.size() != u.indices.size())
return; return;
m_bump_active_face.assign(patch_face_count, 0); m_shaded_active_face.assign(patch_face_count, 0);
// Flag every triangle of every chart being moved. For a group/multi move that is more than one // Flag every triangle of every chart being moved. For a group/multi move that is more than one
// chart, but since such a move is a pure translation the shader applies the same delta to them all // chart, but since such a move is a pure translation the shader applies the same delta to them all
// (see on_island_edited) - exactly the "joined islands move together" behaviour. // (see on_island_edited) - exactly the "joined islands move together" behaviour.
for (size_t t = 0; t < u.indices.size(); ++t) { for (size_t t = 0; t < u.indices.size(); ++t) {
const int f = u.source_face[t]; const int f = u.source_face[t];
const int v0 = u.indices[t][0]; // a triangle lies in one chart, so any corner names it const int v0 = u.indices[t][0]; // a triangle lies in one chart, so any corner names it
if (f < 0 || size_t(f) >= m_bump_active_face.size() || v0 < 0 || size_t(v0) >= u.vertex_chart.size()) if (f < 0 || size_t(f) >= m_shaded_active_face.size() || v0 < 0 || size_t(v0) >= u.vertex_chart.size())
continue; continue;
if (std::find(charts.begin(), charts.end(), u.vertex_chart[size_t(v0)]) != charts.end()) if (std::find(charts.begin(), charts.end(), u.vertex_chart[size_t(v0)]) != charts.end())
m_bump_active_face[size_t(f)] = 1; m_shaded_active_face[size_t(f)] = 1;
} }
} }
@@ -1313,11 +1312,11 @@ std::vector<int> GLGizmoTextureDisplacement::build_island_move_set(const Texture
return set; return set;
} }
void GLGizmoTextureDisplacement::render_bump_preview_mesh() void GLGizmoTextureDisplacement::render_shaded_preview_mesh()
{ {
const ModelObject *mo = m_c->selection_info()->model_object(); const ModelObject *mo = m_c->selection_info()->model_object();
const ModelVolume *mv = texture_volume(); const ModelVolume *mv = texture_volume();
if (mo == nullptr || mv == nullptr || !m_bump_preview_glmodel.is_initialized()) if (mo == nullptr || mv == nullptr || !m_shaded_preview_glmodel.is_initialized())
return; return;
const TextureDisplacementLayer *layer = active_layer(); const TextureDisplacementLayer *layer = active_layer();
@@ -1334,7 +1333,7 @@ void GLGizmoTextureDisplacement::render_bump_preview_mesh()
if (tex == nullptr || tex->get_width() <= 0 || tex->get_height() <= 0) if (tex == nullptr || tex->get_width() <= 0 || tex->get_height() <= 0)
return; return;
GLShaderProgram *shader = wxGetApp().get_shader("texture_displacement_bump"); GLShaderProgram *shader = wxGetApp().get_shader("texture_displacement_shaded");
if (shader == nullptr) if (shader == nullptr)
return; return;
@@ -1401,14 +1400,14 @@ void GLGizmoTextureDisplacement::render_bump_preview_mesh()
shader->set_uniform("tex_anchor", Vec3f(tex_anchor.cast<float>())); shader->set_uniform("tex_anchor", Vec3f(tex_anchor.cast<float>()));
shader->set_uniform("eye_model_pos", Vec3f((camera.get_position() - tex_anchor).cast<float>())); shader->set_uniform("eye_model_pos", Vec3f((camera.get_position() - tex_anchor).cast<float>()));
// When set, the shader samples at the per-vertex uv baked into the mesh (LSCM) rather than // When set, the shader samples at the per-vertex uv baked into the mesh (LSCM) rather than
// projecting; see rebuild_bump_preview_mesh(). // projecting; see rebuild_shaded_preview_mesh().
shader->set_uniform("use_vertex_uv", m_bump_preview_uses_vertex_uv); shader->set_uniform("use_vertex_uv", m_shaded_preview_uses_vertex_uv);
// Cylindrical/Spherical wrap around the painted patch's own centre, which no fragment can derive: // Cylindrical/Spherical wrap around the painted patch's own centre, which no fragment can derive:
// captured with the mesh (see rebuild_bump_preview_mesh()) and handed over here. 0 is the planar // captured with the mesh (see rebuild_shaded_preview_mesh()) and handed over here. 0 is the planar
// projection every other in-shader path uses. // projection every other in-shader path uses.
shader->set_uniform("projection_mode", m_bump_projection_mode); shader->set_uniform("projection_mode", m_shaded_projection_mode);
shader->set_uniform("patch_center", m_bump_patch_center); shader->set_uniform("patch_center", m_shaded_patch_center);
shader->set_uniform("patch_axis", m_bump_patch_axis); shader->set_uniform("patch_axis", m_shaded_patch_axis);
// The filament palette the mesh's per-triangle indices refer to. Count 0 means "no layer is // The filament palette the mesh's per-triangle indices refer to. Count 0 means "no layer is
// colouring", and the shader keeps the model's own colour for every fragment. // colouring", and the shader keeps the model's own colour for every fragment.
@@ -1416,13 +1415,13 @@ void GLGizmoTextureDisplacement::render_bump_preview_mesh()
// matched on the CPU because the quantization is per fragment here. // matched on the CPU because the quantization is per fragment here.
const GLTexture *color_tex = get_layer_color_texture(*layer); const GLTexture *color_tex = get_layer_color_texture(*layer);
const int palette_count = const int palette_count =
(color_tex != nullptr) ? int(std::min(m_bump_preview_palette.size(), size_t(PALETTE_MAX_ENTRIES))) : 0; (color_tex != nullptr) ? int(std::min(m_shaded_preview_palette.size(), size_t(PALETTE_MAX_ENTRIES))) : 0;
shader->set_uniform("palette_count", palette_count); shader->set_uniform("palette_count", palette_count);
shader->set_uniform("has_color_tex", color_tex != nullptr); shader->set_uniform("has_color_tex", color_tex != nullptr);
// A flat-colour image is matched against single filaments only, as the bake does. // A flat-colour image is matched against single filaments only, as the bake does.
shader->set_uniform("pure_only", color_tex != nullptr && analyze_texture_detail(*layer).flat_colors); shader->set_uniform("pure_only", color_tex != nullptr && analyze_texture_detail(*layer).flat_colors);
for (int i = 0; i < palette_count; ++i) { for (int i = 0; i < palette_count; ++i) {
const PaletteEntry &e = m_bump_preview_palette[size_t(i)]; const PaletteEntry &e = m_shaded_preview_palette[size_t(i)];
const std::string idx = "[" + std::to_string(i) + "]"; const std::string idx = "[" + std::to_string(i) + "]";
shader->set_uniform(("palette_rgb" + idx).c_str(), e.rgb); shader->set_uniform(("palette_rgb" + idx).c_str(), e.rgb);
shader->set_uniform(("palette_lab" + idx).c_str(), srgb_to_lab(e.rgb)); shader->set_uniform(("palette_lab" + idx).c_str(), srgb_to_lab(e.rgb));
@@ -1434,7 +1433,7 @@ void GLGizmoTextureDisplacement::render_bump_preview_mesh()
} }
// The filaments those indices refer to, and the interleave the shader resolves a mix with - the // The filaments those indices refer to, and the interleave the shader resolves a mix with - the
// same inputs make_mix_resolver() gets, so the preview shows the pattern that prints rather than // same inputs make_mix_resolver() gets, so the preview shows the pattern that prints rather than
// the mix's smooth average colour. m_palette_filaments is what m_bump_preview_palette was built from. // the mix's smooth average colour. m_palette_filaments is what m_shaded_preview_palette was built from.
const int filament_count = const int filament_count =
(palette_count > 0) ? int(std::min(m_palette_filaments.size(), size_t(PALETTE_MAX_FILAMENTS))) : 0; (palette_count > 0) ? int(std::min(m_palette_filaments.size(), size_t(PALETTE_MAX_FILAMENTS))) : 0;
shader->set_uniform("filament_count", filament_count); shader->set_uniform("filament_count", filament_count);
@@ -1453,18 +1452,18 @@ void GLGizmoTextureDisplacement::render_bump_preview_mesh()
} }
// The live UV-editor island drag rides this 2x3 affine (identity except mid-drag); only the flagged // The live UV-editor island drag rides this 2x3 affine (identity except mid-drag); only the flagged
// island's vertices apply it, so a drag is a uniform update rather than a mesh rebuild. // island's vertices apply it, so a drag is a uniform update rather than a mesh rebuild.
const Eigen::Matrix<float, 2, 3> &d = m_bump_island_delta; const Eigen::Matrix<float, 2, 3> &d = m_shaded_island_delta;
shader->set_uniform("island_delta_lin", std::array<float, 4>{ d(0, 0), d(0, 1), d(1, 0), d(1, 1) }); shader->set_uniform("island_delta_lin", std::array<float, 4>{ d(0, 0), d(0, 1), d(1, 0), d(1, 1) });
shader->set_uniform("island_delta_tr", Vec2f(d(0, 2), d(1, 2))); shader->set_uniform("island_delta_tr", Vec2f(d(0, 2), d(1, 2)));
m_bump_preview_glmodel.render(); m_shaded_preview_glmodel.render();
glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
shader->stop_using(); shader->stop_using();
} }
bool GLGizmoTextureDisplacement::bump_preview_ready() const bool GLGizmoTextureDisplacement::shaded_preview_ready() const
{ {
// Mirrors render_bump_preview_mesh()'s own preconditions. Kept as a separate query because the // Mirrors render_shaded_preview_mesh()'s own preconditions. Kept as a separate query because the
// caller has to know whether the bump pass will draw *before* it hides the real volume for it. // caller has to know whether the shaded pass will draw *before* it hides the real volume for it.
if (m_c->selection_info() == nullptr || m_c->selection_info()->model_object() == nullptr) if (m_c->selection_info() == nullptr || m_c->selection_info()->model_object() == nullptr)
return false; return false;
if (texture_volume() == nullptr) if (texture_volume() == nullptr)
@@ -1472,12 +1471,12 @@ bool GLGizmoTextureDisplacement::bump_preview_ready() const
const TextureDisplacementLayer *layer = active_layer(); const TextureDisplacementLayer *layer = active_layer();
if (layer == nullptr || layer->empty()) if (layer == nullptr || layer->empty())
return false; return false;
// The same texture render_bump_preview_mesh() will bind, not the panel thumbnail - the two are // The same texture render_shaded_preview_mesh() will bind, not the panel thumbnail - the two are
// separate caches and either can fail on its own. // separate caches and either can fail on its own.
const GLTexture *tex = const_cast<GLGizmoTextureDisplacement *>(this)->get_layer_height_texture(*layer); const GLTexture *tex = const_cast<GLGizmoTextureDisplacement *>(this)->get_layer_height_texture(*layer);
if (tex == nullptr || tex->get_width() <= 0 || tex->get_height() <= 0) if (tex == nullptr || tex->get_width() <= 0 || tex->get_height() <= 0)
return false; return false;
return wxGetApp().get_shader("texture_displacement_bump") != nullptr; return wxGetApp().get_shader("texture_displacement_shaded") != nullptr;
} }
// Appends a painted patch to an overlay, lifted onto the displaced surface where that has the base mesh's // Appends a painted patch to an overlay, lifted onto the displaced surface where that has the base mesh's
@@ -1502,7 +1501,7 @@ void GLGizmoTextureDisplacement::rebuild_other_paint_overlay()
// displaced positions it is lifted onto. Compared every frame, rebuilt only when it differs. // displaced positions it is lifted onto. Compared every frame, rebuilt only when it differs.
std::string key; std::string key;
if (mv != nullptr) { if (mv != nullptr) {
key = std::to_string(mv->id().id) + ":" + std::to_string(m_active_layer_slot) + (m_use_bump_preview ? ":b:" : ":t:") + key = std::to_string(mv->id().id) + ":" + std::to_string(m_active_layer_slot) + (m_use_shaded_preview ? ":b:" : ":t:") +
std::to_string(reinterpret_cast<uintptr_t>(m_preview_its.vertices.data())) + ":" + std::to_string(reinterpret_cast<uintptr_t>(m_preview_its.vertices.data())) + ":" +
std::to_string(m_preview_its.vertices.size()); std::to_string(m_preview_its.vertices.size());
for (const TextureDisplacementLayer &l : mv->texture_displacement_layers) for (const TextureDisplacementLayer &l : mv->texture_displacement_layers)
@@ -1517,7 +1516,7 @@ void GLGizmoTextureDisplacement::rebuild_other_paint_overlay()
return; return;
const std::vector<Vec3f> *displaced = nullptr; const std::vector<Vec3f> *displaced = nullptr;
if (!m_use_bump_preview && m_preview_its.vertices.size() == mv->mesh().its.vertices.size() && if (!m_use_shaded_preview && m_preview_its.vertices.size() == mv->mesh().its.vertices.size() &&
!m_preview_its.vertices.empty()) !m_preview_its.vertices.empty())
displaced = &m_preview_its.vertices; displaced = &m_preview_its.vertices;
@@ -1559,7 +1558,7 @@ void GLGizmoTextureDisplacement::rebuild_paint_overlay()
// the brush split live past the end of that array and keep their flat position; they sit on the // the brush split live past the end of that array and keep their flat position; they sit on the
// patch boundary, where the displacement is smallest anyway. // patch boundary, where the displacement is smallest anyway.
const std::vector<Vec3f> *displaced = nullptr; const std::vector<Vec3f> *displaced = nullptr;
if (!m_use_bump_preview && m_preview_its.vertices.size() == mv->mesh().its.vertices.size() && if (!m_use_shaded_preview && m_preview_its.vertices.size() == mv->mesh().its.vertices.size() &&
!m_preview_its.vertices.empty()) !m_preview_its.vertices.empty())
displaced = &m_preview_its.vertices; displaced = &m_preview_its.vertices;
@@ -1592,7 +1591,7 @@ void GLGizmoTextureDisplacement::render_paint_overlay(GLModel &overlay)
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * trafo_matrix); shader->set_uniform("view_model_matrix", camera.get_view_matrix() * trafo_matrix);
shader->set_uniform("projection_matrix", camera.get_projection_matrix()); shader->set_uniform("projection_matrix", camera.get_projection_matrix());
// Translucent, and pulled toward the camera so it wins the depth test against the coincident // Translucent, and pulled toward the camera so it wins the depth test against the coincident
// bump surface. Depth writes are off: this is a tint, and letting it own the depth buffer would // shaded surface. Depth writes are off: this is a tint, and letting it own the depth buffer would
// make the wireframe and seam overlays drawn after it fight with geometry that is not really // make the wireframe and seam overlays drawn after it fight with geometry that is not really
// there. Blending is already enabled by render_painter_gizmo(). // there. Blending is already enabled by render_painter_gizmo().
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL)); glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
@@ -1703,7 +1702,7 @@ void GLGizmoTextureDisplacement::rebuild_uvcheck_mesh()
m_uvcheck_uses_vertex_uv = have_uvs; m_uvcheck_uses_vertex_uv = have_uvs;
m_uvcheck_projection_mode = have_uvs ? 0 : m_uvcheck_projection_mode = have_uvs ? 0 :
layer_projection_frame(patch, *layer, m_uvcheck_patch_center, m_uvcheck_patch_axis); layer_projection_frame(patch, *layer, m_uvcheck_patch_center, m_uvcheck_patch_axis);
// Per corner as well, for the same reason the bump mesh takes them: under LSCM a seam vertex has a // Per corner as well: under LSCM a seam vertex has a
// different uv in each island it borders, so the shared-vertex form drew one triangle per face from // different uv in each island it borders, so the shared-vertex form drew one triangle per face from
// a neighbouring island's placement. Only the *drawing* needs this; the distortion metric below is // a neighbouring island's placement. Only the *drawing* needs this; the distortion metric below is
// a per-vertex average by construction and keeps using `uv`. // a per-vertex average by construction and keeps using `uv`.
@@ -1797,7 +1796,7 @@ void GLGizmoTextureDisplacement::render_uvcheck_mesh()
const Matrix3d view_normal_matrix = camera.get_view_matrix().matrix().block(0, 0, 3, 3); const Matrix3d view_normal_matrix = camera.get_view_matrix().matrix().block(0, 0, 3, 3);
shader->set_uniform("view_normal_matrix", view_normal_matrix); shader->set_uniform("view_normal_matrix", view_normal_matrix);
shader->set_uniform("volume_mirrored", trafo_matrix.matrix().determinant() < 0.0); shader->set_uniform("volume_mirrored", trafo_matrix.matrix().determinant() < 0.0);
shader->set_uniform("tex_anchor", Vec3f(trafo_matrix.translation().cast<float>())); // see the bump shader shader->set_uniform("tex_anchor", Vec3f(trafo_matrix.translation().cast<float>())); // see the preview shader
shader->set_uniform("mode", m_uv_check_mode == UVCheckMode::Distortion ? 1 : 0); shader->set_uniform("mode", m_uv_check_mode == UVCheckMode::Distortion ? 1 : 0);
shader->set_uniform("checker_freq", 4.f); // squares per texture tile shader->set_uniform("checker_freq", 4.f); // squares per texture tile
shader->set_uniform("tiling_scale", layer->tiling_scale); shader->set_uniform("tiling_scale", layer->tiling_scale);
@@ -1855,7 +1854,7 @@ void GLGizmoTextureDisplacement::rebuild_wireframe_overlay()
if (its.indices.empty()) if (its.indices.empty())
return; return;
// Building from the base mesh (bump/paint mode); its topology only changes on bake/subdivide, and // Building from the base mesh (shaded/paint mode); its topology only changes on bake/subdivide, and
// this runs on every rebuild_preview(), so rebuild only when the vertex count actually changes. // this runs on every rebuild_preview(), so rebuild only when the vertex count actually changes.
if (m_wireframe_overlay_glmodel.is_initialized() && m_wireframe_overlay_vcount == its.vertices.size()) if (m_wireframe_overlay_glmodel.is_initialized() && m_wireframe_overlay_vcount == its.vertices.size())
return; return;
@@ -1871,9 +1870,9 @@ void GLGizmoTextureDisplacement::refresh_wireframe()
} }
// The wireframe has to sit on whatever mesh is actually on screen. In the true-displacement view // The wireframe has to sit on whatever mesh is actually on screen. In the true-displacement view
// that is the raised preview geometry (m_preview_its) - drawing the flat base mesh's edges there // that is the raised preview geometry (m_preview_its) - drawing the flat base mesh's edges there
// leaves them buried inside the bumps, which is why the wireframe "didn't show in real mode". In // leaves them buried inside the relief, which is why the wireframe "didn't show in real mode". In
// Fast (bump) mode or with nothing painted, the surface is the undisplaced base mesh. // Fast (shaded) mode or with nothing painted, the surface is the undisplaced base mesh.
if (!m_use_bump_preview && !m_preview_its.indices.empty()) if (!m_use_shaded_preview && !m_preview_its.indices.empty())
build_wireframe_from_its(m_preview_its); build_wireframe_from_its(m_preview_its);
else else
rebuild_wireframe_overlay(); rebuild_wireframe_overlay();
@@ -1907,13 +1906,13 @@ void GLGizmoTextureDisplacement::render_wireframe_overlay()
void GLGizmoTextureDisplacement::rebuild_preview() void GLGizmoTextureDisplacement::rebuild_preview()
{ {
// Bumped first: any in-flight job's result (captured generation from before this call) will // Raised first: any in-flight job's result (captured generation from before this call) will
// now compare unequal to m_preview_generation and be discarded when it completes, even if it // now compare unequal to m_preview_generation and be discarded when it completes, even if it
// finishes after the job queued below - and, since the counter is shared with the worker, that // finishes after the job queued below - and, since the counter is shared with the worker, that
// job also notices mid-run and aborts rather than computing a result nobody will use. // job also notices mid-run and aborts rather than computing a result nobody will use.
m_preview_generation->fetch_add(1); m_preview_generation->fetch_add(1);
update_uv_editor(); update_uv_editor();
rebuild_bump_preview_mesh(); rebuild_shaded_preview_mesh();
rebuild_paint_overlay(); rebuild_paint_overlay();
rebuild_uvcheck_mesh(); rebuild_uvcheck_mesh();
rebuild_seam_overlay(); rebuild_seam_overlay();
@@ -1939,7 +1938,7 @@ void GLGizmoTextureDisplacement::rebuild_preview()
} }
// In Fast/paint modes the wireframe follows the base mesh and can be built now; the true-displacement // In Fast/paint modes the wireframe follows the base mesh and can be built now; the true-displacement
// view's wireframe needs the displaced mesh, which only exists once the job below completes. // view's wireframe needs the displaced mesh, which only exists once the job below completes.
if (m_use_bump_preview) { if (m_use_shaded_preview) {
refresh_wireframe(); refresh_wireframe();
// Fast view: the shader *is* the preview, and m_preview_glmodel is never drawn. Running the // Fast view: the shader *is* the preview, and m_preview_glmodel is never drawn. Running the
// full CPU displacement anyway - which is what happened on every stroke and slider release - // full CPU displacement anyway - which is what happened on every stroke and slider release -
@@ -2040,7 +2039,7 @@ void GLGizmoTextureDisplacement::queue_preview_job()
} }
if (m_preview_job_pending) { if (m_preview_job_pending) {
m_preview_job_pending = false; m_preview_job_pending = false;
if (!m_use_bump_preview) if (!m_use_shaded_preview)
queue_preview_job(); // no-ops if the gizmo has closed in the meantime queue_preview_job(); // no-ops if the gizmo has closed in the meantime
} }
m_parent.set_as_dirty(); m_parent.set_as_dirty();
@@ -2310,12 +2309,12 @@ void GLGizmoTextureDisplacement::process_uv_commands()
void GLGizmoTextureDisplacement::apply_view_mode(int mode) void GLGizmoTextureDisplacement::apply_view_mode(int mode)
{ {
m_use_bump_preview = (mode == 1); m_use_shaded_preview = (mode == 1);
m_uv_check_mode = (mode == 2) ? UVCheckMode::Checker : m_uv_check_mode = (mode == 2) ? UVCheckMode::Checker :
(mode == 3) ? UVCheckMode::Distortion : UVCheckMode::None; (mode == 3) ? UVCheckMode::Distortion : UVCheckMode::None;
rebuild_uvcheck_mesh(); rebuild_uvcheck_mesh();
if (m_use_bump_preview) if (m_use_shaded_preview)
rebuild_bump_preview_mesh(); rebuild_shaded_preview_mesh();
else else
// The Fast view skips the CPU displacement entirely (see rebuild_preview()), so leaving it means // The Fast view skips the CPU displacement entirely (see rebuild_preview()), so leaving it means
// m_preview_glmodel may be stale or absent - ask for it now. // m_preview_glmodel may be stale or absent - ask for it now.
@@ -2426,7 +2425,7 @@ void GLGizmoTextureDisplacement::run_uv_command(int cmd, float value)
if (cmd == int(Command::SetBackground)) { if (cmd == int(Command::SetBackground)) {
// Height goes back to whichever of Normal / Fast was showing; Checker and Distortion are views of their own. // Height goes back to whichever of Normal / Fast was showing; Checker and Distortion are views of their own.
const int background = std::clamp(int(std::lround(value)), 0, 2); const int background = std::clamp(int(std::lround(value)), 0, 2);
apply_view_mode(background == 1 ? 2 : background == 2 ? 3 : (m_use_bump_preview ? 1 : 0)); apply_view_mode(background == 1 ? 2 : background == 2 ? 3 : (m_use_shaded_preview ? 1 : 0));
return; return;
} }
@@ -2838,10 +2837,10 @@ void GLGizmoTextureDisplacement::on_island_edited(int island, const Vec2f &offse
m_island_move_set = is_move ? build_island_move_set(*layer, island) : std::vector<int>{ island }; m_island_move_set = is_move ? build_island_move_set(*layer, island) : std::vector<int>{ island };
// Set up the GPU drag: bake the mesh once (via the dirty flag), which is also what flags the // Set up the GPU drag: bake the mesh once (via the dirty flag), which is also what flags the
// moved islands' triangles. From then on the drag is a uniform update, no rebuild - see // moved islands' triangles. From then on the drag is a uniform update, no rebuild - see
// render_bump_preview_mesh(). // render_shaded_preview_mesh().
m_bump_active_chart = island; m_shaded_active_chart = island;
m_bump_island_delta = Eigen::Matrix<float, 2, 3>::Identity(); m_shaded_island_delta = Eigen::Matrix<float, 2, 3>::Identity();
m_bump_preview_dirty = true; m_shaded_preview_dirty = true;
} }
// Apply the edit. A move goes to every island in the moved set (same offset -> they translate as // Apply the edit. A move goes to every island in the moved set (same offset -> they translate as
@@ -2865,10 +2864,10 @@ void GLGizmoTextureDisplacement::on_island_edited(int island, const Vec2f &offse
if (finished) { if (finished) {
m_island_drag_active = false; m_island_drag_active = false;
m_bump_active_chart = -1; m_shaded_active_chart = -1;
m_bump_active_face.clear(); m_shaded_active_face.clear();
m_island_move_set.clear(); m_island_move_set.clear();
m_bump_island_delta = Eigen::Matrix<float, 2, 3>::Identity(); m_shaded_island_delta = Eigen::Matrix<float, 2, 3>::Identity();
rebuild_preview(); // the real displaced geometry moved: recompute it once, at the end rebuild_preview(); // the real displaced geometry moved: recompute it once, at the end
} else { } else {
const std::vector<Eigen::Matrix<float, 2, 3>> xf = uv_editor_island_transforms(*layer); const std::vector<Eigen::Matrix<float, 2, 3>> xf = uv_editor_island_transforms(*layer);
@@ -2879,15 +2878,15 @@ void GLGizmoTextureDisplacement::on_island_edited(int island, const Vec2f &offse
uv_canvas->set_island_transforms(xf); uv_canvas->set_island_transforms(xf);
} }
// Move the island on the model live through the shader's island_delta uniform - no mesh // Move the island on the model live through the shader's island_delta uniform - no mesh
// rebuild. delta = F_current * F_baked^-1 in final-uv space (the bump mesh bakes F_baked; the // rebuild. delta = F_current * F_baked^-1 in final-uv space.
// shader applies delta to the flagged island's uv). The one rebuild that bakes the flags is // The one rebuild that bakes the flags is
// scheduled at drag start above and consumed once per frame by render_painter_gizmo(). // scheduled at drag start above and consumed once per frame by render_painter_gizmo().
if (m_use_bump_preview && m_bump_active_chart == island && size_t(island) < xf.size()) { if (m_use_shaded_preview && m_shaded_active_chart == island && size_t(island) < xf.size()) {
Eigen::Matrix3f cur = Eigen::Matrix3f::Identity(); Eigen::Matrix3f cur = Eigen::Matrix3f::Identity();
cur.topRows<2>() = xf[size_t(island)]; cur.topRows<2>() = xf[size_t(island)];
Eigen::Matrix3f bak = Eigen::Matrix3f::Identity(); Eigen::Matrix3f bak = Eigen::Matrix3f::Identity();
bak.topRows<2>() = m_bump_baked_active_xf; bak.topRows<2>() = m_shaded_baked_active_xf;
m_bump_island_delta = (cur * bak.inverse()).topRows<2>(); m_shaded_island_delta = (cur * bak.inverse()).topRows<2>();
m_parent.set_as_dirty(); m_parent.set_as_dirty();
} }
} }
@@ -3260,12 +3259,12 @@ void GLGizmoTextureDisplacement::update_model_object()
updated |= facet.set(*m_triangle_selectors[idx]); updated |= facet.set(*m_triangle_selectors[idx]);
} }
// The fast (bump) preview reads the live selector, so it has to be rebuilt after any stroke that // The fast (shaded) preview reads the live selector, so it has to be rebuilt after any stroke that
// flushes here - not only when set() reports a change. Rebuilding it via rebuild_preview() below // flushes here - not only when set() reports a change. Rebuilding it via rebuild_preview() below
// is gated on `updated`, which misses e.g. the first paint into a slot; marking it dirty makes the // is gated on `updated`, which misses e.g. the first paint into a slot; marking it dirty makes the
// render loop (render_painter_gizmo) rebuild it next frame regardless. Without this, fast preview - // render loop (render_painter_gizmo) rebuild it next frame regardless. Without this, fast preview -
// now the default view - stayed blank until a full reload (select-whole-model / reopen). // now the default view - stayed blank until a full reload (select-whole-model / reopen).
m_bump_preview_dirty = true; m_shaded_preview_dirty = true;
if (updated) { if (updated) {
const ModelObjectPtrs &mos = wxGetApp().model().objects; const ModelObjectPtrs &mos = wxGetApp().model().objects;
@@ -3337,7 +3336,7 @@ void GLGizmoTextureDisplacement::set_active_layer(int slot)
// instead of leaving it pointing at the previous layer's (now stale) paint patch. // instead of leaving it pointing at the previous layer's (now stale) paint patch.
if (m_adjust_texture_mode) if (m_adjust_texture_mode)
update_adjust_anchor(); update_adjust_anchor();
// Refresh every preview/overlay (bump, UV editor, seams, ...) for the newly active layer. // Refresh every preview/overlay (shaded, UV editor, seams, ...) for the newly active layer.
rebuild_preview(); rebuild_preview();
} }
@@ -4166,7 +4165,7 @@ TextureColorSettings GLGizmoTextureDisplacement::color_settings_for(const ModelV
const std::vector<GLGizmoTextureDisplacement::PaletteEntry> &GLGizmoTextureDisplacement::cached_palette() const std::vector<GLGizmoTextureDisplacement::PaletteEntry> &GLGizmoTextureDisplacement::cached_palette()
{ {
// Rebuilt only when the loaded filaments or the mixing setting actually change. The bump preview // Rebuilt only when the loaded filaments or the mixing setting actually change. The shaded preview
// rebuilds on every paint stroke and the subdivide preview on every slider frame, and filling the // rebuilds on every paint stroke and the subdivide preview on every slider frame, and filling the
// quantizer's lookup cube for a 64-entry palette is tens of milliseconds - paying that per stroke // quantizer's lookup cube for a 64-entry palette is tens of milliseconds - paying that per stroke
// is the difference between painting that keeps up and painting that stutters. // is the difference between painting that keeps up and painting that stutters.
@@ -4795,7 +4794,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_thumbnail(const TextureDisplace
// Reuses the already-decoded, already-cached grayscale pixels (see decode_height_texture()'s // Reuses the already-decoded, already-cached grayscale pixels (see decode_height_texture()'s
// own cache in TextureDisplacement.cpp) - only the gray-to-RGBA expansion and GPU upload below are // own cache in TextureDisplacement.cpp) - only the gray-to-RGBA expansion and GPU upload below are
// new work. Rebuilt when the texture *or the smoothing* changes, so the fast/bump preview - which // new work. Rebuilt when the texture *or the smoothing* changes, so the fast/shaded preview - which
// samples this GPU texture directly - reflects the current smoothing rather than the raw image. // samples this GPU texture directly - reflects the current smoothing rather than the raw image.
std::unique_ptr<GLTexture> texture = upload_height_thumbnail(decode_height_texture(layer)); std::unique_ptr<GLTexture> texture = upload_height_thumbnail(decode_height_texture(layer));
if (!texture) if (!texture)
@@ -4831,7 +4830,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_height_texture(const TextureDis
if (layer.empty()) if (layer.empty())
return nullptr; return nullptr;
// One slot, not one per layer: the bump shader only ever shades the *active* layer, so a single // One slot, not one per layer: the preview shader only ever shades the *active* layer, so a single
// full-resolution upload is enough and the VRAM cost stays at one texture rather than eight. // full-resolution upload is enough and the VRAM cost stays at one texture rather than eight.
if (m_height_tex && m_height_tex_source == layer.image_data.get() && m_height_tex_smoothing == layer.smoothing) if (m_height_tex && m_height_tex_source == layer.image_data.get() && m_height_tex_smoothing == layer.smoothing)
return m_height_tex.get(); return m_height_tex.get();
@@ -5033,7 +5032,7 @@ void GLGizmoTextureDisplacement::show_debug_stage(int index)
m_debug_stage = index; m_debug_stage = index;
// Drawn through the ordinary true-displacement preview, so the Fast view - a shader trick over // Drawn through the ordinary true-displacement preview, so the Fast view - a shader trick over
// the base mesh that never draws m_preview_glmodel - has to be left first. // the base mesh that never draws m_preview_glmodel - has to be left first.
m_use_bump_preview = false; m_use_shaded_preview = false;
m_preview_color_runs.clear(); m_preview_color_runs.clear();
m_preview_glmodel.reset(); m_preview_glmodel.reset();
@@ -5667,9 +5666,9 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
} }
// ---- View: Normal / Fast / Checker / Distortion as one group, Wireframe on its own ---- // ---- View: Normal / Fast / Checker / Distortion as one group, Wireframe on its own ----
// The underlying state stays m_use_bump_preview + m_uv_check_mode. // The underlying state stays m_use_shaded_preview + m_uv_check_mode.
{ {
const int cur_mode = m_use_bump_preview ? 1 : const int cur_mode = m_use_shaded_preview ? 1 :
m_uv_check_mode == UVCheckMode::Checker ? 2 : m_uv_check_mode == UVCheckMode::Checker ? 2 :
m_uv_check_mode == UVCheckMode::Distortion ? 3 : 0; m_uv_check_mode == UVCheckMode::Distortion ? 3 : 0;
int new_mode = cur_mode; int new_mode = cur_mode;
@@ -5692,7 +5691,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
new_mode = 0; new_mode = 0;
ImGui::SameLine(0.f, gap_s); ImGui::SameLine(0.f, gap_s);
if (icon_toggle(702, "texture_displacement_fast_preview.svg", cur_mode == 1, icon_md, _L("Fast"), if (icon_toggle(702, "texture_displacement_fast_preview.svg", cur_mode == 1, icon_md, _L("Fast"),
_L("Fast - a bump-shaded approximation of the active layer only; quick to update, not exact"))) _L("Fast - a shaded approximation of the active layer only; quick to update, not exact")))
new_mode = 1; new_mode = 1;
ImGui::SameLine(0.f, gap_s); ImGui::SameLine(0.f, gap_s);
if (icon_toggle(703, "texture_displacement_checker.svg", cur_mode == 2, icon_md, _L("Checker"), if (icon_toggle(703, "texture_displacement_checker.svg", cur_mode == 2, icon_md, _L("Checker"),
@@ -6443,7 +6442,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
if (m_subdivide_feature) { if (m_subdivide_feature) {
if (float_row("##subdiv_detail", _L("Detail"), &m_subdivide_detail_mm, 0.001f, 1.f, "%.3f mm", true, 0.f)) if (float_row("##subdiv_detail", _L("Detail"), &m_subdivide_detail_mm, 0.001f, 1.f, "%.3f mm", true, 0.f))
preview_live(); preview_live();
hover_tip(_u8L("How closely the mesh follows the texture's relief. Smaller captures finer bumps; " hover_tip(_u8L("How closely the mesh follows the texture's relief. Smaller captures finer detail; "
"larger only chases the big features.")); "larger only chases the big features."));
if (float_row("##subdiv_min", _L("Min edge"), &m_subdivide_min_edge_mm, 0.001f, 20.f, "%.3f mm", true, 0.f)) if (float_row("##subdiv_min", _L("Min edge"), &m_subdivide_min_edge_mm, 0.001f, 20.f, "%.3f mm", true, 0.f))
preview_live(); preview_live();
@@ -6528,7 +6527,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
_u8L("Refines the painted area to the target edge length and carries your paint onto " _u8L("Refines the painted area to the target edge length and carries your paint onto "
"the finer mesh. The rest of the model is left as it is.") : "the finer mesh. The rest of the model is left as it is.") :
_u8L("Replaces the model's geometry with the subdivided mesh and clears any not-yet-baked " _u8L("Replaces the model's geometry with the subdivided mesh and clears any not-yet-baked "
"paint on it (already-baked bumps are unaffected).")); "paint on it (already-baked relief is unaffected)."));
} }
// Remesh: even out uneven triangle sizes (CGAL isotropic remeshing). // Remesh: even out uneven triangle sizes (CGAL isotropic remeshing).
@@ -6569,7 +6568,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
hover_tip(_u8L("Rebuilds the whole model with triangles close to this edge length - splitting the big " hover_tip(_u8L("Rebuilds the whole model with triangles close to this edge length - splitting the big "
"ones and merging the small ones - so displacement has an even density to work with. " "ones and merging the small ones - so displacement has an even density to work with. "
"Replaces the geometry; your paint is carried onto the new triangles spatially, so it " "Replaces the geometry; your paint is carried onto the new triangles spatially, so it "
"survives (already-baked bumps are kept too).")); "survives (already-baked relief is kept too)."));
// Settings for the whole stack rather than one layer. Standard mode pins them instead of showing them. // Settings for the whole stack rather than one layer. Standard mode pins them instead of showing them.
if (mv != nullptr) { if (mv != nullptr) {
@@ -6751,7 +6750,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
} }
m_imgui->disabled_end(); m_imgui->disabled_end();
hover_tip(_u8L("Auto: the resolution follows the model's size and the budget is the standard " hover_tip(_u8L("Auto: the resolution follows the model's size and the budget is the standard "
"750 k, the same defaults as bumpmesh.com. Untick to set them by hand.")); "750 k. Untick to set them by hand."));
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth(x0 + panel_w - ImGui::GetCursorPosX()); ImGui::SetNextItemWidth(x0 + panel_w - ImGui::GetCursorPosX());
float shown = auto_res ? rec.edge_mm : opts.v2_refine_mm; float shown = auto_res ? rec.edge_mm : opts.v2_refine_mm;
@@ -6764,7 +6763,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
} }
m_imgui->disabled_end(); m_imgui->disabled_end();
if (auto_res && rec.edge_mm > 0.f) if (auto_res && rec.edge_mm > 0.f)
hover_tip(Slic3r::format(_u8L("The model's diagonal / 250, as bumpmesh.com sets it. Budget %1% k."), rec.budget_k)); hover_tip(Slic3r::format(_u8L("The model's diagonal / 250. Budget %1% k."), rec.budget_k));
else else
hover_tip(_u8L("Triangle edge length the painted area is refined to before displacement. " hover_tip(_u8L("Triangle edge length the painted area is refined to before displacement. "
"Smaller carries finer texture detail and costs more triangles; the budget " "Smaller carries finer texture detail and costs more triangles; the budget "
@@ -303,7 +303,7 @@ private:
GLTexture *get_layer_thumbnail(const TextureDisplacementLayer &layer); GLTexture *get_layer_thumbnail(const TextureDisplacementLayer &layer);
// The same texture at full resolution, for the fast-preview shader. One slot, shared by whichever // The same texture at full resolution, for the fast-preview shader. One slot, shared by whichever
// layer is active, because that is the only one the bump shader ever shades. // layer is active, because that is the only one the preview shader ever shades.
GLTexture *get_layer_height_texture(const TextureDisplacementLayer &layer); GLTexture *get_layer_height_texture(const TextureDisplacementLayer &layer);
// The layer's colour texture for the fast preview's per-fragment quantization. Null when the // The layer's colour texture for the fast preview's per-fragment quantization. Null when the
// layer is not colouring or its texture is grayscale. // layer is not colouring or its texture is grayscale.
@@ -389,13 +389,13 @@ private:
void render_preview_mesh(); void render_preview_mesh();
// Alternate, GPU-only preview: perturbs shading normals from the active layer's height texture // Alternate, GPU-only preview: perturbs shading normals from the active layer's height texture
// (a classic bump map) instead of actually moving vertices, using the // instead of actually moving vertices, using the resources/shaders/*/texture_displacement_shaded.*
// resources/shaders/*/texture_displacement_bump.* shader. Faster than the true-displacement // shader. Faster than the true-displacement preview (no CPU meshing at all - just a per-vertex
// preview (no CPU meshing at all - just a per-vertex paint-weight buffer built at the same // paint-weight buffer built at the same cadence as rebuild_preview()) but only shows the *active*
// cadence as rebuild_preview()) but only shows the *active* layer, and any bump is a shading // layer, and the relief it shows is a shading illusion, not real geometry - "Bake" always produces
// illusion, not real geometry - "Bake" always produces the true, exact result either way. // the true, exact result either way.
void rebuild_bump_preview_mesh(); void rebuild_shaded_preview_mesh();
void render_bump_preview_mesh(); void render_shaded_preview_mesh();
// Feeds the active layer's painted patch + LSCM unwrap (if it's using that projection method) // Feeds the active layer's painted patch + LSCM unwrap (if it's using that projection method)
// into Plater's docked UV-editor pane and shows it, or hides the pane if the active layer // into Plater's docked UV-editor pane and shows it, or hides the pane if the active layer
@@ -636,16 +636,16 @@ private:
// mouse button driving the drag hasn't been released yet - see on_render_input_window(). // mouse button driving the drag hasn't been released yet - see on_render_input_window().
bool m_preview_params_dirty = false; bool m_preview_params_dirty = false;
// See rebuild_bump_preview_mesh()/render_bump_preview_mesh(). On by default: it is the cheap, // See rebuild_shaded_preview_mesh()/render_shaded_preview_mesh(). On by default: it is the cheap,
// instant-updating preview, so it is the better first impression while painting. The true- // instant-updating preview, so it is the better first impression while painting. The true-
// displacement view (a background CPU remesh) is one click away in the View row when the user // displacement view (a background CPU remesh) is one click away in the View row when the user
// wants an exact look at what Bake will produce. // wants an exact look at what Bake will produce.
bool m_use_bump_preview = true; bool m_use_shaded_preview = true;
// Set from the UV editor's per-move island edits instead of rebuilding the (potentially large) bump // Set from the UV editor's per-move island edits instead of rebuilding the (potentially large) shaded
// mesh synchronously inside that mouse handler - doing the rebuild there stalled both the UV pane // mesh synchronously inside that mouse handler - doing the rebuild there stalled both the UV pane
// and the 3D view. The rebuild is instead coalesced to once per 3D frame (render_painter_gizmo). // and the 3D view. The rebuild is instead coalesced to once per 3D frame (render_painter_gizmo).
bool m_bump_preview_dirty = false; bool m_shaded_preview_dirty = false;
GLModel m_bump_preview_glmodel; GLModel m_shaded_preview_glmodel;
// Translucent tint over the active layer's painted triangles, drawn on top of whichever preview // Translucent tint over the active layer's painted triangles, drawn on top of whichever preview
// is showing. The base painter's own opaque paint highlight (render_triangles()) cannot be used // is showing. The base painter's own opaque paint highlight (render_triangles()) cannot be used
@@ -662,7 +662,7 @@ private:
void rebuild_island_overlay(const std::vector<int> &selection); void rebuild_island_overlay(const std::vector<int> &selection);
void render_island_overlay(); void render_island_overlay();
// Set on every paint event, cleared when the overlay is rebuilt in render_painter_gizmo(). Kept // Set on every paint event, cleared when the overlay is rebuilt in render_painter_gizmo(). Kept
// separate from m_bump_preview_dirty so a stroke refreshes only the small painted patch per frame, // separate from m_shaded_preview_dirty so a stroke refreshes only the small painted patch per frame,
bool m_paint_overlay_dirty = false; bool m_paint_overlay_dirty = false;
void rebuild_paint_overlay(); void rebuild_paint_overlay();
void render_paint_overlay(GLModel &overlay); void render_paint_overlay(GLModel &overlay);
@@ -671,33 +671,33 @@ private:
GLModel m_other_paint_glmodel; GLModel m_other_paint_glmodel;
std::string m_other_paint_key; std::string m_other_paint_key;
void rebuild_other_paint_overlay(); void rebuild_other_paint_overlay();
// Whether render_bump_preview_mesh() would actually draw something. Checked before the real volume // Whether render_shaded_preview_mesh() would actually draw something. Checked before the real volume
// is hidden: with no layer, no texture or no shader the bump path draws nothing, and hiding the // is hidden: with no layer, no texture or no shader the shaded path draws nothing, and hiding the
// volume for it left the model invisible. // volume for it left the model invisible.
bool bump_preview_ready() const; bool shaded_preview_ready() const;
// Whether the current bump mesh carries a precomputed per-vertex uv (LSCM) that the shader // Whether the current displacement mesh carries a precomputed per-vertex uv (LSCM) that the shader
// should sample at directly, rather than projecting in-shader. Set by rebuild_bump_preview_mesh(). // should sample at directly, rather than projecting in-shader. Set by rebuild_shaded_preview_mesh().
bool m_bump_preview_uses_vertex_uv = false; bool m_shaded_preview_uses_vertex_uv = false;
// The projection frame handed to the bump shader, captured when the mesh is built. Cylindrical and // The projection frame handed to the preview shader, captured when the mesh is built. Cylindrical and
// Spherical are reconstructed in the fragment shader (there is no per-vertex uv for them) and wrap // Spherical are reconstructed in the fragment shader (there is no per-vertex uv for them) and wrap
// around the whole patch, which no fragment can work out for itself. See layer_projection_frame(). // around the whole patch, which no fragment can work out for itself. See layer_projection_frame().
int m_bump_projection_mode = 0; int m_shaded_projection_mode = 0;
Vec3f m_bump_patch_center = Vec3f::Zero(); Vec3f m_shaded_patch_center = Vec3f::Zero();
Vec3f m_bump_patch_axis = Vec3f::UnitZ(); Vec3f m_shaded_patch_axis = Vec3f::UnitZ();
// The palette the fast preview's per-triangle filament indices were built against, captured when // The palette the fast preview's per-triangle filament indices were built against, captured when
// the mesh was. Empty when the active layer is not colouring, which is what tells the shader to // the mesh was. Empty when the active layer is not colouring, which is what tells the shader to
// fall back to the model's own colour. Held rather than re-read at draw time so the indices baked // fall back to the model's own colour. Held rather than re-read at draw time so the indices baked
// into the mesh can never be resolved against a different set of filaments than they were computed // into the mesh can never be resolved against a different set of filaments than they were computed
// from - loading a filament mid-session would otherwise recolour a stale preview at random. // from - loading a filament mid-session would otherwise recolour a stale preview at random.
std::vector<PaletteEntry> m_bump_preview_palette; std::vector<PaletteEntry> m_shaded_preview_palette;
// GPU island drag: while an island is dragged in the UV editor, the bump mesh is baked once (with // GPU island drag: while an island is dragged in the UV editor, the displacement mesh is baked once (with
// the dragged island's vertices flagged, v_normal.y = 1) and then moved purely through the shader's // the dragged island's vertices flagged, v_normal.y = 1) and then moved purely through the shader's
// island_delta uniform - one uniform update per mouse move, no rebuild - so it tracks the cursor // island_delta uniform - one uniform update per mouse move, no rebuild - so it tracks the cursor
// as smoothly as Adjust placement. m_bump_active_chart is the dragged island (or -1); // as smoothly as Adjust placement. m_shaded_active_chart is the dragged island (or -1);
// m_bump_active_face flags the dragged islands' *triangles*, indexed by painted-patch face; // m_shaded_active_face flags the dragged islands' *triangles*, indexed by painted-patch face;
// m_bump_baked_active_xf is that island's placement baked into the current mesh, against which the // m_shaded_baked_active_xf is that island's placement baked into the current mesh, against which the
// live delta is measured; m_bump_island_delta is the resulting final-uv-space affine handed to the // live delta is measured; m_shaded_island_delta is the resulting final-uv-space affine handed to the
// shader (identity except mid-drag). // shader (identity except mid-drag).
// //
// Per triangle rather than per vertex deliberately: a seam vertex belongs to every chart touching // Per triangle rather than per vertex deliberately: a seam vertex belongs to every chart touching
@@ -705,13 +705,13 @@ private:
// island_active is an interpolated varying, so those neighbouring triangles then had island_delta // island_active is an interpolated varying, so those neighbouring triangles then had island_delta
// applied too - dragging one island moved every adjacent island's texture while the editor, which // applied too - dragging one island moved every adjacent island's texture while the editor, which
// is per chart, correctly moved only the one. A triangle belongs to exactly one chart. // is per chart, correctly moved only the one. A triangle belongs to exactly one chart.
int m_bump_active_chart = -1; int m_shaded_active_chart = -1;
std::vector<uint8_t> m_bump_active_face; std::vector<uint8_t> m_shaded_active_face;
Eigen::Matrix<float, 2, 3> m_bump_baked_active_xf = Eigen::Matrix<float, 2, 3>::Identity(); Eigen::Matrix<float, 2, 3> m_shaded_baked_active_xf = Eigen::Matrix<float, 2, 3>::Identity();
Eigen::Matrix<float, 2, 3> m_bump_island_delta = Eigen::Matrix<float, 2, 3>::Identity(); Eigen::Matrix<float, 2, 3> m_shaded_island_delta = Eigen::Matrix<float, 2, 3>::Identity();
// Flags `charts`' triangles in m_bump_active_face, sized to `patch_face_count` (the painted patch // Flags `charts`' triangles in m_shaded_active_face, sized to `patch_face_count` (the painted patch
// the bump mesh is being built from). Cleared if the unwrap carries no face map. // the displacement mesh is being built from). Cleared if the unwrap carries no face map.
void compute_bump_active_faces(const std::vector<int> &charts, size_t patch_face_count); void compute_shaded_active_faces(const std::vector<int> &charts, size_t patch_face_count);
// The set of islands the current UV-editor drag moves together: the pane's multi-selection unioned // The set of islands the current UV-editor drag moves together: the pane's multi-selection unioned
// with each selected island's join group (see build_island_move_set()). Populated at drag start and // with each selected island's join group (see build_island_move_set()). Populated at drag start and
@@ -729,7 +729,7 @@ private:
// Final per-vertex texture uv for the projections the shader can't reconstruct itself - LSCM (an // Final per-vertex texture uv for the projections the shader can't reconstruct itself - LSCM (an
// unwrap) and ViewProjected (a projector plane the shader doesn't know). One entry per patch/base // unwrap) and ViewProjected (a projector plane the shader doesn't know). One entry per patch/base
// vertex, already through apply_uv_transform(). Empty for Triplanar/Cylindrical/Spherical, which // vertex, already through apply_uv_transform(). Empty for Triplanar/Cylindrical/Spherical, which
// the shader projects on its own. Shared by the bump preview and the UV-check overlay. // the shader projects on its own. Shared by the shaded preview and the UV-check overlay.
std::vector<Vec2f> compute_layer_vertex_uvs(const indexed_triangle_set &patch, std::vector<Vec2f> compute_layer_vertex_uvs(const indexed_triangle_set &patch,
const TextureDisplacementLayer &layer) const; const TextureDisplacementLayer &layer) const;
// The same, but three UVs per patch triangle (corner 0..2 of triangle i at 3i..3i+2). This is what // The same, but three UVs per patch triangle (corner 0..2 of triangle i at 3i..3i+2). This is what
@@ -757,7 +757,7 @@ private:
UVCheckMode m_uv_check_mode = UVCheckMode::None; UVCheckMode m_uv_check_mode = UVCheckMode::None;
GLModel m_uvcheck_glmodel; GLModel m_uvcheck_glmodel;
bool m_uvcheck_uses_vertex_uv = false; bool m_uvcheck_uses_vertex_uv = false;
// As m_bump_projection_mode and friends, for the Checker overlay. // As m_shaded_projection_mode and friends, for the Checker overlay.
int m_uvcheck_projection_mode = 0; int m_uvcheck_projection_mode = 0;
Vec3f m_uvcheck_patch_center = Vec3f::Zero(); Vec3f m_uvcheck_patch_center = Vec3f::Zero();
Vec3f m_uvcheck_patch_axis = Vec3f::UnitZ(); Vec3f m_uvcheck_patch_axis = Vec3f::UnitZ();
@@ -798,14 +798,14 @@ private:
bool m_wireframe_overlay = false; bool m_wireframe_overlay = false;
GLModel m_wireframe_overlay_glmodel; GLModel m_wireframe_overlay_glmodel;
size_t m_wireframe_overlay_vcount = 0; // topology signature, so it rebuilds only on a real change size_t m_wireframe_overlay_vcount = 0; // topology signature, so it rebuilds only on a real change
void rebuild_wireframe_overlay(); // from the base mesh (bump/paint mode) void rebuild_wireframe_overlay(); // from the base mesh (shaded/paint mode)
void build_wireframe_from_its(const indexed_triangle_set &its); // from an explicit mesh, no early-out void build_wireframe_from_its(const indexed_triangle_set &its); // from an explicit mesh, no early-out
void refresh_wireframe(); // pick base vs displaced source for the current view void refresh_wireframe(); // pick base vs displaced source for the current view
void render_wireframe_overlay(); void render_wireframe_overlay();
// The displaced preview geometry the last preview job produced, kept so the wireframe overlay can be // The displaced preview geometry the last preview job produced, kept so the wireframe overlay can be
// drawn on the raised surface actually shown in the true-displacement view (#: "wireframe in real mode"). // drawn on the raised surface actually shown in the true-displacement view (#: "wireframe in real mode").
indexed_triangle_set m_preview_its; indexed_triangle_set m_preview_its;
// Bumped on every rebuild_preview() call; a background TextureDisplacementPreviewJob's result // Raised on every rebuild_preview() call; a background TextureDisplacementPreviewJob's result
// is only applied if this hasn't moved on since the job was queued (see rebuild_preview()), // is only applied if this hasn't moved on since the job was queued (see rebuild_preview()),
// so a burst of edits can't have an earlier, now-stale job clobber a later one's result. // so a burst of edits can't have an earlier, now-stale job clobber a later one's result.
// //
@@ -831,7 +831,7 @@ private:
// The smoothing each cached thumbnail was built at, so a smoothing change re-uploads it. // The smoothing each cached thumbnail was built at, so a smoothing change re-uploads it.
std::array<float, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_smoothing{}; std::array<float, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_smoothing{};
// Full-resolution height texture for the bump shader, keyed the same way (see // Full-resolution height texture for the preview shader, keyed the same way (see
// get_layer_height_texture()). A smoothing change re-uploads it, so the fast preview shows the // get_layer_height_texture()). A smoothing change re-uploads it, so the fast preview shows the
// blur the bake will apply. // blur the bake will apply.
std::unique_ptr<GLTexture> m_height_tex; std::unique_ptr<GLTexture> m_height_tex;
+10 -10
View File
@@ -694,10 +694,10 @@ TEST_CASE("TextureDisplacement: feature-adaptive subdivision follows curvature,
(its.vertices[t[0]].y() + its.vertices[t[1]].y() + its.vertices[t[2]].y()) / 3.f); (its.vertices[t[0]].y() + its.vertices[t[1]].y() + its.vertices[t[2]].y()) / 3.f);
}; };
SECTION("a sharp bump refines densely at its center and leaves flat corners coarse") SECTION("a sharp hill refines densely at its center and leaves flat corners coarse")
{ {
// A tight Gaussian bump at the sheet's center: strong curvature near (0.5, 0.5), flat far away. // A tight Gaussian hill at the sheet's center: strong curvature near (0.5, 0.5), flat far away.
HeightFieldSampler bump = [](const Vec3f &p, const Vec3f &) { HeightFieldSampler hill = [](const Vec3f &p, const Vec3f &) {
const float r2 = (p.x() - 0.5f) * (p.x() - 0.5f) + (p.y() - 0.5f) * (p.y() - 0.5f); const float r2 = (p.x() - 0.5f) * (p.x() - 0.5f) + (p.y() - 0.5f) * (p.y() - 0.5f);
return 1.0f * std::exp(-r2 / 0.02f); return 1.0f * std::exp(-r2 / 0.02f);
}; };
@@ -706,12 +706,12 @@ TEST_CASE("TextureDisplacement: feature-adaptive subdivision follows curvature,
// here - this isolates the *curvature* contribution (the grid already meets the baseline). // here - this isolates the *curvature* contribution (the grid already meets the baseline).
std::vector<int> source; std::vector<int> source;
const indexed_triangle_set out = const indexed_triangle_set out =
subdivide_mesh_adaptive(grid, region, /*max edge*/ 0.3f, 200000, &source, bump, /*tol*/ 0.02f, subdivide_mesh_adaptive(grid, region, /*max edge*/ 0.3f, 200000, &source, hill, /*tol*/ 0.02f,
/*min_edge*/ 0.01f); /*min_edge*/ 0.01f);
CHECK(out.indices.size() > grid.indices.size()); // the bump forced real refinement CHECK(out.indices.size() > grid.indices.size()); // the hill forced real refinement
// The largest triangle near the bump's center must be much smaller than the largest in a flat // The largest triangle near the hill's center must be much smaller than the largest in a flat
// corner - i.e. triangles went where the curvature is, not spread evenly. // corner - i.e. triangles went where the curvature is, not spread evenly.
float near_max = 0.f, far_max = 0.f; float near_max = 0.f, far_max = 0.f;
for (const auto &t : out.indices) { for (const auto &t : out.indices) {
@@ -1140,9 +1140,9 @@ TEST_CASE("TextureDisplacement: the step cutter turns a stepped field into walls
TEST_CASE("TextureDisplacement: the step cutter passes a smooth field through untouched", "[TextureDisplacement]") TEST_CASE("TextureDisplacement: the step cutter passes a smooth field through untouched", "[TextureDisplacement]")
{ {
// A wide bump: its mid-level contour runs through the sheet, but nowhere is it a step, so there is // A wide hill: its mid-level contour runs through the sheet, but nowhere is it a step, so there is
// nothing to cut - refinement is the right tool for it. // nothing to cut - refinement is the right tool for it.
const HeightFieldSampler bump = [](const Vec3f &p, const Vec3f &) { const HeightFieldSampler hill = [](const Vec3f &p, const Vec3f &) {
const float r2 = (p.x() - 3.f) * (p.x() - 3.f) + (p.y() - 3.f) * (p.y() - 3.f); const float r2 = (p.x() - 3.f) * (p.x() - 3.f) + (p.y() - 3.f) * (p.y() - 3.f);
return 0.4f * std::exp(-r2 / 3.f); return 0.4f * std::exp(-r2 / 3.f);
}; };
@@ -1151,7 +1151,7 @@ TEST_CASE("TextureDisplacement: the step cutter passes a smooth field through un
std::vector<int> source; std::vector<int> source;
size_t cuts = 0; size_t cuts = 0;
const indexed_triangle_set out = cut_mesh_at_steps(sheet, region, bump, 0.05f, 0.075f, 0.f, &source, &cuts); const indexed_triangle_set out = cut_mesh_at_steps(sheet, region, hill, 0.05f, 0.075f, 0.f, &source, &cuts);
CHECK(cuts == 0); CHECK(cuts == 0);
CHECK(out.indices.size() == sheet.indices.size()); CHECK(out.indices.size() == sheet.indices.size());
@@ -1363,7 +1363,7 @@ TEST_CASE("TextureDisplacement: edge flips lay a stepped field's wall along the
// Automatic resolution (v2 pipeline) // Automatic resolution (v2 pipeline)
// --------------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------------
TEST_CASE("TextureDisplacement: automatic resolution follows the model's size like bumpmesh.com", "[TextureDisplacement]") TEST_CASE("TextureDisplacement: automatic resolution follows the model's size", "[TextureDisplacement]")
{ {
// A 20 mm cube: diagonal 34.64 mm, so diagonal / 250 = 0.1386 mm, rounded up to 0.14. // A 20 mm cube: diagonal 34.64 mm, so diagonal / 250 = 0.1386 mm, rounded up to 0.14.
const indexed_triangle_set cube = its_make_cube(20.f, 20.f, 20.f); const indexed_triangle_set cube = its_make_cube(20.f, 20.f, 20.f);