From 3ea8393e9a0f8dbf8554294567b6fbec71660d1c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 31 Jul 2024 15:51:06 -0400 Subject: [PATCH] First pass at a polyline implementation for stroke --- .../shaders/quadratic_bezier_stroke/frag.glsl | 56 +----- .../shaders/quadratic_bezier_stroke/geom.glsl | 186 +++++++++--------- .../shaders/quadratic_bezier_stroke/vert.glsl | 3 +- 3 files changed, 94 insertions(+), 151 deletions(-) diff --git a/manimlib/shaders/quadratic_bezier_stroke/frag.glsl b/manimlib/shaders/quadratic_bezier_stroke/frag.glsl index fb9a483e..a8170ec5 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/frag.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/frag.glsl @@ -1,66 +1,18 @@ #version 330 -in vec2 uv_coords; - +in float signed_dist_to_curve; in float uv_stroke_width; in float uv_anti_alias_width; in vec4 color; -in float is_linear; - out vec4 frag_color; -const float QUICK_DIST_WIDTH = 0.2; - -float dist_to_curve(){ - // In the linear case, the curve will have - // been set to equal the x axis - if(bool(is_linear)) return abs(uv_coords.y); - - // Otherwise, find the distance from uv_coords to the curve y = x^2 - float x0 = uv_coords.x; - float y0 = uv_coords.y; - - // This is a quick approximation for computing - // the distance to the curve. - // Evaluate F(x, y) = y - x^2 - // divide by its gradient's magnitude - float Fxy = y0 - x0 * x0; - float approx_dist = abs(Fxy) * inversesqrt(1.0 + 4 * x0 * x0); - if(approx_dist < QUICK_DIST_WIDTH) return approx_dist; - - // Otherwise, solve for the minimal distance. - // The distance squared between (x0, y0) and a point (x, x^2) looks like - // - // (x0 - x)^2 + (y0 - x^2)^2 = x^4 + (1 - 2y0)x^2 - 2x0 * x + (x0^2 + y0^2) - // - // Setting the derivative equal to zero (and rescaling) looks like - // - // x^3 + (0.5 - y0) * x - 0.5 * x0 = 0 - // - // Adapted from https://www.shadertoy.com/view/ws3GD7 - x0 = abs(x0); - float p = (0.5 - y0) / 3.0; // p / 3 in usual Cardano's formula notation - float q = 0.25 * x0; // -q / 2 in usual Cardano's formula notation - float disc = q*q + p*p*p; - float r = sqrt(abs(disc)); - - float x = (disc > 0.0) ? - // 1 root - pow(q + r, 1.0 / 3.0) + pow(abs(q - r), 1.0 / 3.0) * sign(-p) : - // 3 roots - 2.0 * cos(atan(r, q) / 3.0) * sqrt(-p); - - return length(vec2(x0 - x, y0 - x * x)); -} - - void main() { if (uv_stroke_width == 0) discard; frag_color = color; // sdf for the region around the curve we wish to color. - float signed_dist = dist_to_curve() - 0.5 * uv_stroke_width; - - frag_color.a *= smoothstep(0.5, -0.5, signed_dist / uv_anti_alias_width); + float signed_dist_to_region = abs(signed_dist_to_curve) - 0.5 * uv_stroke_width; + frag_color.a *= smoothstep(1.0, 0.0, signed_dist_to_region / uv_anti_alias_width); + frag_color.a += 0.2; // undo } \ No newline at end of file diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index 937674bc..7c4a075d 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -1,7 +1,7 @@ #version 330 layout (triangles) in; -layout (triangle_strip, max_vertices = 6) out; +layout (triangle_strip, max_vertices = 32) out; // Related to MAX_STEPS below uniform float anti_alias_width; uniform float flat_stroke; @@ -17,10 +17,7 @@ in vec4 v_color[3]; out vec4 color; out float uv_stroke_width; out float uv_anti_alias_width; - -out float is_linear; - -out vec2 uv_coords; +out float signed_dist_to_curve; // Codes for joint types const int NO_JOINT = 0; @@ -31,7 +28,8 @@ const int MITER_JOINT = 3; // When the cosine of the angle between // two vectors is larger than this, we // consider them aligned -const float COS_THRESHOLD = 0.99; +const float COS_THRESHOLD = 0.999; +const int MAX_STEPS = 16; vec3 unit_normal = vec3(0.0, 0.0, 1.0); @@ -83,73 +81,68 @@ void create_joint( changing_c1 = static_c1 + shift * unit_tan; } -vec3 get_perp(int index, vec4 joint_product, vec3 point, vec3 tangent, float aaw){ +vec3 get_perp(vec4 joint_product, vec3 point, vec3 tangent){ /* Perpendicular vectors to the left of the curve */ - float buff = 0.5 * v_stroke_width[index] + aaw; // Add correction for sharp angles to prevent weird bevel effects - if(joint_product.w < -0.75) buff *= 4 * (joint_product.w + 1.0); + float mult = 1.0; + if(joint_product.w < -0.75) mult *= 4 * (joint_product.w + 1.0); vec3 normal = get_joint_unit_normal(joint_product); // Set global unit normal unit_normal = normal; // Choose the "outward" normal direction if(normal.z < 0) normal *= -1; if(bool(flat_stroke)){ - return buff * normalize(cross(normal, tangent)); + return mult * normalize(cross(normal, tangent)); }else{ - return buff * normalize(cross(camera_position - point, tangent)); + return mult * normalize(cross(camera_position - point, tangent)); } } -// This function is responsible for finding the corners of -// a bounding region around the bezier curve, which can be -// emitted as a triangle fan, with vertices vaguely close -// to control points so that the passage of vert data to -// frag shaders is most natural. -void get_corners( - // Control points for a bezier curve - vec3 p0, - vec3 p1, - vec3 p2, - // Unit tangent vectors at p0 and p2 - vec3 v01, - vec3 v12, - // Anti-alias width - float aaw, - out vec3 corners[6] + +vec3 point_on_curve(float t){ + return verts[0] + 2 * (verts[1] - verts[0]) * t + (verts[0] - 2 * verts[1] + verts[2]) * t * t; +} + + +vec3 tangent_on_curve(float t){ + return 2 * (verts[1] + -verts[0]) + 2 * (verts[0] - 2 * verts[1] + verts[2]) * t; +} + + +void emit_point_with_width( + vec3 point, + vec3 tangent, + vec4 joint_product, + float width, + vec4 joint_color, + float aaw ){ - bool linear = bool(is_linear); - vec4 jp0 = normalized_joint_product(v_joint_product[0]); - vec4 jp2 = normalized_joint_product(v_joint_product[2]); - vec3 p0_perp = get_perp(0, jp0, p0, v01, aaw); - vec3 p2_perp = get_perp(2, jp2, p2, v12, aaw); - vec3 p1_perp = 0.5 * (p0_perp + p2_perp); - if(linear){ - p1_perp *= (0.5 * v_stroke_width[1] + aaw) / length(p1_perp); + vec3 unit_tan = normalize(tangent); + vec4 njp = normalized_joint_product(joint_product); + float buff = 0.5 * width + aaw; + vec3 perp = buff * get_perp(njp, point, unit_tan); + + vec3 corners[2] = vec3[2](point + perp, point - perp); + create_joint( + njp, unit_tan, length(perp), + corners[0], corners[0], + corners[1], corners[1] + ); + + color = finalize_color(joint_color, point, unit_normal); + uv_anti_alias_width = aaw; + uv_stroke_width = width; + + // Emit two corners + for(int i = 0; i < 2; i++){ + float sign = i % 2 == 0 ? -1 : 1; + signed_dist_to_curve = sign * buff; + emit_gl_Position(corners[i]); + EmitVertex(); } - // The order of corners should be for a triangle_strip. - vec3 c0 = p0 + p0_perp; - vec3 c1 = p0 - p0_perp; - vec3 c2 = p1 + p1_perp; - vec3 c3 = p1 - p1_perp; - vec3 c4 = p2 + p2_perp; - vec3 c5 = p2 - p2_perp; - // Move the inner middle control point to make - // room for the curve - // float orientation = dot(unit_normal, v_joint_product[1].xyz); - float orientation = v_joint_product[1].z; - if(!linear && orientation >= 0.0) c2 = 0.5 * (c0 + c4); - else if(!linear && orientation < 0.0) c3 = 0.5 * (c1 + c5); - - // Account for previous and next control points - if(bool(flat_stroke)){ - create_joint(jp0, v01, length(p0_perp), c1, c1, c0, c0); - create_joint(jp2, -v12, length(p2_perp), c5, c5, c4, c4); - } - - corners = vec3[6](c0, c1, c2, c3, c4, c5); } void main() { @@ -157,52 +150,51 @@ void main() { // the first anchor is set equal to that anchor if (verts[0] == verts[1]) return; - vec3 p0 = verts[0]; - vec3 p1 = verts[1]; - vec3 p2 = verts[2]; - vec3 v01 = normalize(p1 - p0); - vec3 v12 = normalize(p2 - p1); - vec4 jp1 = normalized_joint_product(v_joint_product[1]); - is_linear = float(jp1.w > COS_THRESHOLD); + bool is_linear = jp1.w > COS_THRESHOLD; // TODO, something with this - // We want to change the coordinates to a space where the curve - // coincides with y = x^2, between some values x0 and x2. Or, in - // the case of a linear curve just put it on the x-axis - mat4 xyz_to_uv; - float uv_scale_factor; - if(!bool(is_linear)){ - bool too_steep; - xyz_to_uv = get_xyz_to_uv(p0, p1, p2, 2.0, too_steep); - is_linear = float(too_steep); - uv_scale_factor = length(xyz_to_uv[0].xyz); + // Compute subdivision + int n_steps; + if (is_linear){ + n_steps = 2; + }else{ + n_steps = MAX_STEPS; // TODO } + float subdivision[MAX_STEPS]; + vec3 points[MAX_STEPS]; + for(int i = 0; i < MAX_STEPS; i++){ + if (i >= n_steps) break; + subdivision[i] = float(i) / (n_steps - 1); + points[i] = point_on_curve(subdivision[i]); + } + + // Compute joint products + vec4 joint_products[MAX_STEPS]; + joint_products[0] = v_joint_product[0]; + joint_products[0].xyz *= -1; + joint_products[n_steps - 1] = v_joint_product[2]; + for (int i = 1; i < MAX_STEPS; i++){ + if (i >= n_steps - 1) break; + vec3 v1 = points[i] - points[i - 1]; + vec3 v2 = points[i + 1] - points[i]; + joint_products[i].xyz = cross(v1, v2); + joint_products[i].w = dot(v1, v2); + } + + // Intermediate points float scaled_aaw = anti_alias_width * pixel_size; - vec3 corners[6]; - get_corners(p0, p1, p2, v01, v12, scaled_aaw, corners); - - // Emit each corner - float max_sw = max(v_stroke_width[0], v_stroke_width[2]); - for(int i = 0; i < 6; i++){ - float stroke_width = v_stroke_width[i / 2]; - - if(bool(is_linear)){ - float sign = (i % 2 == 0 ? -1 : 1); - // In this case, we only really care about - // the v coordinate - uv_coords = vec2(0, sign * (0.5 * stroke_width + scaled_aaw)); - uv_anti_alias_width = scaled_aaw; - uv_stroke_width = stroke_width; - }else{ - uv_coords = (xyz_to_uv * vec4(corners[i], 1.0)).xy; - uv_stroke_width = uv_scale_factor * stroke_width; - uv_anti_alias_width = uv_scale_factor * scaled_aaw; - } - - color = finalize_color(v_color[i / 2], corners[i], unit_normal); - emit_gl_Position(corners[i]); - EmitVertex(); + for (int i = 0; i < MAX_STEPS; i++){ + if (i >= n_steps) break; + float t = subdivision[i]; + emit_point_with_width( + points[i], + tangent_on_curve(t), + joint_products[i], // TODO + mix(v_stroke_width[0], v_stroke_width[2], t), + mix(v_color[0], v_color[2], t), + scaled_aaw + ); } EndPrimitive(); } \ No newline at end of file diff --git a/manimlib/shaders/quadratic_bezier_stroke/vert.glsl b/manimlib/shaders/quadratic_bezier_stroke/vert.glsl index ea290bfa..eebf7f53 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/vert.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/vert.glsl @@ -20,8 +20,7 @@ const float STROKE_WIDTH_CONVERSION = 0.01; void main(){ verts = point; - v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width; - v_stroke_width *= mix(frame_scale, 1, is_fixed_in_frame); + v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width * mix(frame_scale, 1, is_fixed_in_frame); v_joint_product = joint_product; v_color = stroke_rgba; } \ No newline at end of file