Fixed the random-dimples-on-zeros bug while fixing up the fill shaders

This commit is contained in:
Grant Sanderson
2020-05-31 17:02:05 -07:00
parent 270e93f6f0
commit 00dcc14df1
5 changed files with 80 additions and 134 deletions

View File

@ -64,7 +64,6 @@ class VMobject(Mobject):
('point', np.float32, (3,)), ('point', np.float32, (3,)),
('color', np.float32, (4,)), ('color', np.float32, (4,)),
('fill_all', np.float32, (1,)), ('fill_all', np.float32, (1,)),
('orientation', np.float32, (1,)),
], ],
"stroke_dtype": [ "stroke_dtype": [
("point", np.float32, (3,)), ("point", np.float32, (3,)),
@ -984,16 +983,13 @@ class VMobject(Mobject):
# Triangulate # Triangulate
inner_verts = points[inner_vert_indices] inner_verts = points[inner_vert_indices]
inner_tri_indices = inner_vert_indices[ inner_tri_indices = inner_vert_indices[earclip_triangulation(inner_verts, rings)]
earclip_triangulation(inner_verts, rings)
]
tri_indices = np.hstack([indices, inner_tri_indices]) tri_indices = np.hstack([indices, inner_tri_indices])
return tri_indices return tri_indices
def get_fill_shader_data(self): def get_fill_shader_data(self):
points = self.points points = self.points
orientation = self.get_orientation() orientation = self.get_orientation()
tri_indices = self.get_triangulation(orientation) tri_indices = self.get_triangulation(orientation)
@ -1007,8 +1003,9 @@ class VMobject(Mobject):
# are on the boundary, and the rest are in the interior # are on the boundary, and the rest are in the interior
data["fill_all"][:len(points)] = 0 data["fill_all"][:len(points)] = 0
data["fill_all"][len(points):] = 1 data["fill_all"][len(points):] = 1
data["orientation"] = orientation # Always send points in a positively oriented way
if orientation < 0:
data["point"][:len(points)] = points[::-1]
return data return data

View File

@ -1,11 +1,10 @@
#version 330 #version 330
in vec4 color; in vec4 color;
in float fill_type; in float fill_all; // Either 0 or 1e
in float uv_anti_alias_width; in float uv_anti_alias_width;
in vec2 uv_coords; in vec2 uv_coords;
in vec2 wz_coords;
in vec2 uv_b2; in vec2 uv_b2;
in float bezier_degree; in float bezier_degree;
@ -16,7 +15,7 @@ const float FILL_OUTSIDE = 1;
const float FILL_ALL = 2; const float FILL_ALL = 2;
// Needed for quadratic_bezier_distance // Needed for quadratic_bezier_distance insertion below
float modify_distance_for_endpoints(vec2 p, float dist, float t){ float modify_distance_for_endpoints(vec2 p, float dist, float t){
return dist; return dist;
} }
@ -26,27 +25,36 @@ float modify_distance_for_endpoints(vec2 p, float dist, float t){
// replaces this line with the contents of quadratic_bezier_sdf.glsl // replaces this line with the contents of quadratic_bezier_sdf.glsl
#INSERT quadratic_bezier_distance.glsl #INSERT quadratic_bezier_distance.glsl
bool is_inside_curve(){
if(bezier_degree < 2) return false;
float value = wz_coords.x * wz_coords.x - wz_coords.y;
if(fill_type == FILL_INSIDE) return value < 0;
if(fill_type == FILL_OUTSIDE) return value > 0;
return false;
}
float sdf(){ float sdf(){
if(is_inside_curve()) return -1.0; // For really flat curves, just take the distance to the curve
if(bezier_degree < 2 || abs(uv_b2.y / uv_b2.x) < uv_anti_alias_width){
return min_dist_to_curve(uv_coords, uv_b2, bezier_degree, false); return min_dist_to_curve(uv_coords, uv_b2, bezier_degree, false);
} }
// This converts uv_coords to a space where the bezier points sit on
// (0, 0), (1/2, 0) and (1, 1), so that the curve can be expressed implicityly
// as y = x^2.
float u2 = uv_b2.x;
float v2 = uv_b2.y;
mat2 to_simple_space = mat2(
v2, 0,
2 - u2, 4 * v2
);
vec2 p = to_simple_space * uv_coords;
float Fp = sign(v2) * (p.x * p.x - p.y);
vec2 grad = vec2(
- 2 * p.x * v2, // del C / del u
4 * v2 - 4 * p.x * (2 - u2) // del C / del v
);
return Fp / length(grad);
}
void main() { void main() {
if (color.a == 0) discard; if (color.a == 0) discard;
frag_color = color; frag_color = color;
if (fill_type == FILL_ALL) return; // TODO, Add shading based on normal vector, light position and gloss
if (fill_all == 1.0) return;
frag_color.a *= smoothstep(1, 0, sdf() / uv_anti_alias_width); frag_color.a *= smoothstep(1, 0, sdf() / uv_anti_alias_width);
// frag_color.a += 0.2;
} }

View File

@ -11,17 +11,13 @@ uniform vec3 frame_center;
in vec3 bp[3]; in vec3 bp[3];
in vec4 v_color[3]; in vec4 v_color[3];
in float v_fill_all[3]; in float v_fill_all[3];
in float v_orientation[3];
out vec4 color; out vec4 color;
out float fill_type; out float fill_all;
out float uv_anti_alias_width; out float uv_anti_alias_width;
// uv space is where b0 = (0, 0), b1 = (1, 0), and transform is orthogonal // uv space is where b0 = (0, 0), b1 = (1, 0), and transform is orthogonal
out vec2 uv_coords; out vec2 uv_coords;
out vec2 uv_b2; out vec2 uv_b2;
// wz space is where b0 = (0, 0), b1 = (0.5, 0), b2 = (1, 1)
out vec2 wz_coords;
out float bezier_degree; out float bezier_degree;
@ -38,30 +34,6 @@ const float SQRT5 = 2.236068;
#INSERT quadratic_bezier_geometry_functions.glsl #INSERT quadratic_bezier_geometry_functions.glsl
#INSERT scale_and_shift_point_for_frame.glsl #INSERT scale_and_shift_point_for_frame.glsl
mat3 get_xy_to_wz(vec2 b0, vec2 b1, vec2 b2){
// If linear or null, this matrix is not needed
if(bezier_degree < 2) return mat3(1.0);
vec2 inv_col1 = 2 * (b1 - b0);
vec2 inv_col2 = b2 - 2 * b1 + b0;
float inv_det = cross(inv_col1, inv_col2);
mat3 transform = mat3(
inv_col2.y, -inv_col1.y, 0,
-inv_col2.x, inv_col1.x, 0,
0, 0, inv_det
) / inv_det;
mat3 shift = mat3(
1, 0, 0,
0, 1, 0,
-b0.x, -b0.y, 1
);
return transform * shift;
}
void emit_simple_triangle(){ void emit_simple_triangle(){
for(int i = 0; i < 3; i++){ for(int i = 0; i < 3; i++){
color = v_color[i]; color = v_color[i];
@ -75,29 +47,17 @@ void emit_simple_triangle(){
} }
void emit_pentagon(vec2 bp0, vec2 bp1, vec2 bp2, float orientation){ void emit_pentagon(vec2 bp0, vec2 bp1, vec2 bp2){
// Tangent vectors // Tangent vectors
vec2 t01 = normalize(bp1 - bp0); vec2 t01 = normalize(bp1 - bp0);
vec2 t12 = normalize(bp2 - bp1); vec2 t12 = normalize(bp2 - bp1);
// Inside and left turn -> rot right -> -1
// Outside and left turn -> rot left -> +1
// Inside and right turn -> rot left -> +1
// Outside and right turn -> rot right -> -1
float c_orient = (cross(t01, t12) > 0) ? 1 : -1;
c_orient *= orientation;
bool fill_in = (c_orient > 0);
fill_type = fill_in ? FILL_INSIDE : FILL_OUTSIDE;
// float orient = in_or_out * c_orient;
// Normal vectors // Normal vectors
// Rotate tangent vector 90-degrees clockwise // Rotate tangent vector 90-degrees clockwise
// if the curve is positively oriented, otherwise vec2 n01 = vec2(t01.y, -t01.x);
// rotate it 90-degrees counterclockwise vec2 n12 = vec2(t12.y, -t12.x);
vec2 n01 = orientation * vec2(t01.y, -t01.x);
vec2 n12 = orientation * vec2(t12.y, -t12.x); float c_orient = sign(cross(t01, t12));
bool fill_in = (c_orient > 0);
float aaw = anti_alias_width; float aaw = anti_alias_width;
vec2 nudge1 = fill_in ? 0.5 * aaw * (n01 + n12) : vec2(0); vec2 nudge1 = fill_in ? 0.5 * aaw * (n01 + n12) : vec2(0);
@ -113,29 +73,19 @@ void emit_pentagon(vec2 bp0, vec2 bp1, vec2 bp2, float orientation){
if(!fill_in) coords_index_map = int[5](1, 0, 2, 4, 3); if(!fill_in) coords_index_map = int[5](1, 0, 2, 4, 3);
mat3 xy_to_uv = get_xy_to_uv(bp0, bp1); mat3 xy_to_uv = get_xy_to_uv(bp0, bp1);
mat3 xy_to_wz = get_xy_to_wz(bp0, bp1, bp2);
uv_b2 = (xy_to_uv * vec3(bp2, 1)).xy; uv_b2 = (xy_to_uv * vec3(bp2, 1)).xy;
uv_anti_alias_width = anti_alias_width / length(bp1 - bp0); uv_anti_alias_width = anti_alias_width / length(bp1 - bp0);
int nearest_bp_index_map[5] = int[5](0, 0, 1, 2, 2);
for(int i = 0; i < 5; i++){ for(int i = 0; i < 5; i++){
vec2 corner = corners[coords_index_map[i]]; vec2 corner = corners[coords_index_map[i]];
float z = bp[nearest_bp_index_map[i]].z;
uv_coords = (xy_to_uv * vec3(corner, 1)).xy; uv_coords = (xy_to_uv * vec3(corner, 1)).xy;
wz_coords = (xy_to_wz * vec3(corner, 1)).xy;
float z;
// I haven't a clue why an index map doesn't work just // I haven't a clue why an index map doesn't work just
// as well here, but for some reason it doesn't. // as well here, but for some reason it doesn't.
if(i < 2){ if(i < 2) color = v_color[0];
color = v_color[0]; else if(i == 2) color = v_color[1];
z = bp[0].z; else color = v_color[2];
}
else if(i == 2){
color = v_color[1];
z = bp[1].z;
}
else{
color = v_color[2];
z = bp[2].z;
}
gl_Position = vec4( gl_Position = vec4(
scale_and_shift_point_for_frame(vec3(corner, z)), scale_and_shift_point_for_frame(vec3(corner, z)),
1.0 1.0
@ -147,17 +97,16 @@ void emit_pentagon(vec2 bp0, vec2 bp1, vec2 bp2, float orientation){
void main(){ void main(){
float fill_all = v_fill_all[0]; fill_all = v_fill_all[0];
if(fill_all == 1){ if(fill_all == 1){
fill_type = FILL_ALL;
emit_simple_triangle(); emit_simple_triangle();
}else{ return;
}
vec2 new_bp[3]; vec2 new_bp[3];
int n = get_reduced_control_points(bp[0].xy, bp[1].xy, bp[2].xy, new_bp); int n = get_reduced_control_points(bp[0].xy, bp[1].xy, bp[2].xy, new_bp);
bezier_degree = float(n); bezier_degree = float(n);
float orientation = v_orientation[0];
vec2 bp0, bp1, bp2; vec2 bp0, bp1, bp2;
if(n == 0){ if(n == 0){
return; // Don't emit any vertices return; // Don't emit any vertices
@ -172,7 +121,6 @@ void main(){
bp2 = new_bp[2]; bp2 = new_bp[2];
} }
emit_pentagon(bp0, bp1, bp2, orientation); emit_pentagon(bp0, bp1, bp2);
}
} }

View File

@ -2,23 +2,16 @@
in vec3 point; in vec3 point;
in vec4 color; in vec4 color;
// fill_all is 0 or 1 in float fill_all; // Either 0 or 1
in float fill_all;
// orientation is +1 for counterclockwise curves, -1 otherwise
in float orientation;
out vec3 bp; // Bezier control point out vec3 bp; // Bezier control point
out vec4 v_color; out vec4 v_color;
out float v_fill_all; out float v_fill_all;
out float v_orientation;
#INSERT rotate_point_for_frame.glsl #INSERT rotate_point_for_frame.glsl
void main(){ void main(){
bp = rotate_point_for_frame(point); bp = rotate_point_for_frame(point);
v_color = color; v_color = color;
v_fill_all = fill_all; v_fill_all = fill_all;
v_orientation = orientation;
} }

View File

@ -30,25 +30,25 @@ mat3 get_xy_to_uv(vec2 b0, vec2 b1){
// might change. The idea is to inform the caller of the degree, // might change. The idea is to inform the caller of the degree,
// while also passing tangency information in the linear case. // while also passing tangency information in the linear case.
int get_reduced_control_points(vec2 b0, vec2 b1, vec2 b2, out vec2 new_points[3]){ int get_reduced_control_points(vec2 b0, vec2 b1, vec2 b2, out vec2 new_points[3]){
float epsilon = 1e-6; float length_threshold = 1e-6;
float angle_threshold = 1e-3;
vec2 v01 = (b1 - b0); vec2 v01 = (b1 - b0);
vec2 v12 = (b2 - b1); vec2 v12 = (b2 - b1);
bool distinct_01 = length(v01) > epsilon; // v01 is considered nonzero // bool aligned = abs(cross(normalize(v01), normalize(v12))) < angle_threshold;
bool distinct_12 = length(v12) > epsilon; // v12 is considered nonzero bool aligned = acos(dot(normalize(v01), normalize(v12))) < angle_threshold;
bool distinct_01 = length(v01) > length_threshold; // v01 is considered nonzero
bool distinct_12 = length(v12) > length_threshold; // v12 is considered nonzero
int n_uniques = int(distinct_01) + int(distinct_12); int n_uniques = int(distinct_01) + int(distinct_12);
if(n_uniques == 2){
bool linear = dot(normalize(v01), normalize(v12)) > 1 - epsilon; bool quadratic = (n_uniques == 2) && !aligned;
if(linear){ bool linear = (n_uniques == 1) || ((n_uniques == 2) && aligned);
new_points[0] = b0; bool constant = (n_uniques == 0);
new_points[1] = b2; if(quadratic){
return 1;
}else{
new_points[0] = b0; new_points[0] = b0;
new_points[1] = b1; new_points[1] = b1;
new_points[2] = b2; new_points[2] = b2;
return 2; return 2;
} }else if(linear){
}else if(n_uniques == 1){
new_points[0] = b0; new_points[0] = b0;
new_points[1] = b2; new_points[1] = b2;
return 1; return 1;