Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add options to reduce edge artifacts in scatter (text, lines) #3408

Merged
merged 11 commits into from
Dec 21, 2023
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}
Loading