Skip to content

Commit

Permalink
Add options to reduce edge artifacts in scatter (text, lines) (#3408)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
ffreyer and SimonDanisch authored Dec 21, 2023
1 parent ada44a2 commit 2f9b798
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 13 deletions.
39 changes: 31 additions & 8 deletions GLMakie/assets/shader/distance_shape.frag
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);


Expand Down
17 changes: 12 additions & 5 deletions GLMakie/assets/shader/lines.frag
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -79,6 +87,5 @@ void main(){
// color.r = 1 - color.a;
// color.a = 0.5 + 0.5 * color.a;


write2framebuffer(color, f_id);
}
2 changes: 2 additions & 0 deletions GLMakie/src/drawing_primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions GLMakie/src/glshaders/particles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
27 changes: 27 additions & 0 deletions GLMakie/test/glmakie_refimages.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
28 changes: 28 additions & 0 deletions docs/reference/plots/lines.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}
28 changes: 28 additions & 0 deletions docs/reference/plots/linesegments.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}
36 changes: 36 additions & 0 deletions docs/reference/plots/scatter.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}

0 comments on commit 2f9b798

Please sign in to comment.