From 2f9b7989194f4a6484a0f19bcedd43b6a13951c3 Mon Sep 17 00:00:00 2001 From: Frederic Freyer Date: Thu, 21 Dec 2023 14:25:52 +0100 Subject: [PATCH] Add options to reduce edge artifacts in scatter (text, lines) (#3408) * prototype fxaa mode * same for lines * update news * add refimg test * add docs examples/notes * Add naive depth sorting for scatter & text (#3432) * add naive depth sorting for scatter & text * add depthsorting docs * update news * test depthsorting too --------- Co-authored-by: Simon --- GLMakie/assets/shader/distance_shape.frag | 39 ++++++++++++++++++----- GLMakie/assets/shader/lines.frag | 17 +++++++--- GLMakie/src/drawing_primitives.jl | 2 ++ GLMakie/src/glshaders/particles.jl | 16 ++++++++++ GLMakie/test/glmakie_refimages.jl | 27 ++++++++++++++++ NEWS.md | 2 ++ docs/reference/plots/lines.md | 28 ++++++++++++++++ docs/reference/plots/linesegments.md | 28 ++++++++++++++++ docs/reference/plots/scatter.md | 36 +++++++++++++++++++++ 9 files changed, 182 insertions(+), 13 deletions(-) diff --git a/GLMakie/assets/shader/distance_shape.frag b/GLMakie/assets/shader/distance_shape.frag index 6fdeda71b3f..899225239a3 100644 --- a/GLMakie/assets/shader/distance_shape.frag +++ b/GLMakie/assets/shader/distance_shape.frag @@ -27,6 +27,7 @@ uniform float glow_width; uniform int shape; // shape is a uniform for now. Making them a in && using them for control flow is expected to kill performance uniform float px_per_unit; uniform bool transparent_picking; +uniform bool fxaa; flat in float f_viewport_from_u_scale; flat in float f_distancefield_scale; @@ -123,6 +124,14 @@ void stroke(vec4 strokecolor, float signed_distance, float width, inout vec4 col } } +void stroke_fxaa(vec4 strokecolor, float signed_distance, float width, inout vec4 color){ + if (width != 0.0){ + float t = step(min(width, 0.0), signed_distance) - step(max(width, 0.0), signed_distance); + vec4 bg_color = mix(color, vec4(strokecolor.rgb, 0), float(signed_distance < 0.5 * width)); + color = mix(bg_color, strokecolor, t); + } +} + void glow(vec4 glowcolor, float signed_distance, float inside, inout vec4 color){ if (glow_width > 0.0){ float s_stroke_width = px_per_unit * stroke_width; @@ -179,16 +188,30 @@ void main(){ float s_stroke_width = px_per_unit * stroke_width; float inside_start = max(-s_stroke_width, 0.0); - float inside = aastep(inside_start, signed_distance); - - // For the initial coloring we can use the base pixel color and modulate - // its alpha value to create the shape set by the signed distance field. (i.e. inside) vec4 final_color = fill(f_color, image, tex_uv); - final_color.a = final_color.a * inside; - // Stroke and glow need to also modulate colors (rgb) to smoothly transition - // from one to another. - stroke(f_stroke_color, signed_distance, -s_stroke_width, final_color); + if (!fxaa){ // anti-aliasing via sdf + // For the initial coloring we can use the base pixel color and modulate + // its alpha value to create the shape set by the signed distance field. (i.e. inside) + float inside = aastep(inside_start, signed_distance); + final_color.a = final_color.a * inside; + + // Stroke and glow need to also modulate colors (rgb) to smoothly transition + // from one to another. + stroke(f_stroke_color, signed_distance, -s_stroke_width, final_color); + + } else { // AA via FXAA + // Here we don't smooth edges (i.e. use step rather than smoothstep) and + // let fxaa figure out smoothing/anti-aliasing later. This fixes the + // halo artifact when rendering at different depths for solid colors + float inside = step(inside_start, signed_distance); + final_color.a = final_color.a * inside; + + stroke_fxaa(f_stroke_color, signed_distance, -s_stroke_width, final_color); + } + + // glow is always semi transparent so switching between step and smoothstep + // is mostly useless here glow(f_glow_color, signed_distance, aastep(-s_stroke_width, signed_distance), final_color); diff --git a/GLMakie/assets/shader/lines.frag b/GLMakie/assets/shader/lines.frag index 644e7a6c9d3..0de6518c2da 100644 --- a/GLMakie/assets/shader/lines.frag +++ b/GLMakie/assets/shader/lines.frag @@ -14,6 +14,7 @@ flat in vec2 f_uv_minmax; {{pattern_type}} pattern; uniform float pattern_length; +uniform bool fxaa; // Half width of antialiasing smoothstep #define ANTIALIAS_RADIUS 0.8 @@ -60,15 +61,22 @@ void main(){ vec4 color = vec4(f_color.rgb, 0.0); vec2 xy = get_sd(pattern, f_uv); - float alpha = aastep(0.0, xy.x); - float alpha2 = aastep(-f_thickness, f_thickness, xy.y); - float alpha3 = aastep_scaled(f_uv_minmax.x, f_uv_minmax.y, f_uv.x); + float alpha, alpha2, alpha3; + if (!fxaa) { + alpha = aastep(0.0, xy.x); + alpha2 = aastep(-f_thickness, f_thickness, xy.y); + alpha3 = aastep_scaled(f_uv_minmax.x, f_uv_minmax.y, f_uv.x); + } else { + alpha = step(0.0, xy.x); + alpha2 = step(-f_thickness, xy.y) - step(f_thickness, xy.y); + alpha3 = step(f_uv_minmax.x, f_uv.x) - step(f_uv_minmax.y, f_uv.x); + } color = vec4(f_color.rgb, f_color.a * alpha * alpha2 * alpha3); // Debug: Show uv values in line direction (repeating) // color = vec4(mod(f_uv.x, 1.0), 0, 0, 1); - + // Debug: Show uv values in line direction with pattern // color.r = 0.5; // color.g = mod(f_uv.x, 1.0); @@ -79,6 +87,5 @@ void main(){ // color.r = 1 - color.a; // color.a = 0.5 + 0.5 * color.a; - write2framebuffer(color, f_id); } diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 33773b5921c..9a66f835321 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -571,6 +571,8 @@ function draw_atomic(screen::Screen, scene::Scene, gl_attributes[:uv_offset_width] = uv_offset_width gl_attributes[:distancefield] = get_texture!(atlas) gl_attributes[:visible] = plot.visible + gl_attributes[:fxaa] = get(plot, :fxaa, Observable(false)) + gl_attributes[:depthsorting] = get(plot, :depthsorting, false) cam = scene.camera # gl_attributes[:preprojection] = Observable(Mat4f(I)) gl_attributes[:preprojection] = lift(plot, space, markerspace, cam.projectionview, cam.resolution) do s, ms, pv, res diff --git a/GLMakie/src/glshaders/particles.jl b/GLMakie/src/glshaders/particles.jl index 90e3aab1575..b0066970c23 100644 --- a/GLMakie/src/glshaders/particles.jl +++ b/GLMakie/src/glshaders/particles.jl @@ -176,6 +176,20 @@ function draw_scatter(screen, (marker, position), data) rot = vec2quaternion(rot) delete!(data, :rotation) + if to_value(pop!(data, :depthsorting, false)) + data[:indices] = map( + data[:projectionview], data[:preprojection], data[:model], + position + ) do pv, pp, m, pos + T = pv * pp * m + depth_vals = map(pos) do p + p4d = T * to_ndim(Point4f, to_ndim(Point3f, p, 0f0), 1f0) + p4d[3] / p4d[4] + end + UInt32.(sortperm(depth_vals, rev = true) .- 1) + end |> indexbuffer + end + @gen_defaults! data begin shape = Cint(0) position = position => GLBuffer @@ -194,6 +208,7 @@ function draw_scatter(screen, (marker, position), data) return shape end end + @gen_defaults! data begin quad_offset = Vec2f(0) => GLBuffer intensity = nothing => GLBuffer @@ -226,6 +241,7 @@ function draw_scatter(screen, (marker, position), data) scale_primitive = true gl_primitive = GL_POINTS end + # Exception for intensity, to make it possible to handle intensity with a # different length compared to position. Intensities will be interpolated in that case data[:intensity] = intensity_convert(intensity, position) diff --git a/GLMakie/test/glmakie_refimages.jl b/GLMakie/test/glmakie_refimages.jl index 4c08416ffda..7aadbc3fa4b 100644 --- a/GLMakie/test/glmakie_refimages.jl +++ b/GLMakie/test/glmakie_refimages.jl @@ -162,5 +162,32 @@ end p = mesh!(scene, Rect3f(Point3f(-10, -10, 0.01), Vec3f(20, 20, 0.02)), color = :white) update_cam!(scene, Vec3f(0, 0, 7), Vec3f(0, 0, 0), Vec3f(0, 1, 0)) + scene +end + +@reference_test "Signed Distance Field - FXAA interaction" begin + scene = Scene(size = (300, 200), camera = campixel!) + + # scatter/text shader + xs = 20:20:280 + ys = fill(170, length(xs)) + zs = range(3, 1, length=length(xs)) + scatter!(scene, xs, ys, zs, color = :blue, markersize = 40, fxaa = false) + ys = fill(130, length(xs)) + scatter!(scene, xs, ys, zs, color = :blue, markersize = 40, fxaa = true) + ys = fill(90, length(xs)) + scatter!(scene, xs, ys, zs, color = :blue, markersize = 40, depthsorting = true) + + # lines/linesegments shader + xs = 20:10:270 + ys = [50 + shift for _ in 1:13 for shift in (-10, 10)] + zs = range(3, 1, length=length(xs)) + lines!(scene, xs, ys, zs, color = :blue, linewidth = 4, fxaa = false) + ys = [20 + shift for _ in 1:13 for shift in (-10, 10)] + lines!(scene, xs, ys, zs, color = :blue, linewidth = 4, fxaa = true) + + # create some harder contrasts + mesh!(scene, Rect2f(0, 0, 300, 200), color = :red) + scene end \ No newline at end of file diff --git a/NEWS.md b/NEWS.md index 9210c5c9106..2fe7f103ad2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,8 @@ ## master +- Add `depthsorting` as a hidden attribute for scatter plots in GLMakie as an alternative fix for outline artifacts. [#3432](https://github.com/MakieOrg/Makie.jl/pull/3432) +- Disable SDF based anti-aliasing in scatter, text and lines plots when `fxaa = true` in GLMakie. This allows removing outline artifacts at the cost of quality. [#3408](https://github.com/MakieOrg/Makie.jl/pull/3408) - DataInspector Fixes: Fixed depth order, positional labels being in transformed space and `:inspector_clear` not getting called when moving from one plot to another. [#3454](https://github.com/MakieOrg/Makie.jl/pull/3454) - Fixed bug in GLMakie where the update from a (i, j) sized GPU buffer to a (j, i) sized buffer would fail [#3456](https://github.com/MakieOrg/Makie.jl/pull/3456). - Add `interpolate=true` to `volume(...)`, allowing to disable interpolation [#3485](https://github.com/MakieOrg/Makie.jl/pull/3485). diff --git a/docs/reference/plots/lines.md b/docs/reference/plots/lines.md index 28145387529..83c3e4f82e8 100644 --- a/docs/reference/plots/lines.md +++ b/docs/reference/plots/lines.md @@ -51,3 +51,31 @@ end f ``` \end{examplefigure} + +### Dealing with outline artifacts in GLMakie + +In GLMakie 3D line plots can generate outline artifacts depending on the order line segments are rendered in. +Currently there are a few ways to mitigate this problem, but they all come at a cost: +- `fxaa = true` will disable the native anti-aliasing of line segments and use fxaa instead. This results in less detailed lines. +- `transparency = true` will disable depth testing to a degree, resulting in all lines being rendered without artifacts. However with this lines will always have some level of transparency. +- `overdraw = true` will disable depth testing entirely (read and write) for the plot, removing artifacts. This will however change the z-order of line segments and allow plots rendered later to show up on top of the lines plot. + +\begin{examplefigure}{} +```julia +using GLMakie +GLMakie.activate!() # hide + +ps = rand(Point3f, 500) +cs = rand(500) +f = Figure(size = (600, 650)) +Label(f[1, 1], "base", tellwidth = false) +lines(f[2, 1], ps, color = cs, markersize = 20, fxaa = false) +Label(f[1, 2], "fxaa = true", tellwidth = false) +lines(f[2, 2], ps, color = cs, markersize = 20, fxaa = true) +Label(f[3, 1], "transparency = true", tellwidth = false) +lines(f[4, 1], ps, color = cs, markersize = 20, transparency = true) +Label(f[3, 2], "overdraw = true", tellwidth = false) +lines(f[4, 2], ps, color = cs, markersize = 20, overdraw = true) +f +``` +\end{examplefigure} \ No newline at end of file diff --git a/docs/reference/plots/linesegments.md b/docs/reference/plots/linesegments.md index 783f4bde81d..2f336f9e2d4 100644 --- a/docs/reference/plots/linesegments.md +++ b/docs/reference/plots/linesegments.md @@ -23,3 +23,31 @@ linesegments!(xs, ys .- 2, linewidth = 5, color = LinRange(1, 5, length(xs))) f ``` \end{examplefigure} + +### Dealing with outline artifacts in GLMakie + +In GLMakie 3D line plots can generate outline artifacts depending on the order line segments are rendered in. +Currently there are a few ways to mitigate this problem, but they all come at a cost: +- `fxaa = true` will disable the native anti-aliasing of line segments and use fxaa instead. This results in less detailed lines. +- `transparency = true` will disable depth testing to a degree, resulting in all lines being rendered without artifacts. However with this lines will always have some level of transparency. +- `overdraw = true` will disable depth testing entirely (read and write) for the plot, removing artifacts. This will however change the z-order of line segments and allow plots rendered later to show up on top of the linesegments plot. + +\begin{examplefigure}{} +```julia +using GLMakie +GLMakie.activate!() # hide + +ps = rand(Point3f, 500) +cs = rand(500) +f = Figure(size = (600, 650)) +Label(f[1, 1], "base", tellwidth = false) +linesegments(f[2, 1], ps, color = cs, markersize = 20, fxaa = false) +Label(f[1, 2], "fxaa = true", tellwidth = false) +linesegments(f[2, 2], ps, color = cs, markersize = 20, fxaa = true) +Label(f[3, 1], "transparency = true", tellwidth = false) +linesegments(f[4, 1], ps, color = cs, markersize = 20, transparency = true) +Label(f[3, 2], "overdraw = true", tellwidth = false) +linesegments(f[4, 2], ps, color = cs, markersize = 20, overdraw = true) +f +``` +\end{examplefigure} \ No newline at end of file diff --git a/docs/reference/plots/scatter.md b/docs/reference/plots/scatter.md index a507cf49a7b..b91750eb1cb 100644 --- a/docs/reference/plots/scatter.md +++ b/docs/reference/plots/scatter.md @@ -370,3 +370,39 @@ scatter(a[1:50:end, :], marker = '✈', markersize = 20, color = :black) ``` \end{examplefigure} + +### Dealing with outline artifacts in GLMakie + +In GLMakie 3D scatter plots can generate outline artifacts depending on the order markers are rendered in. +Currently there are a few ways to mitigate this problem, but they all come at a cost: +- `fxaa = true` will disable the native anti-aliasing of scatter markers and use fxaa instead. This results in less detailed markers, especially for thin markers like characters. +- `transparency = true` will disable depth testing to a degree, resulting in all markers being rendered without artifacts. However with this markers always have some level of transparency +- `overdraw = true` will disable depth testing entirely (read and write) for the plot, removing artifacts. This will however change the z-order of markers and allow plots rendered later to show up on top of the scatter plot +- `depthsorting = true` will sort markers by depth before rendering to fix the issue. This only works within a plot call, so when other plots are involved the issue may reappear. + +\begin{examplefigure}{} +```julia +using GLMakie +GLMakie.activate!() # hide + +ps = rand(Point3f, 500) +cs = rand(500) +f = Figure(size = (900, 650)) +Label(f[1, 1], "base", tellwidth = false) +scatter(f[2, 1], ps, color = cs, markersize = 20, fxaa = false) +Label(f[1, 2], "fxaa = true", tellwidth = false) +scatter(f[2, 2], ps, color = cs, markersize = 20, fxaa = true) + +Label(f[3, 1], "transparency = true", tellwidth = false) +scatter(f[4, 1], ps, color = cs, markersize = 20, transparency = true) +Label(f[3, 2], "overdraw = true", tellwidth = false) +scatter(f[4, 2], ps, color = cs, markersize = 20, overdraw = true) + +Label(f[1, 3], "depthsorting = true", tellwidth = false) +scatter(f[2, 3], ps, color = cs, markersize = 20, depthsorting = true) +Label(f[3, 3], "depthsorting = true", tellwidth = false) +scatter(f[4, 3], ps, color = cs, markersize = 20, depthsorting = true) +mesh!(Rect3f(Point3f(0), Vec3f(0.9, 0.9, 0.9)), color = :orange) +f +``` +\end{examplefigure} \ No newline at end of file