mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-24 01:12:35 +00:00
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:
+11
-11
@@ -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
|
||||
**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*
|
||||
(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
|
||||
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.
|
||||
|
||||
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)
|
||||
|
||||
`resources/shaders/{110,140}/texture_displacement_bump.{vs,fs}`, registered as
|
||||
`"texture_displacement_bump"`. 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`).
|
||||
`resources/shaders/{110,140}/texture_displacement_shaded.{vs,fs}`, registered as
|
||||
`"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_shaded_preview = true`).
|
||||
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
|
||||
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
|
||||
*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
|
||||
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.
|
||||
@@ -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
|
||||
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()` →
|
||||
`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
|
||||
`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).
|
||||
|
||||
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
|
||||
for `tile_enabled`/`tile_method`, so with tiling *off* the GPU repeats where the CPU returns 0 outside
|
||||
`[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/Plater.hpp/.cpp` - `uv_editor_canvas` member, AUI pane registration,
|
||||
`get_uv_editor_canvas()`/`show_uv_editor()`.
|
||||
- `src/slic3r/GUI/GLShadersManager.cpp` - registers `"texture_displacement_bump"`.
|
||||
- `resources/shaders/{110,140}/texture_displacement_bump.{vs,fs}` - the fast-preview shader.
|
||||
- `src/slic3r/GUI/GLShadersManager.cpp` - registers `"texture_displacement_shaded"`.
|
||||
- `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/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
|
||||
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
|
||||
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.
|
||||
|
||||
`BUILD_TESTS` is `OFF` in the checked-in build cache; flip it on to run them:
|
||||
|
||||
@@ -79,7 +79,7 @@ radio group; **Wireframe** is an independent toggle. Hover any icon for its tool
|
||||
| View | Meaning |
|
||||
|------|---------|
|
||||
| **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). |
|
||||
| **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. |
|
||||
@@ -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.
|
||||
- **Inward cuts** (high Midlevel + big Depth) can self-intersect on thin walls or sharp concave
|
||||
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.
|
||||
- **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).
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
#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).
|
||||
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
@@ -309,7 +309,7 @@ void main()
|
||||
bool have_uv = false;
|
||||
|
||||
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
|
||||
// via a multiply so the branch stays uniform (use_vertex_uv is a uniform).
|
||||
vec2 uv = (island_active > 0.5)
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#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.
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
@@ -22,7 +22,7 @@ uniform vec2 uv_offset;
|
||||
uniform bool use_vertex_uv;
|
||||
// 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
|
||||
// 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 vec3 patch_center;
|
||||
uniform vec3 patch_axis;
|
||||
@@ -91,7 +91,7 @@ void main()
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
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 tex_pos = world_pos.xyz - tex_anchor;
|
||||
if (volume_mirrored)
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
#version 140
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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:
|
||||
// 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
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
// 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
|
||||
// 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.
|
||||
@@ -56,7 +56,7 @@
|
||||
// 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
|
||||
// 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
|
||||
|
||||
@@ -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)
|
||||
// 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
|
||||
// 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
|
||||
uniform vec4 island_delta_lin;
|
||||
uniform vec2 island_delta_tr;
|
||||
@@ -377,7 +377,7 @@ void main()
|
||||
bool have_uv = false;
|
||||
|
||||
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
|
||||
// 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,
|
||||
@@ -388,7 +388,7 @@ void main()
|
||||
// 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)
|
||||
// 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.
|
||||
vec2 uv = (island_active > 0.5)
|
||||
? vec2(dot(island_delta_lin.xy, vertex_uv), dot(island_delta_lin.zw, vertex_uv)) + island_delta_tr
|
||||
+1
-1
@@ -12,7 +12,7 @@ uniform vec4 clipping_plane;
|
||||
in vec3 v_position;
|
||||
// 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
|
||||
// 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.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
|
||||
@@ -8,7 +8,7 @@
|
||||
// where the texture actually lands.
|
||||
// mode 1 - Distortion heatmap: the per-vertex area-distortion carried in `distortion`, blue
|
||||
// (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.
|
||||
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
@@ -31,7 +31,7 @@ uniform vec2 uv_offset;
|
||||
uniform bool use_vertex_uv;
|
||||
// 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
|
||||
// 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 vec3 patch_center;
|
||||
uniform vec3 patch_axis;
|
||||
@@ -103,7 +103,7 @@ void main()
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
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 tex_pos = world_pos.xyz - tex_anchor;
|
||||
if (volume_mirrored)
|
||||
|
||||
@@ -134,7 +134,7 @@ struct DecodedTextureCache
|
||||
};
|
||||
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
|
||||
// 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
|
||||
@@ -428,10 +428,10 @@ V2Resolution recommend_v2_resolution(const indexed_triangle_set
|
||||
const std::vector<TextureDisplacementLayer> &layers,
|
||||
const Transform3d &volume_to_world)
|
||||
{
|
||||
// bumpmesh.com's defaults on model load: edge = diagonal / 250 in [0.05, 5] mm, budget 750 k. A
|
||||
// texture-driven variant (BumpMesh's smart resolution) 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
|
||||
// defaults. The texel size and sharpness are still reported for the panel.
|
||||
// The defaults on model load: edge = diagonal / 250 in [0.05, 5] mm, budget 750 k. A texture-driven
|
||||
// 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, and was not worth that; these
|
||||
// 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 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
|
||||
// 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
|
||||
// children are fresh entries.
|
||||
std::vector<float> tri_err;
|
||||
|
||||
@@ -420,10 +420,9 @@ struct TextureDisplacementOptions
|
||||
}
|
||||
};
|
||||
|
||||
// How much detail a height texture carries, as BumpMesh's smart resolution measures it: central
|
||||
// differences of the grey image, the mean gradient and the share of texels steeper than 30 grey
|
||||
// levels, mapped to how many texels one mesh edge may span (1 for a hard-edged image, 4 for a smooth
|
||||
// one). Cached per image, like the decode.
|
||||
// How much detail a height texture carries: central differences of the grey image, the mean gradient
|
||||
// and the share of texels steeper than 30 grey levels, mapped to how many texels one mesh edge may
|
||||
// span (1 for a hard-edged image, 4 for a smooth one). Cached per image, like the decode.
|
||||
struct TextureDetail
|
||||
{
|
||||
float mean_gradient = 0.f;
|
||||
@@ -438,8 +437,8 @@ struct TextureDetail
|
||||
TextureDetail analyze_texture_detail(const TextureDisplacementLayer &layer);
|
||||
|
||||
// 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
|
||||
// rounded up to 0.01; budget 750 k. The texel size of the finest layer and its sharpness class are
|
||||
// edge = the model's world-space diagonal / 250, clamped to [0.05, 5] mm and rounded up to 0.01;
|
||||
// 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.
|
||||
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
|
||||
// over the three edge midpoints AND the centroid of |sampled displacement - the flat
|
||||
// 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
|
||||
// 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
|
||||
// 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).
|
||||
|
||||
@@ -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 });
|
||||
else
|
||||
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).
|
||||
valid &= append_shader("texture_displacement_bump", { prefix + "texture_displacement_bump.vs", prefix + "texture_displacement_bump.fs" });
|
||||
// Fast shaded preview for the texture displacement gizmo (see libslic3r/TextureDisplacement.hpp).
|
||||
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
|
||||
// painted patch, to sanity-check the unwrap.
|
||||
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
|
||||
// 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)
|
||||
// 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.
|
||||
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).
|
||||
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.
|
||||
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.
|
||||
// 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 -
|
||||
@@ -390,10 +390,10 @@ void GLGizmoTextureDisplacement::on_shutdown()
|
||||
{
|
||||
m_parent.toggle_model_objects_visibility(true);
|
||||
m_preview_glmodel.reset();
|
||||
m_bump_preview_glmodel.reset();
|
||||
m_shaded_preview_glmodel.reset();
|
||||
m_paint_overlay_glmodel.reset();
|
||||
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.
|
||||
m_preview_generation->fetch_add(1);
|
||||
m_preview_job_pending = false;
|
||||
@@ -410,9 +410,9 @@ void GLGizmoTextureDisplacement::on_shutdown()
|
||||
m_subdivide_editing = false;
|
||||
m_subdivide_preview_tris = -1;
|
||||
m_subdivide_preview_glmodel.reset();
|
||||
m_bump_active_chart = -1;
|
||||
m_bump_active_face.clear();
|
||||
m_bump_island_delta = Eigen::Matrix<float, 2, 3>::Identity();
|
||||
m_shaded_active_chart = -1;
|
||||
m_shaded_active_face.clear();
|
||||
m_shaded_island_delta = Eigen::Matrix<float, 2, 3>::Identity();
|
||||
m_island_drag_active = false;
|
||||
m_island_move_set.clear();
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// 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.
|
||||
// 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.
|
||||
if (m_use_bump_preview && m_bump_preview_dirty) {
|
||||
rebuild_bump_preview_mesh();
|
||||
m_bump_preview_dirty = false;
|
||||
if (m_use_shaded_preview && m_shaded_preview_dirty) {
|
||||
rebuild_shaded_preview_mesh();
|
||||
m_shaded_preview_dirty = false;
|
||||
}
|
||||
// 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
|
||||
// unpainted triangle of the volume and stays on the stroke-end cadence.
|
||||
// (see on_mouse()) and it only costs the painted patch
|
||||
if (m_paint_overlay_dirty) {
|
||||
rebuild_paint_overlay();
|
||||
m_paint_overlay_dirty = false;
|
||||
}
|
||||
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
|
||||
// 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.
|
||||
const bool use_bump = m_use_bump_preview && m_bump_preview_glmodel.is_initialized() && bump_preview_ready();
|
||||
const bool use_true_preview = !use_bump && m_preview_glmodel.is_initialized();
|
||||
const bool use_shaded = m_use_shaded_preview && m_shaded_preview_glmodel.is_initialized() && shaded_preview_ready();
|
||||
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
|
||||
// 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.
|
||||
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
|
||||
// than per branch below.
|
||||
m_parent.toggle_model_objects_visibility(true);
|
||||
if (use_bump || use_true_preview) {
|
||||
if (use_shaded || use_true_preview) {
|
||||
if (ModelVolume *mv = texture_volume())
|
||||
m_parent.toggle_model_objects_visibility(false, m_c->selection_info()->model_object(),
|
||||
m_c->selection_info()->get_active_instance(), mv);
|
||||
}
|
||||
|
||||
if (use_bump) {
|
||||
render_bump_preview_mesh();
|
||||
if (use_shaded) {
|
||||
render_shaded_preview_mesh();
|
||||
} else if (use_true_preview) {
|
||||
render_preview_mesh();
|
||||
|
||||
@@ -527,11 +526,11 @@ void GLGizmoTextureDisplacement::render_painter_gizmo()
|
||||
if (show_paint_overlay)
|
||||
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*
|
||||
// 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.
|
||||
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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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();
|
||||
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*
|
||||
// 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
|
||||
// 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
|
||||
// area, because only the brush splits. Everything below indexes the patch's own vertex array, so
|
||||
// the extra vertices are simply carried through.
|
||||
|
||||
// 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);
|
||||
|
||||
// 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().
|
||||
const TextureDisplacementLayer *active = active_layer();
|
||||
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;
|
||||
if (!m_bump_preview_uses_vertex_uv)
|
||||
m_shaded_preview_uses_vertex_uv = corner_uv.size() == patch.indices.size() * 3;
|
||||
if (!m_shaded_preview_uses_vertex_uv)
|
||||
corner_uv.clear();
|
||||
m_bump_projection_mode = (active != nullptr && !m_bump_preview_uses_vertex_uv) ?
|
||||
layer_projection_frame(patch, *active, m_bump_patch_center, m_bump_patch_axis) : 0;
|
||||
m_shaded_projection_mode = (active != nullptr && !m_shaded_preview_uses_vertex_uv) ?
|
||||
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
|
||||
// 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).
|
||||
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 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
|
||||
// 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>{};
|
||||
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
// 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();
|
||||
@@ -1198,10 +1197,10 @@ void GLGizmoTextureDisplacement::rebuild_bump_preview_mesh()
|
||||
const stl_triangle_vertex_indices &tri = its.indices[f];
|
||||
// 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.
|
||||
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) {
|
||||
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();
|
||||
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;
|
||||
}
|
||||
};
|
||||
emit_triangles(patch, 1.f, true); // painted -> bumped, and coloured by the shader
|
||||
// Untouched surface: flat, so it still shows but isn't bumped - and uncoloured, which is what the
|
||||
emit_triangles(patch, 1.f, true); // painted -> shaded as relief, and coloured by the shader
|
||||
// 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).
|
||||
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
|
||||
// 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.
|
||||
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
|
||||
// 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();
|
||||
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);
|
||||
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();
|
||||
} 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)
|
||||
return;
|
||||
const PatchUnwrap &u = m_uv_editor_unwrap;
|
||||
if (u.source_face.size() != u.indices.size())
|
||||
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
|
||||
// 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.
|
||||
for (size_t t = 0; t < u.indices.size(); ++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
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
void GLGizmoTextureDisplacement::render_bump_preview_mesh()
|
||||
void GLGizmoTextureDisplacement::render_shaded_preview_mesh()
|
||||
{
|
||||
const ModelObject *mo = m_c->selection_info()->model_object();
|
||||
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;
|
||||
|
||||
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)
|
||||
return;
|
||||
|
||||
GLShaderProgram *shader = wxGetApp().get_shader("texture_displacement_bump");
|
||||
GLShaderProgram *shader = wxGetApp().get_shader("texture_displacement_shaded");
|
||||
if (shader == nullptr)
|
||||
return;
|
||||
|
||||
@@ -1401,14 +1400,14 @@ void GLGizmoTextureDisplacement::render_bump_preview_mesh()
|
||||
shader->set_uniform("tex_anchor", Vec3f(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
|
||||
// projecting; see rebuild_bump_preview_mesh().
|
||||
shader->set_uniform("use_vertex_uv", m_bump_preview_uses_vertex_uv);
|
||||
// projecting; see rebuild_shaded_preview_mesh().
|
||||
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:
|
||||
// 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.
|
||||
shader->set_uniform("projection_mode", m_bump_projection_mode);
|
||||
shader->set_uniform("patch_center", m_bump_patch_center);
|
||||
shader->set_uniform("patch_axis", m_bump_patch_axis);
|
||||
shader->set_uniform("projection_mode", m_shaded_projection_mode);
|
||||
shader->set_uniform("patch_center", m_shaded_patch_center);
|
||||
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
|
||||
// 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.
|
||||
const GLTexture *color_tex = get_layer_color_texture(*layer);
|
||||
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("has_color_tex", color_tex != nullptr);
|
||||
// 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);
|
||||
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) + "]";
|
||||
shader->set_uniform(("palette_rgb" + idx).c_str(), 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
|
||||
// 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 =
|
||||
(palette_count > 0) ? int(std::min(m_palette_filaments.size(), size_t(PALETTE_MAX_FILAMENTS))) : 0;
|
||||
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
|
||||
// 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_tr", Vec2f(d(0, 2), d(1, 2)));
|
||||
m_bump_preview_glmodel.render();
|
||||
m_shaded_preview_glmodel.render();
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
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
|
||||
// caller has to know whether the bump pass will draw *before* it hides the real volume for it.
|
||||
// Mirrors render_shaded_preview_mesh()'s own preconditions. Kept as a separate query because the
|
||||
// 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)
|
||||
return false;
|
||||
if (texture_volume() == nullptr)
|
||||
@@ -1472,12 +1471,12 @@ bool GLGizmoTextureDisplacement::bump_preview_ready() const
|
||||
const TextureDisplacementLayer *layer = active_layer();
|
||||
if (layer == nullptr || layer->empty())
|
||||
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.
|
||||
const GLTexture *tex = const_cast<GLGizmoTextureDisplacement *>(this)->get_layer_height_texture(*layer);
|
||||
if (tex == nullptr || tex->get_width() <= 0 || tex->get_height() <= 0)
|
||||
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
|
||||
@@ -1502,7 +1501,7 @@ void GLGizmoTextureDisplacement::rebuild_other_paint_overlay()
|
||||
// displaced positions it is lifted onto. Compared every frame, rebuilt only when it differs.
|
||||
std::string key;
|
||||
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(m_preview_its.vertices.size());
|
||||
for (const TextureDisplacementLayer &l : mv->texture_displacement_layers)
|
||||
@@ -1517,7 +1516,7 @@ void GLGizmoTextureDisplacement::rebuild_other_paint_overlay()
|
||||
return;
|
||||
|
||||
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())
|
||||
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
|
||||
// patch boundary, where the displacement is smallest anyway.
|
||||
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())
|
||||
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("projection_matrix", camera.get_projection_matrix());
|
||||
// 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
|
||||
// there. Blending is already enabled by render_painter_gizmo().
|
||||
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
|
||||
@@ -1703,7 +1702,7 @@ void GLGizmoTextureDisplacement::rebuild_uvcheck_mesh()
|
||||
m_uvcheck_uses_vertex_uv = have_uvs;
|
||||
m_uvcheck_projection_mode = have_uvs ? 0 :
|
||||
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
|
||||
// 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`.
|
||||
@@ -1797,7 +1796,7 @@ void GLGizmoTextureDisplacement::render_uvcheck_mesh()
|
||||
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("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("checker_freq", 4.f); // squares per texture tile
|
||||
shader->set_uniform("tiling_scale", layer->tiling_scale);
|
||||
@@ -1855,7 +1854,7 @@ void GLGizmoTextureDisplacement::rebuild_wireframe_overlay()
|
||||
if (its.indices.empty())
|
||||
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.
|
||||
if (m_wireframe_overlay_glmodel.is_initialized() && m_wireframe_overlay_vcount == its.vertices.size())
|
||||
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
|
||||
// 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
|
||||
// Fast (bump) mode or with nothing painted, the surface is the undisplaced base mesh.
|
||||
if (!m_use_bump_preview && !m_preview_its.indices.empty())
|
||||
// leaves them buried inside the relief, which is why the wireframe "didn't show in real mode". In
|
||||
// Fast (shaded) mode or with nothing painted, the surface is the undisplaced base mesh.
|
||||
if (!m_use_shaded_preview && !m_preview_its.indices.empty())
|
||||
build_wireframe_from_its(m_preview_its);
|
||||
else
|
||||
rebuild_wireframe_overlay();
|
||||
@@ -1907,13 +1906,13 @@ void GLGizmoTextureDisplacement::render_wireframe_overlay()
|
||||
|
||||
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
|
||||
// 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.
|
||||
m_preview_generation->fetch_add(1);
|
||||
update_uv_editor();
|
||||
rebuild_bump_preview_mesh();
|
||||
rebuild_shaded_preview_mesh();
|
||||
rebuild_paint_overlay();
|
||||
rebuild_uvcheck_mesh();
|
||||
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
|
||||
// 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();
|
||||
// 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 -
|
||||
@@ -2040,7 +2039,7 @@ void GLGizmoTextureDisplacement::queue_preview_job()
|
||||
}
|
||||
if (m_preview_job_pending) {
|
||||
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
|
||||
}
|
||||
m_parent.set_as_dirty();
|
||||
@@ -2310,12 +2309,12 @@ void GLGizmoTextureDisplacement::process_uv_commands()
|
||||
|
||||
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 :
|
||||
(mode == 3) ? UVCheckMode::Distortion : UVCheckMode::None;
|
||||
rebuild_uvcheck_mesh();
|
||||
if (m_use_bump_preview)
|
||||
rebuild_bump_preview_mesh();
|
||||
if (m_use_shaded_preview)
|
||||
rebuild_shaded_preview_mesh();
|
||||
else
|
||||
// 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.
|
||||
@@ -2426,7 +2425,7 @@ void GLGizmoTextureDisplacement::run_uv_command(int cmd, float value)
|
||||
if (cmd == int(Command::SetBackground)) {
|
||||
// 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);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
// 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
|
||||
// render_bump_preview_mesh().
|
||||
m_bump_active_chart = island;
|
||||
m_bump_island_delta = Eigen::Matrix<float, 2, 3>::Identity();
|
||||
m_bump_preview_dirty = true;
|
||||
// render_shaded_preview_mesh().
|
||||
m_shaded_active_chart = island;
|
||||
m_shaded_island_delta = Eigen::Matrix<float, 2, 3>::Identity();
|
||||
m_shaded_preview_dirty = true;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
m_island_drag_active = false;
|
||||
m_bump_active_chart = -1;
|
||||
m_bump_active_face.clear();
|
||||
m_shaded_active_chart = -1;
|
||||
m_shaded_active_face.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
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
// 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
|
||||
// shader applies delta to the flagged island's uv). The one rebuild that bakes the flags is
|
||||
// rebuild. delta = F_current * F_baked^-1 in final-uv space.
|
||||
// The one rebuild that bakes the flags is
|
||||
// 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();
|
||||
cur.topRows<2>() = xf[size_t(island)];
|
||||
Eigen::Matrix3f bak = Eigen::Matrix3f::Identity();
|
||||
bak.topRows<2>() = m_bump_baked_active_xf;
|
||||
m_bump_island_delta = (cur * bak.inverse()).topRows<2>();
|
||||
bak.topRows<2>() = m_shaded_baked_active_xf;
|
||||
m_shaded_island_delta = (cur * bak.inverse()).topRows<2>();
|
||||
m_parent.set_as_dirty();
|
||||
}
|
||||
}
|
||||
@@ -3260,12 +3259,12 @@ void GLGizmoTextureDisplacement::update_model_object()
|
||||
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
|
||||
// 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 -
|
||||
// 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) {
|
||||
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.
|
||||
if (m_adjust_texture_mode)
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -4166,7 +4165,7 @@ TextureColorSettings GLGizmoTextureDisplacement::color_settings_for(const ModelV
|
||||
|
||||
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
|
||||
// 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.
|
||||
@@ -4795,7 +4794,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_thumbnail(const TextureDisplace
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
std::unique_ptr<GLTexture> texture = upload_height_thumbnail(decode_height_texture(layer));
|
||||
if (!texture)
|
||||
@@ -4831,7 +4830,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_height_texture(const TextureDis
|
||||
if (layer.empty())
|
||||
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.
|
||||
if (m_height_tex && m_height_tex_source == layer.image_data.get() && m_height_tex_smoothing == layer.smoothing)
|
||||
return m_height_tex.get();
|
||||
@@ -5033,7 +5032,7 @@ void GLGizmoTextureDisplacement::show_debug_stage(int index)
|
||||
m_debug_stage = index;
|
||||
// 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.
|
||||
m_use_bump_preview = false;
|
||||
m_use_shaded_preview = false;
|
||||
m_preview_color_runs.clear();
|
||||
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 ----
|
||||
// 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::Distortion ? 3 : 0;
|
||||
int new_mode = cur_mode;
|
||||
@@ -5692,7 +5691,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
||||
new_mode = 0;
|
||||
ImGui::SameLine(0.f, gap_s);
|
||||
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;
|
||||
ImGui::SameLine(0.f, gap_s);
|
||||
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 (float_row("##subdiv_detail", _L("Detail"), &m_subdivide_detail_mm, 0.001f, 1.f, "%.3f mm", true, 0.f))
|
||||
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."));
|
||||
if (float_row("##subdiv_min", _L("Min edge"), &m_subdivide_min_edge_mm, 0.001f, 20.f, "%.3f mm", true, 0.f))
|
||||
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 "
|
||||
"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 "
|
||||
"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).
|
||||
@@ -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 "
|
||||
"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 "
|
||||
"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.
|
||||
if (mv != nullptr) {
|
||||
@@ -6751,7 +6750,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
||||
}
|
||||
m_imgui->disabled_end();
|
||||
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::SetNextItemWidth(x0 + panel_w - ImGui::GetCursorPosX());
|
||||
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();
|
||||
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
|
||||
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 "
|
||||
|
||||
@@ -303,7 +303,7 @@ private:
|
||||
GLTexture *get_layer_thumbnail(const TextureDisplacementLayer &layer);
|
||||
|
||||
// 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);
|
||||
// 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.
|
||||
@@ -389,13 +389,13 @@ private:
|
||||
void render_preview_mesh();
|
||||
|
||||
// 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
|
||||
// resources/shaders/*/texture_displacement_bump.* shader. Faster than the true-displacement
|
||||
// preview (no CPU meshing at all - just a per-vertex paint-weight buffer built at the same
|
||||
// cadence as rebuild_preview()) but only shows the *active* layer, and any bump is a shading
|
||||
// illusion, not real geometry - "Bake" always produces the true, exact result either way.
|
||||
void rebuild_bump_preview_mesh();
|
||||
void render_bump_preview_mesh();
|
||||
// instead of actually moving vertices, using the resources/shaders/*/texture_displacement_shaded.*
|
||||
// shader. Faster than the true-displacement preview (no CPU meshing at all - just a per-vertex
|
||||
// paint-weight buffer built at the same cadence as rebuild_preview()) but only shows the *active*
|
||||
// layer, and the relief it shows is a shading illusion, not real geometry - "Bake" always produces
|
||||
// the true, exact result either way.
|
||||
void rebuild_shaded_preview_mesh();
|
||||
void render_shaded_preview_mesh();
|
||||
|
||||
// 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
|
||||
@@ -636,16 +636,16 @@ private:
|
||||
// mouse button driving the drag hasn't been released yet - see on_render_input_window().
|
||||
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-
|
||||
// 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.
|
||||
bool m_use_bump_preview = true;
|
||||
// Set from the UV editor's per-move island edits instead of rebuilding the (potentially large) bump
|
||||
bool m_use_shaded_preview = true;
|
||||
// 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
|
||||
// and the 3D view. The rebuild is instead coalesced to once per 3D frame (render_painter_gizmo).
|
||||
bool m_bump_preview_dirty = false;
|
||||
GLModel m_bump_preview_glmodel;
|
||||
bool m_shaded_preview_dirty = false;
|
||||
GLModel m_shaded_preview_glmodel;
|
||||
|
||||
// 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
|
||||
@@ -662,7 +662,7 @@ private:
|
||||
void rebuild_island_overlay(const std::vector<int> &selection);
|
||||
void render_island_overlay();
|
||||
// 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;
|
||||
void rebuild_paint_overlay();
|
||||
void render_paint_overlay(GLModel &overlay);
|
||||
@@ -671,33 +671,33 @@ private:
|
||||
GLModel m_other_paint_glmodel;
|
||||
std::string m_other_paint_key;
|
||||
void rebuild_other_paint_overlay();
|
||||
// Whether render_bump_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
|
||||
// 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 shaded path draws nothing, and hiding the
|
||||
// volume for it left the model invisible.
|
||||
bool bump_preview_ready() const;
|
||||
// Whether the current bump 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().
|
||||
bool m_bump_preview_uses_vertex_uv = false;
|
||||
// The projection frame handed to the bump shader, captured when the mesh is built. Cylindrical and
|
||||
bool shaded_preview_ready() const;
|
||||
// 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_shaded_preview_mesh().
|
||||
bool m_shaded_preview_uses_vertex_uv = false;
|
||||
// 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
|
||||
// around the whole patch, which no fragment can work out for itself. See layer_projection_frame().
|
||||
int m_bump_projection_mode = 0;
|
||||
Vec3f m_bump_patch_center = Vec3f::Zero();
|
||||
Vec3f m_bump_patch_axis = Vec3f::UnitZ();
|
||||
int m_shaded_projection_mode = 0;
|
||||
Vec3f m_shaded_patch_center = Vec3f::Zero();
|
||||
Vec3f m_shaded_patch_axis = Vec3f::UnitZ();
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// 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);
|
||||
// m_bump_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
|
||||
// live delta is measured; m_bump_island_delta is the resulting final-uv-space affine handed to the
|
||||
// as smoothly as Adjust placement. m_shaded_active_chart is the dragged island (or -1);
|
||||
// m_shaded_active_face flags the dragged islands' *triangles*, indexed by painted-patch face;
|
||||
// m_shaded_baked_active_xf is that island's placement baked into the current mesh, against which the
|
||||
// live delta is measured; m_shaded_island_delta is the resulting final-uv-space affine handed to the
|
||||
// shader (identity except mid-drag).
|
||||
//
|
||||
// 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
|
||||
// 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.
|
||||
int m_bump_active_chart = -1;
|
||||
std::vector<uint8_t> m_bump_active_face;
|
||||
Eigen::Matrix<float, 2, 3> m_bump_baked_active_xf = Eigen::Matrix<float, 2, 3>::Identity();
|
||||
Eigen::Matrix<float, 2, 3> m_bump_island_delta = Eigen::Matrix<float, 2, 3>::Identity();
|
||||
// Flags `charts`' triangles in m_bump_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.
|
||||
void compute_bump_active_faces(const std::vector<int> &charts, size_t patch_face_count);
|
||||
int m_shaded_active_chart = -1;
|
||||
std::vector<uint8_t> m_shaded_active_face;
|
||||
Eigen::Matrix<float, 2, 3> m_shaded_baked_active_xf = 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_shaded_active_face, sized to `patch_face_count` (the painted patch
|
||||
// the displacement mesh is being built from). Cleared if the unwrap carries no face map.
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
// 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,
|
||||
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
|
||||
@@ -757,7 +757,7 @@ private:
|
||||
UVCheckMode m_uv_check_mode = UVCheckMode::None;
|
||||
GLModel m_uvcheck_glmodel;
|
||||
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;
|
||||
Vec3f m_uvcheck_patch_center = Vec3f::Zero();
|
||||
Vec3f m_uvcheck_patch_axis = Vec3f::UnitZ();
|
||||
@@ -798,14 +798,14 @@ private:
|
||||
bool m_wireframe_overlay = false;
|
||||
GLModel m_wireframe_overlay_glmodel;
|
||||
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 refresh_wireframe(); // pick base vs displaced source for the current view
|
||||
void render_wireframe_overlay();
|
||||
// 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").
|
||||
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()),
|
||||
// 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.
|
||||
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
|
||||
// blur the bake will apply.
|
||||
std::unique_ptr<GLTexture> m_height_tex;
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
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.
|
||||
HeightFieldSampler bump = [](const Vec3f &p, const Vec3f &) {
|
||||
// A tight Gaussian hill at the sheet's center: strong curvature near (0.5, 0.5), flat far away.
|
||||
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);
|
||||
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).
|
||||
std::vector<int> source;
|
||||
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);
|
||||
|
||||
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.
|
||||
float near_max = 0.f, far_max = 0.f;
|
||||
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]")
|
||||
{
|
||||
// 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.
|
||||
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);
|
||||
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;
|
||||
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(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)
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
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.
|
||||
const indexed_triangle_set cube = its_make_cube(20.f, 20.f, 20.f);
|
||||
|
||||
Reference in New Issue
Block a user