From 6494010009f541910a8a75cee83f78919d338cd8 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Thu, 18 Jan 2024 18:46:55 +0100 Subject: [PATCH] fix truncated join cutoff + some cleanup --- GLMakie/assets/shader/lines.frag | 8 ++- GLMakie/assets/shader/lines.geom | 104 +++++++++++------------------- GLMakie/src/drawing_primitives.jl | 2 +- 3 files changed, 42 insertions(+), 72 deletions(-) diff --git a/GLMakie/assets/shader/lines.frag b/GLMakie/assets/shader/lines.frag index 67c81d5b913..5151811a835 100644 --- a/GLMakie/assets/shader/lines.frag +++ b/GLMakie/assets/shader/lines.frag @@ -8,9 +8,7 @@ struct Nothing{ //Nothing type, to encode if some variable doesn't contain any d in vec4 f_color; in vec4 f_quad_sdf; -in vec2 f_joint_cutoff; -// in float f_line_width; -// in float f_cumulative_length; +in vec4 f_joint_cutoff; in vec2 f_uv; flat in vec4 f_pattern_overwrite; flat in uvec2 f_id; @@ -92,6 +90,10 @@ void main(){ // sdf = max(sdf, abs(f_quad_sdf.z) - f_line_width); sdf = max(sdf, max(f_quad_sdf.z, f_quad_sdf.w)); + // smoothly cut off corners of truncated miter joints + sdf = max(sdf, max(f_joint_cutoff.z, f_joint_cutoff.w)); + + // add pattern sdf = max(sdf, get_pattern_sdf(pattern)); // draw diff --git a/GLMakie/assets/shader/lines.geom b/GLMakie/assets/shader/lines.geom index 88835717bfd..53c7ddeb5bf 100644 --- a/GLMakie/assets/shader/lines.geom +++ b/GLMakie/assets/shader/lines.geom @@ -19,9 +19,7 @@ in float g_thickness[]; out vec4 f_color; out vec4 f_quad_sdf; // smooth edges (along length and width) -out vec2 f_joint_cutoff; // hard edges (joint) -// out float f_line_width; -// out float f_cumulative_length; +out vec4 f_joint_cutoff; // xy = hard cutoff, zw = smooth cutoff out vec2 f_uv; flat out vec4 f_pattern_overwrite; flat out uvec2 f_id; @@ -58,7 +56,7 @@ How it works: struct LineData { vec3 p1, p2, v; float segment_length, extrusion_a, extrusion_b; - vec2 n, miter_v_a, miter_n_a, miter_v_b, miter_n_b; + vec2 n, n0, n2, miter_v_a, miter_n_a, miter_v_b, miter_n_b; float miter_offset_a, miter_offset_b; bool is_start, is_end; }; @@ -105,73 +103,37 @@ void process_pattern(sampler1D pattern, LineData line) { } void emit_vertex(vec3 origin, vec2 center, LineData line, int index, vec2 geom_offset) { + vec3 position = origin + geom_offset.x * line.v + vec3(geom_offset.y * line.n, 0); - // sdf prototyping + // sdf generation + + bool is_a_truncated_joint = line.miter_offset_a < 0.5; + bool is_b_truncated_joint = line.miter_offset_b < 0.5; - // joint hard cutoff - // TODO Problem: this can cut the whole line even if the next segment does - // fill that space (due to being short for example) - // Options: - // - don't do this for truncated join -> overlap, need to clean extra corner generated by extrusion - // if line start/end move hard cutoff out so smooth rect cutoff can act vec2 VP1 = position.xy - line.p1.xy; vec2 VP2 = position.xy - line.p2.xy; - f_joint_cutoff = vec2( - line.is_start ? -10.0 : dot(VP1, -line.miter_v_a), - line.is_end ? -10.0 : dot(VP2, line.miter_v_b) - ); - // note miter_v_a and miter_v_b are different, so no -width .. + width merge - // also no max - - - // rect - // vec2 VC = position.xy - center; - // can't have absolute, will break interpolation - // f_rect_sdf = vec2( - // abs(dot(VC, v)) - 0.5 * segment_length, // - extrusion, apply in frag? do we need 0 edge? - // abs(dot(VC, n)) - thickness - // ); - // f_rect_sdf = vec2(dot(VC, line.v.xy), dot(VC, line.n)); - - // joint soft cutoff - // need miter_offset to be big if no truncation - // f_joint_smooth = vec2(100.0, 100.0); - // if (line.miter_offset_a < 0.5) // truncated join condition - // f_joint_smooth.x = dot(VP1, line.miter_n_a) - 0.5 * g_thickness[1] * line.miter_offset_a; - // if (line.miter_offset_b < 0.5) // truncated join condition - // f_joint_smooth.y = dot(VP2, line.miter_n_b) - 0.5 * g_thickness[2] * line.miter_offset_b; - - - /* - ####### - width sdf: - vert/geom: x = dot(vertex_pos - center, line.n) - frag: sd = abs(x) - unpadded_linewidth - length sdf: - start sdf: - vert/geom: x = dot(vertex_pos - P1, -line.v) - frag: sd = x - stop sdf: - vert/goem: x = dot(vertex_pos - P2, line.v); - frag: sd = x - smooth truncated join sdf: - vert/geom: x = dot(vertex_pos - P1/P2, sign(dot(miter_v, v)) * miter_v) - miter_distance - frag: sd = x - sharp join sdf: - vert/geom: x = -10.0 - frag: sd = x - */ - - // SDF in (v dir at P1, v dir at P2, n dir) (default is sharp joint) - // f_quad_sdf = vec3(-1.0, -1.0, dot(position.xy - center, line.n)); - float temp = dot(position.xy - center, line.n); - // f_quad_sdf = vec4(-1.0, -1.0, temp - 0.5 * g_thickness[index], 0.5 * g_thickness[index] - temp); - f_quad_sdf = vec4(-1.0, -1.0, temp - 0.5 * g_thickness[index], -temp - 0.5 * g_thickness[index]); - bool is_a_truncated_joint = line.miter_offset_a < 0.5; - bool is_b_truncated_joint = line.miter_offset_b < 0.5; + // by default joint cutoffs do nothing + f_joint_cutoff = vec4(-10.0); + + // sharp joints use sharp (pixelated) cut offs to avoid self-overlap + if (!line.is_start && !is_a_truncated_joint) + f_joint_cutoff.x = dot(VP1, -line.miter_v_a); + if (!line.is_end && !is_b_truncated_joint) + f_joint_cutoff.y = dot(VP2, line.miter_v_b); + + // truncated joints use smooth cutoff for corners that are outside the other segment + // TODO: this slightly degrades AA quality due to two AA edges overlapping + if (is_a_truncated_joint) + f_joint_cutoff.z = dot(VP1, -sign(dot(line.v.xy, line.n0)) * line.n0) - 0.5 * g_thickness[1]; + if (is_b_truncated_joint) + f_joint_cutoff.w = dot(VP2, sign(dot(line.v.xy, line.n2)) * line.n2) - 0.5 * g_thickness[2]; + + // SDF of quad + + // In line direction if (line.is_start) // flat line end f_quad_sdf.x = dot(VP1, -line.v.xy); else if (is_a_truncated_joint) @@ -182,14 +144,17 @@ void emit_vertex(vec3 origin, vec2 center, LineData line, int index, vec2 geom_o else if (is_b_truncated_joint) f_quad_sdf.y = dot(VP2, line.miter_n_b) - 0.5 * g_thickness[2] * line.miter_offset_b; - // if truncated or no join -> use lw at p - // else -> use lw at p + dot(miter_n, v) * sign(miter_n, offset dir) - // float line_length = line.segment_length; + // In line normal direction (linewidth) // This mostly works // TODO: integrate better // TODO Problem: you can get concave shapes with very different linewidths + zooming // we want to adjust v direction on respective side to dodge both edge normals... + + // start/end/truncated joint: use g_thickness[i] at point i + // sharp joint: use g_thickness[i] at point i +- joint offset to avoid different + // linewidth between this and the previous/next segment + // TODO: can we calculate this more efficiently? // top float offset_a = !is_a_truncated_joint && !line.is_start ? line.extrusion_a : 0.0; float offset_b = !is_b_truncated_joint && !line.is_end ? line.extrusion_b : 0.0; @@ -207,10 +172,11 @@ void emit_vertex(vec3 origin, vec2 center, LineData line, int index, vec2 geom_o edge_normal = vec2(-edge_vector.y, edge_vector.x); f_quad_sdf.w = dot(position.xy - corner1, -edge_normal); + // And the simpler things... f_color = g_color[index]; gl_Position = vec4((2.0 * position.xy / resolution) - 1.0, position.z, 1.0); f_id = g_id[index]; - // TODO: pattern sampling + // index into pattern f_uv = vec2( (g_lastlen[index] + geom_offset.x) / pattern_length, 0.5 + geom_offset.y / g_thickness[index] @@ -304,6 +270,8 @@ void main(void) line.p2 = p2; line.v = v1; line.n = n1; + line.n0 = n0; + line.n2 = n2; line.segment_length = segment_length; line.is_start = !isvalid[0]; line.is_end = !isvalid[3]; diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 9e3ef9eb834..1cc98b1355a 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -459,7 +459,7 @@ function draw_atomic(screen::Screen, scene::Scene, @nospecialize(plot::Lines)) linewidth = gl_attributes[:thickness] px_per_unit = data[:px_per_unit] data[:pattern] = map(linestyle, linewidth, px_per_unit) do ls, lw, ppu - ppu * _mean(lw) .* ls + return ppu * _mean(lw) .* ls end data[:fast] = false