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 stroke and glow to WGLMakie #3518

Merged
merged 7 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## master

- Changes for Bonito rename and WGLMakie docs improvements [#3477](https://github.com/MakieOrg/Makie.jl/pull/3477).
- Add stroke and glow support to scatter and text in WGLMakie [#3518](https://github.com/MakieOrg/Makie.jl/pull/3518)

## 0.20.3

Expand Down
62 changes: 53 additions & 9 deletions WGLMakie/assets/sprites.frag
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ in vec2 frag_uv;

// Half width of antialiasing smoothstep
#define ANTIALIAS_RADIUS 0.8

in float frag_uvscale;
in float frag_distancefield_scale;
in vec4 frag_uv_offset_width;
flat in uint frag_instance_id;

// These versions of aastep assume that `dist` is a signed distance function
// which has been scaled to be in units of pixels.
float aastep(float threshold1, float dist) {
Expand Down Expand Up @@ -58,9 +64,23 @@ void fill(sampler2D image, vec4 fillcolor, vec2 uv, float infill, inout vec4 col
color = mix(color, im_color, infill);
}

in float frag_uvscale;
in float frag_distancefield_scale;
in vec4 frag_uv_offset_width;
void stroke(vec4 strokecolor, float signed_distance, float width, inout vec4 color){
if (width != 0.0){
float t = aastep(min(width, 0.0), 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){
float glow_width = get_glowwidth();
float stroke_width = get_strokewidth();
if (glow_width > 0.0){
float outside = (abs(signed_distance) - stroke_width) / glow_width;
float alpha = 1.0 - outside;
color = mix(vec4(glowcolor.rgb, glowcolor.a*alpha), color, inside);
}
}

float scaled_distancefield(sampler2D distancefield, vec2 uv){
// Glyph distance field units are in pixels. Convert to same distance
Expand All @@ -73,7 +93,6 @@ float scaled_distancefield(bool distancefield, vec2 uv){
return 0.0;
}

flat in uint frag_instance_id;
vec4 pack_int(uint id, uint index) {
vec4 unpack;
unpack.x = float((id & uint(0xff00)) >> 8) / 255.0;
Expand All @@ -84,32 +103,57 @@ vec4 pack_int(uint id, uint index) {
}

void main() {

int shape = get_shape_type();
float signed_distance = 0.0;

vec4 uv_off = frag_uv_offset_width;
vec2 tex_uv = mix(uv_off.xy, uv_off.zw, clamp(frag_uv, 0.0, 1.0));

int shape = get_shape_type();
if(shape == CIRCLE)
signed_distance = circle(frag_uv);
else if(shape == DISTANCEFIELD)
else if(shape == DISTANCEFIELD) {
signed_distance = scaled_distancefield(distancefield, tex_uv);
else if(shape == ROUNDED_RECTANGLE)
if (get_strokewidth() > 0.0 || get_glowwidth() > 0.0) {
// Compensate for the clamping of tex_uv by an approximate
// extension of the signed distance outside the valid texture
// region.
vec2 bufuv = frag_uv - clamp(frag_uv, 0.0, 1.0);
signed_distance -= length(bufuv);
}
} else if(shape == ROUNDED_RECTANGLE)
signed_distance = rounded_rectangle(frag_uv, vec2(0.2), vec2(0.8));
else if(shape == RECTANGLE)
signed_distance = 1.0; // rectangle(f_uv);
else if(shape == TRIANGLE)
signed_distance = triangle(frag_uv);

signed_distance *= frag_uvscale;
float inside = aastep(0.0, signed_distance);


float stroke_width = get_strokewidth();
float inside_start = max(-stroke_width, 0.0);
float inside = aastep(inside_start, signed_distance);

vec4 final_color = vec4(frag_color.xyz, 0);
fill(image, frag_color, frag_uv, inside, final_color);
stroke(get_strokecolor(), signed_distance, -stroke_width, final_color);
glow(get_glowcolor(), signed_distance, aastep(-stroke_width, signed_distance), final_color);

// debug - show background
// final_color.a = clamp(final_color.a, 0.0, 1.0);
// final_color = vec4(
// vec3(1,0,0) * (1.0 - final_color.a) + final_color.rgb * final_color.a,
// 0.4 + 0.6 * final_color.a
// );

if (picking) {
if (final_color.a > 0.1) {
fragment_color = pack_int(object_id, frag_instance_id);
}
return;
}


if (final_color.a <= 0.0){
discard;
}
Expand Down
30 changes: 23 additions & 7 deletions WGLMakie/assets/sprites.vert
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ out float frag_uvscale;
out float frag_distancefield_scale;
out vec4 frag_uv_offset_width;

flat out uint frag_instance_id;

#define ANTIALIAS_RADIUS 0.8


mat4 qmat(vec4 quat){
float num = quat.x * 2.0;
Expand Down Expand Up @@ -54,11 +58,12 @@ float _determinant(mat2 m) {
return m[0][0] * m[1][1] - m[0][1] * m[1][0];
}

flat out uint frag_instance_id;

void main(){
vec2 bbox_signed_radius = 0.5 * get_markersize(); // note; components may be negative.
vec2 sprite_bbox_centre = get_quad_offset() + bbox_signed_radius;
// get_pos() returns the position of the scatter marker
// get_position() returns the (relative) position of the current quad vertex

vec2 bbox_radius = 0.5 * get_markersize();
vec2 sprite_bbox_centre = get_quad_offset() + bbox_radius;

mat4 pview = projection * view;
mat4 trans = get_transform_marker() ? model : mat4(1.0);
Expand Down Expand Up @@ -111,14 +116,25 @@ void main(){
// any calculation based on them will not be a distance function.)
// * For sampled distance fields, we need to consistently choose the *x*
// for the scaling in get_distancefield_scale().
float sprite_from_u_scale = abs(get_markersize().x);
float sprite_from_u_scale = min(abs(get_markersize().x), abs(get_markersize().y));
frag_uvscale = viewport_from_sprite_scale * sprite_from_u_scale;
frag_distancefield_scale = distancefield_scale();

// add padding for AA, stroke and glow (non native distancefields don't need
// AA padding but CIRCLE etc do)
vec2 padded_bbox_size = bbox_radius + (
ANTIALIAS_RADIUS + max(0.0, get_strokewidth()) + max(0.0, get_glowwidth())
) / viewport_from_sprite_scale;
vec2 uv_pad_scale = padded_bbox_size / bbox_radius;

frag_color = tovec4(get_color());
frag_uv = get_uv();
frag_uv_offset_width = get_uv_offset_width();
// get_uv() returns (0, 0), (1, 0), (0, 1) or (1, 1)
// to accomodate stroke and glowwidth we need to extrude uv's outwards from (0.5, 0.5)
frag_uv = vec2(0.5) + (get_uv() - vec2(0.5)) * uv_pad_scale;

// screen space coordinates of the position
vec4 quad_vertex = (trans * vec4(2.0 * bbox_signed_radius * get_position(), 0.0, 0.0));
vec4 quad_vertex = (trans * vec4(2.0 * padded_bbox_size * get_position(), 0.0, 0.0));
gl_Position = vclip + quad_vertex;
gl_Position.z += gl_Position.w * get_depth_shift();

Expand Down
7 changes: 7 additions & 0 deletions WGLMakie/src/particles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,13 @@ function scatter_shader(scene::Scene, attributes, plot)
# id + picking gets filled in JS, needs to be here to emit the correct shader uniforms
uniform_dict[:picking] = false
uniform_dict[:object_id] = UInt32(0)

# Make sure these exist
get!(uniform_dict, :strokewidth, 0f0)
get!(uniform_dict, :strokecolor, RGBAf(0, 0, 0, 0))
get!(uniform_dict, :glowwidth, 0f0)
get!(uniform_dict, :glowcolor, RGBAf(0, 0, 0, 0))

return InstancedProgram(WebGL(), lasset("sprites.vert"), lasset("sprites.frag"),
instance, VertexArray(; per_instance...), uniform_dict)
end
Expand Down
3 changes: 0 additions & 3 deletions WGLMakie/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,8 @@ excludes = Set([
"fast pixel marker",
"Array of Images Scatter",
"Image Scatter different sizes",
"scatter with stroke",
"scatter with glow",
"lines and linestyles",
"Textured meshscatter", # not yet implemented
"BezierPath marker stroke", # not yet implemented
])
Makie.inline!(Makie.automatic)

Expand Down
1 change: 0 additions & 1 deletion docs/explanations/backends/wglmakie.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ Moving more of the implementation to JavaScript is currently the goal and will g

#### Missing Backend Features

* glow & stroke for scatter markers aren't implemented yet
* `lines(...)` just creates unconnected linesegments and `linestyle` isn't supported

#### Browser Support
Expand Down
Loading