mirror of
https://github.com/3b1b/manim.git
synced 2025-08-02 19:46:21 +08:00
First pass at a polyline implementation for stroke
This commit is contained in:
@ -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
|
||||
}
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
Reference in New Issue
Block a user