From a393b216429abdb6bdd60069834a92fbb428c710 Mon Sep 17 00:00:00 2001 From: ExPikaPaka Date: Thu, 16 Jul 2026 08:43:08 +0200 Subject: [PATCH] Add texture displacement bump and UV-check shaders --- .../shaders/110/texture_displacement_bump.fs | 62 ++++++++- .../shaders/110/texture_displacement_bump.vs | 18 +-- .../110/texture_displacement_uvcheck.fs | 70 +++++++++++ .../110/texture_displacement_uvcheck.vs | 31 +++++ .../shaders/140/texture_displacement_bump.fs | 118 ++++++++++++++++-- .../shaders/140/texture_displacement_bump.vs | 24 +++- .../140/texture_displacement_uvcheck.fs | 83 ++++++++++++ .../140/texture_displacement_uvcheck.vs | 36 ++++++ src/slic3r/GUI/GLShadersManager.cpp | 3 + 9 files changed, 415 insertions(+), 30 deletions(-) create mode 100644 resources/shaders/110/texture_displacement_uvcheck.fs create mode 100644 resources/shaders/110/texture_displacement_uvcheck.vs create mode 100644 resources/shaders/140/texture_displacement_uvcheck.fs create mode 100644 resources/shaders/140/texture_displacement_uvcheck.vs diff --git a/resources/shaders/110/texture_displacement_bump.fs b/resources/shaders/110/texture_displacement_bump.fs index e39a484e3a..a3c44413b5 100644 --- a/resources/shaders/110/texture_displacement_bump.fs +++ b/resources/shaders/110/texture_displacement_bump.fs @@ -30,11 +30,33 @@ uniform float tiling_scale; uniform float rotation_rad; uniform vec2 uv_offset; uniform bool invert; +uniform bool use_vertex_uv; +// 2x3 affine (lin = (m00, m01, m10, m11), tr = (m02, m12)) applied to the dragged island's uv; see the +// 140 variant. Identity when nothing is dragged. +uniform vec4 island_delta_lin; +uniform vec2 island_delta_tr; varying vec3 clipping_planes_dots; varying vec4 model_pos; varying vec4 world_pos; varying float weight; +varying float active; +varying vec2 vertex_uv; + +void projection_axes(vec3 n, out vec3 t, out vec3 b) +{ + vec3 an = abs(n); + if (an.x >= an.y && an.x >= an.z) { // planar = p.yz + t = vec3(0.0, 1.0, 0.0); + b = vec3(0.0, 0.0, 1.0); + } else if (an.y >= an.x && an.y >= an.z) { // planar = p.xz + t = vec3(1.0, 0.0, 0.0); + b = vec3(0.0, 0.0, 1.0); + } else { // planar = p.xy + t = vec3(1.0, 0.0, 0.0); + b = vec3(0.0, 1.0, 0.0); + } +} vec2 project_uv(vec3 p, vec3 n) { @@ -55,15 +77,45 @@ void main() if (volume_mirrored) triangle_normal = -triangle_normal; - if (weight > 0.0) { - vec2 uv = project_uv(model_pos.xyz, triangle_normal); + if (use_vertex_uv) { + // Mikkelsen surface-gradient bump; 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 = (active > 0.5) + ? vec2(dot(island_delta_lin.xy, vertex_uv), dot(island_delta_lin.zw, vertex_uv)) + island_delta_tr + : vertex_uv; + float h = texture2D(height_tex, uv).r; + float k = (invert ? -1.0 : 1.0) * depth_mm * clamp(weight, 0.0, 1.0); + vec3 sigmaS = dFdx(model_pos.xyz); + vec3 sigmaT = dFdy(model_pos.xyz); + vec3 R1 = cross(sigmaT, triangle_normal); + vec3 R2 = cross(triangle_normal, sigmaS); + float det = dot(sigmaS, R1); + float dHdx = k * dFdx(h); + float dHdy = k * dFdy(h); + if (abs(det) > 1e-12) + triangle_normal = normalize(triangle_normal - (dHdx * R1 + dHdy * R2) / det); + } else if (weight > 0.0) { + vec2 uv = project_uv(model_pos.xyz, triangle_normal); + vec3 t, b; + projection_axes(triangle_normal, t, b); + float hL = texture2D(height_tex, uv - vec2(height_tex_texel.x, 0.0)).r; float hR = texture2D(height_tex, uv + vec2(height_tex_texel.x, 0.0)).r; float hD = texture2D(height_tex, uv - vec2(0.0, height_tex_texel.y)).r; float hU = texture2D(height_tex, uv + vec2(0.0, height_tex_texel.y)).r; - float sign_mul = invert ? -1.0 : 1.0; - vec3 bumped_normal = normalize(triangle_normal + sign_mul * depth_mm * vec3(hL - hR, hD - hU, 0.0)); - triangle_normal = normalize(mix(triangle_normal, bumped_normal, clamp(weight, 0.0, 1.0))); + + vec2 dh_duv = vec2((hR - hL) / (2.0 * height_tex_texel.x), (hU - hD) / (2.0 * height_tex_texel.y)); + float inv_tiling = (tiling_scale > 1e-6) ? (1.0 / tiling_scale) : 1.0; + float amplitude = (invert ? -1.0 : 1.0) * depth_mm * inv_tiling * clamp(weight, 0.0, 1.0); + + float cs = cos(rotation_rad); + float sn = sin(rotation_rad); + vec2 slope = amplitude * vec2(dh_duv.x * cs + dh_duv.y * sn, -dh_duv.x * sn + dh_duv.y * cs); + + vec3 gradient = slope.x * t + slope.y * b; + gradient -= triangle_normal * dot(triangle_normal, gradient); + triangle_normal = normalize(triangle_normal - gradient); } vec3 eye_normal = normalize(view_normal_matrix * triangle_normal); diff --git a/resources/shaders/110/texture_displacement_bump.vs b/resources/shaders/110/texture_displacement_bump.vs index 14a42ac0b1..293129cbb8 100644 --- a/resources/shaders/110/texture_displacement_bump.vs +++ b/resources/shaders/110/texture_displacement_bump.vs @@ -1,22 +1,24 @@ #version 110 +// See resources/shaders/140/texture_displacement_bump.vs for full documentation; this is the +// GLSL 1.10 compatibility variant. + uniform mat4 view_model_matrix; uniform mat4 projection_matrix; - uniform mat4 volume_world_matrix; -// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane. uniform vec2 z_range; -// Clipping plane - general orientation. Used by the SLA gizmo. uniform vec4 clipping_plane; -attribute vec3 v_position; -// Per-vertex paint weight for the currently active texture-displacement layer, 0..1. -attribute float v_weight; +attribute vec3 v_position; +attribute vec3 v_normal; // .x = paint weight (0/1); .y = 1 for the dragged island's vertices +attribute vec2 v_tex_coord; // precomputed texture uv, used only when use_vertex_uv is set varying vec3 clipping_planes_dots; varying vec4 model_pos; varying vec4 world_pos; varying float weight; +varying float active; +varying vec2 vertex_uv; void main() { @@ -26,5 +28,7 @@ void main() gl_Position = projection_matrix * view_model_matrix * model_pos; clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); - weight = v_weight; + weight = v_normal.x; + active = v_normal.y; + vertex_uv = v_tex_coord; } diff --git a/resources/shaders/110/texture_displacement_uvcheck.fs b/resources/shaders/110/texture_displacement_uvcheck.fs new file mode 100644 index 0000000000..fbfa6a0517 --- /dev/null +++ b/resources/shaders/110/texture_displacement_uvcheck.fs @@ -0,0 +1,70 @@ +#version 110 + +// See resources/shaders/140/texture_displacement_uvcheck.fs; GLSL 1.10 compatibility variant. + +#define INTENSITY_CORRECTION 0.6 +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) +#define INTENSITY_AMBIENT 0.3 +const vec3 ZERO = vec3(0.0, 0.0, 0.0); + +uniform mat3 view_normal_matrix; +uniform bool volume_mirrored; + +uniform int mode; +uniform float checker_freq; +uniform float tiling_scale; +uniform float rotation_rad; +uniform vec2 uv_offset; +uniform bool use_vertex_uv; + +varying vec3 clipping_planes_dots; +varying vec4 model_pos; +varying vec4 world_pos; +varying float distortion; +varying vec2 vertex_uv; + +vec2 project_uv(vec3 p, vec3 n) +{ + vec3 an = abs(n); + vec2 planar = (an.x >= an.y && an.x >= an.z) ? p.yz : ((an.y >= an.x && an.y >= an.z) ? p.xz : p.xy); + planar *= (tiling_scale > 1e-6) ? (1.0 / tiling_scale) : 1.0; + float cs = cos(rotation_rad); + float sn = sin(rotation_rad); + return vec2(planar.x * cs - planar.y * sn, planar.x * sn + planar.y * cs) + uv_offset; +} + +vec3 heatmap(float t) +{ + t = clamp(t, 0.0, 1.0); + return clamp(vec3(1.5 - abs(4.0 * t - 3.0), + 1.5 - abs(4.0 * t - 2.0), + 1.5 - abs(4.0 * t - 1.0)), 0.0, 1.0); +} + +void main() +{ + if (any(lessThan(clipping_planes_dots, ZERO))) + discard; + + vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz))); + if (volume_mirrored) + triangle_normal = -triangle_normal; + + vec3 base; + if (mode == 1) { + base = heatmap(distortion); + } else { + vec2 uv = use_vertex_uv ? vertex_uv : project_uv(model_pos.xyz, triangle_normal); + vec2 c = floor(uv * checker_freq); + float check = mod(c.x + c.y, 2.0); + base = (check < 0.5) ? vec3(0.22, 0.23, 0.26) : vec3(0.82, 0.83, 0.86); + } + + vec3 eye_normal = normalize(view_normal_matrix * triangle_normal); + float intensity = INTENSITY_AMBIENT + max(dot(eye_normal, LIGHT_TOP_DIR), 0.0) * LIGHT_TOP_DIFFUSE + + max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0) * LIGHT_FRONT_DIFFUSE; + gl_FragColor = vec4(base * intensity, 1.0); +} diff --git a/resources/shaders/110/texture_displacement_uvcheck.vs b/resources/shaders/110/texture_displacement_uvcheck.vs new file mode 100644 index 0000000000..06c59e5c61 --- /dev/null +++ b/resources/shaders/110/texture_displacement_uvcheck.vs @@ -0,0 +1,31 @@ +#version 110 + +// See resources/shaders/140/texture_displacement_uvcheck.vs; GLSL 1.10 compatibility variant. + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat4 volume_world_matrix; +uniform vec2 z_range; +uniform vec4 clipping_plane; + +attribute vec3 v_position; +attribute vec3 v_normal; // .x = per-vertex uv distortion +attribute vec2 v_tex_coord; // precomputed texture uv, used only when use_vertex_uv is set + +varying vec3 clipping_planes_dots; +varying vec4 model_pos; +varying vec4 world_pos; +varying float distortion; +varying vec2 vertex_uv; + +void main() +{ + model_pos = vec4(v_position, 1.0); + world_pos = volume_world_matrix * model_pos; + + gl_Position = projection_matrix * view_model_matrix * model_pos; + clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); + + distortion = v_normal.x; + vertex_uv = v_tex_coord; +} diff --git a/resources/shaders/140/texture_displacement_bump.fs b/resources/shaders/140/texture_displacement_bump.fs index e023e43d74..5f762c4f32 100644 --- a/resources/shaders/140/texture_displacement_bump.fs +++ b/resources/shaders/140/texture_displacement_bump.fs @@ -1,14 +1,33 @@ #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. Used -// while the brush is actively dragging (see project plan, "Preview shaders" section); the true, -// exact result is what "Bake" produces via libslic3r/TextureDisplacement.cpp on the CPU. +// height texture's local gradient (a bump map), faded out by the per-vertex paint weight. The +// true, exact result is what "Bake" produces via libslic3r/TextureDisplacement.cpp on the CPU. // -// NOTE: the gradient-to-normal conversion below assumes the two planar-projection axes chosen by -// project_uv() are reasonably aligned with the surface here; it is a cheap approximation (no -// tangent/bitangent basis is reconstructed on the GPU), acceptable for a live preview but not -// intended to be pixel-exact versus the CPU-side bake. +// The bake displaces each surface point along its normal by H = +/- depth_mm * (h(uv) - midlevel), +// with uv from the layer's projection. The perturbed normal is the analytic +// +// N' = normalize(N - (dH/da) * T - (dH/db) * B) +// +// over any orthonormal surface tangent pair (T, B), where the two slopes are real mm-per-mm +// 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 +// wrong axes. +// +// Two projection paths: +// * Triplanar (use_vertex_uv = 0): uv and the tangent axes are derived in-shader from the dominant +// normal axis, mirroring libslic3r's project_planar()/apply_uv_transform(), and the slope is +// formed analytically (there is a closed-form uv, so 1 uv unit is exactly tiling_scale mm). +// * Precomputed uv (use_vertex_uv = 1, used for LSCM): uv comes per-vertex from the CPU (the LSCM +// unwrap with island placement + tiling/rotation/offset already folded in), and the perturbed +// normal is built with Mikkelsen's method -- the surface gradient taken straight from the +// screen-space derivatives of the sampled height and position. This makes no uv->mm scale +// 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. #define INTENSITY_CORRECTION 0.6 @@ -39,14 +58,41 @@ uniform float tiling_scale; uniform float rotation_rad; uniform vec2 uv_offset; uniform bool invert; +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 (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 +// only a uniform update -- no mesh rebuild -- exactly the way Adjust placement moves the whole texture. +uniform vec4 island_delta_lin; +uniform vec2 island_delta_tr; in vec3 clipping_planes_dots; in vec4 model_pos; in vec4 world_pos; in float weight; +in float active; +in vec2 vertex_uv; out vec4 out_color; +// The two model-space axes the triplanar planar coordinate is read off, per dominant normal +// component -- same choice libslic3r's project_planar() makes, so planar.x runs along t, planar.y +// along b. +void projection_axes(vec3 n, out vec3 t, out vec3 b) +{ + vec3 an = abs(n); + if (an.x >= an.y && an.x >= an.z) { // planar = p.yz + t = vec3(0.0, 1.0, 0.0); + b = vec3(0.0, 0.0, 1.0); + } else if (an.y >= an.x && an.y >= an.z) { // planar = p.xz + t = vec3(1.0, 0.0, 0.0); + b = vec3(0.0, 0.0, 1.0); + } else { // planar = p.xy + t = vec3(1.0, 0.0, 0.0); + b = vec3(0.0, 1.0, 0.0); + } +} + vec2 project_uv(vec3 p, vec3 n) { vec3 an = abs(n); @@ -66,15 +112,63 @@ void main() if (volume_mirrored) triangle_normal = -triangle_normal; - if (weight > 0.0) { - vec2 uv = project_uv(model_pos.xyz, triangle_normal); + if (use_vertex_uv) { + // Precomputed-uv (LSCM) path -- Mikkelsen's surface-gradient bump ("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, + // not isometric, so the local mm-per-uv varies across the chart and the earlier "one global + // 1/tiling factor" got the depth visibly wrong. dFdx(h) captures the true on-screen rate of + // change however the chart is stretched or however fine the tiling is. + // + // 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 + // other vertex (active == 0) samples its baked uv unchanged. + vec2 uv = (active > 0.5) + ? vec2(dot(island_delta_lin.xy, vertex_uv), dot(island_delta_lin.zw, vertex_uv)) + island_delta_tr + : vertex_uv; + float h = texture(height_tex, uv).r; + float k = (invert ? -1.0 : 1.0) * depth_mm * clamp(weight, 0.0, 1.0); + vec3 sigmaS = dFdx(model_pos.xyz); + vec3 sigmaT = dFdy(model_pos.xyz); + vec3 R1 = cross(sigmaT, triangle_normal); + vec3 R2 = cross(triangle_normal, sigmaS); + float det = dot(sigmaS, R1); + float dHdx = k * dFdx(h); + float dHdy = k * dFdy(h); + if (abs(det) > 1e-12) + triangle_normal = normalize(triangle_normal - (dHdx * R1 + dHdy * R2) / det); + } else if (weight > 0.0) { + // Triplanar path: uv and the tangent axes are reconstructed in-shader from the dominant + // normal component (see header). The gradient is expressed analytically because there is a + // closed-form uv here, unlike the LSCM case. + vec2 uv = project_uv(model_pos.xyz, triangle_normal); + vec3 t, b; + projection_axes(triangle_normal, t, b); + float hL = texture(height_tex, uv - vec2(height_tex_texel.x, 0.0)).r; float hR = texture(height_tex, uv + vec2(height_tex_texel.x, 0.0)).r; float hD = texture(height_tex, uv - vec2(0.0, height_tex_texel.y)).r; float hU = texture(height_tex, uv + vec2(0.0, height_tex_texel.y)).r; - float sign_mul = invert ? -1.0 : 1.0; - vec3 bumped_normal = normalize(triangle_normal + sign_mul * depth_mm * vec3(hL - hR, hD - hU, 0.0)); - triangle_normal = normalize(mix(triangle_normal, bumped_normal, clamp(weight, 0.0, 1.0))); + + // Central difference, per uv unit (not per texel). + vec2 dh_duv = vec2((hR - hL) / (2.0 * height_tex_texel.x), (hU - hD) / (2.0 * height_tex_texel.y)); + + // uv -> mm is 1/tiling_scale for the triplanar projection, so this turns the uv-space + // gradient into a real surface slope. + float inv_tiling = (tiling_scale > 1e-6) ? (1.0 / tiling_scale) : 1.0; + float amplitude = (invert ? -1.0 : 1.0) * depth_mm * inv_tiling * clamp(weight, 0.0, 1.0); + // uv was rotated by project_uv() while t/b are the unrotated model axes, so rotate the + // gradient back into the axes' frame. + float cs = cos(rotation_rad); + float sn = sin(rotation_rad); + vec2 slope = amplitude * vec2(dh_duv.x * cs + dh_duv.y * sn, -dh_duv.x * sn + dh_duv.y * cs); + + vec3 gradient = slope.x * t + slope.y * b; + gradient -= triangle_normal * dot(triangle_normal, gradient); + triangle_normal = normalize(triangle_normal - gradient); } vec3 eye_normal = normalize(view_normal_matrix * triangle_normal); diff --git a/resources/shaders/140/texture_displacement_bump.vs b/resources/shaders/140/texture_displacement_bump.vs index 636cdf525c..4518fb6687 100644 --- a/resources/shaders/140/texture_displacement_bump.vs +++ b/resources/shaders/140/texture_displacement_bump.vs @@ -9,16 +9,26 @@ uniform vec2 z_range; // Clipping plane - general orientation. Used by the SLA gizmo. uniform vec4 clipping_plane; -in vec3 v_position; -// Per-vertex paint weight for the currently active texture-displacement layer, 0..1 (see -// TriangleSelectorWeighted... note: Phase 1 stores this as a plain enforced/not-enforced mask, -// so this is 0.0 or 1.0 today; a continuous value is a natural later-phase extension). -in float v_weight; +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: +// 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 +// single uniform update rather than a whole-mesh rebuild (like Adjust placement). +// v_tex_coord -- the precomputed texture uv for this vertex, valid only when use_vertex_uv is set +// (i.e. the LSCM projection, where uv can't be reconstructed in the shader). The +// triplanar path ignores it and projects in the fragment shader instead. +in vec3 v_normal; +in vec2 v_tex_coord; out vec3 clipping_planes_dots; out vec4 model_pos; out vec4 world_pos; out float weight; +out float active; +out vec2 vertex_uv; void main() { @@ -28,5 +38,7 @@ void main() gl_Position = projection_matrix * view_model_matrix * model_pos; clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); - weight = v_weight; + weight = v_normal.x; + active = v_normal.y; + vertex_uv = v_tex_coord; } diff --git a/resources/shaders/140/texture_displacement_uvcheck.fs b/resources/shaders/140/texture_displacement_uvcheck.fs new file mode 100644 index 0000000000..323a49a4ba --- /dev/null +++ b/resources/shaders/140/texture_displacement_uvcheck.fs @@ -0,0 +1,83 @@ +#version 140 + +// UV-check overlay for the texture-displacement gizmo, drawn over the painted patch so the LSCM +// unwrap can be sanity-checked on the real 3D surface (mode set by the `mode` uniform): +// mode 0 -- Checker: a procedural checkerboard sampled at the layer's uv. Even squares that stay +// square everywhere on the model mean the unwrap is low-distortion; squares that smear or +// shear reveal exactly where it stretches. Same uv the bake samples, so what you see is +// 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 +// reads as 3D. + +#define INTENSITY_CORRECTION 0.6 +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) +#define INTENSITY_AMBIENT 0.3 +const vec3 ZERO = vec3(0.0, 0.0, 0.0); + +uniform mat3 view_normal_matrix; +uniform bool volume_mirrored; + +uniform int mode; // 0 checker, 1 distortion +uniform float checker_freq; // checker squares per uv unit (one uv unit == one texture tile) +uniform float tiling_scale; +uniform float rotation_rad; +uniform vec2 uv_offset; +uniform bool use_vertex_uv; + +in vec3 clipping_planes_dots; +in vec4 model_pos; +in vec4 world_pos; +in float distortion; +in vec2 vertex_uv; + +out vec4 out_color; + +vec2 project_uv(vec3 p, vec3 n) +{ + vec3 an = abs(n); + vec2 planar = (an.x >= an.y && an.x >= an.z) ? p.yz : ((an.y >= an.x && an.y >= an.z) ? p.xz : p.xy); + planar *= (tiling_scale > 1e-6) ? (1.0 / tiling_scale) : 1.0; + float cs = cos(rotation_rad); + float sn = sin(rotation_rad); + return vec2(planar.x * cs - planar.y * sn, planar.x * sn + planar.y * cs) + uv_offset; +} + +// Blue -> cyan -> green -> yellow -> red over t in [0,1]. +vec3 heatmap(float t) +{ + t = clamp(t, 0.0, 1.0); + return clamp(vec3(1.5 - abs(4.0 * t - 3.0), + 1.5 - abs(4.0 * t - 2.0), + 1.5 - abs(4.0 * t - 1.0)), 0.0, 1.0); +} + +void main() +{ + if (any(lessThan(clipping_planes_dots, ZERO))) + discard; + + vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz))); + if (volume_mirrored) + triangle_normal = -triangle_normal; + + vec3 base; + if (mode == 1) { + base = heatmap(distortion); + } else { + vec2 uv = use_vertex_uv ? vertex_uv : project_uv(model_pos.xyz, triangle_normal); + vec2 c = floor(uv * checker_freq); + float check = mod(c.x + c.y, 2.0); + // Two distinct greys, plus a faint tint on one set so orientation is readable at a glance. + base = (check < 0.5) ? vec3(0.22, 0.23, 0.26) : vec3(0.82, 0.83, 0.86); + } + + vec3 eye_normal = normalize(view_normal_matrix * triangle_normal); + float intensity = INTENSITY_AMBIENT + max(dot(eye_normal, LIGHT_TOP_DIR), 0.0) * LIGHT_TOP_DIFFUSE + + max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0) * LIGHT_FRONT_DIFFUSE; + out_color = vec4(base * intensity, 1.0); +} diff --git a/resources/shaders/140/texture_displacement_uvcheck.vs b/resources/shaders/140/texture_displacement_uvcheck.vs new file mode 100644 index 0000000000..86a7dd13e9 --- /dev/null +++ b/resources/shaders/140/texture_displacement_uvcheck.vs @@ -0,0 +1,36 @@ +#version 140 + +// Vertex stage for the UV-check overlay (checker / distortion heatmap) drawn over the painted patch +// by GLGizmoTextureDisplacement. Reuses GLModel's P3N3T2 layout so it needs no bespoke buffer: +// v_normal.x -- per-vertex UV distortion (uv-area / surface-area ratio, remapped so 0.5 = ideal); +// only the distortion mode reads it. +// v_tex_coord -- precomputed texture uv, valid only when use_vertex_uv is set (LSCM); the checker +// mode reconstructs uv in the fragment shader otherwise. + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat4 volume_world_matrix; +uniform vec2 z_range; +uniform vec4 clipping_plane; + +in vec3 v_position; +in vec3 v_normal; +in vec2 v_tex_coord; + +out vec3 clipping_planes_dots; +out vec4 model_pos; +out vec4 world_pos; +out float distortion; +out vec2 vertex_uv; + +void main() +{ + model_pos = vec4(v_position, 1.0); + world_pos = volume_world_matrix * model_pos; + + gl_Position = projection_matrix * view_model_matrix * model_pos; + clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); + + distortion = v_normal.x; + vertex_uv = v_tex_coord; +} diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index d602ca1e06..5e85154677 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -102,6 +102,9 @@ std::pair GLShadersManager::init() 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" }); + // 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" }); return { valid, error }; }