mirror of
https://github.com/3b1b/manim.git
synced 2025-08-21 05:44:04 +00:00
First pass at a polyline implementation for stroke
This commit is contained in:
parent
5aeb457bb1
commit
3ea8393e9a
3 changed files with 94 additions and 151 deletions
|
@ -1,66 +1,18 @@
|
||||||
#version 330
|
#version 330
|
||||||
|
|
||||||
in vec2 uv_coords;
|
in float signed_dist_to_curve;
|
||||||
|
|
||||||
in float uv_stroke_width;
|
in float uv_stroke_width;
|
||||||
in float uv_anti_alias_width;
|
in float uv_anti_alias_width;
|
||||||
in vec4 color;
|
in vec4 color;
|
||||||
|
|
||||||
in float is_linear;
|
|
||||||
|
|
||||||
out vec4 frag_color;
|
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() {
|
void main() {
|
||||||
if (uv_stroke_width == 0) discard;
|
if (uv_stroke_width == 0) discard;
|
||||||
frag_color = color;
|
frag_color = color;
|
||||||
|
|
||||||
// sdf for the region around the curve we wish to color.
|
// sdf for the region around the curve we wish to color.
|
||||||
float signed_dist = dist_to_curve() - 0.5 * uv_stroke_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 *= smoothstep(0.5, -0.5, signed_dist / uv_anti_alias_width);
|
frag_color.a += 0.2; // undo
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
#version 330
|
#version 330
|
||||||
|
|
||||||
layout (triangles) in;
|
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 anti_alias_width;
|
||||||
uniform float flat_stroke;
|
uniform float flat_stroke;
|
||||||
|
@ -17,10 +17,7 @@ in vec4 v_color[3];
|
||||||
out vec4 color;
|
out vec4 color;
|
||||||
out float uv_stroke_width;
|
out float uv_stroke_width;
|
||||||
out float uv_anti_alias_width;
|
out float uv_anti_alias_width;
|
||||||
|
out float signed_dist_to_curve;
|
||||||
out float is_linear;
|
|
||||||
|
|
||||||
out vec2 uv_coords;
|
|
||||||
|
|
||||||
// Codes for joint types
|
// Codes for joint types
|
||||||
const int NO_JOINT = 0;
|
const int NO_JOINT = 0;
|
||||||
|
@ -31,7 +28,8 @@ const int MITER_JOINT = 3;
|
||||||
// When the cosine of the angle between
|
// When the cosine of the angle between
|
||||||
// two vectors is larger than this, we
|
// two vectors is larger than this, we
|
||||||
// consider them aligned
|
// 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);
|
vec3 unit_normal = vec3(0.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
@ -83,73 +81,68 @@ void create_joint(
|
||||||
changing_c1 = static_c1 + shift * unit_tan;
|
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
|
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
|
// 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);
|
vec3 normal = get_joint_unit_normal(joint_product);
|
||||||
// Set global unit normal
|
// Set global unit normal
|
||||||
unit_normal = normal;
|
unit_normal = normal;
|
||||||
// Choose the "outward" normal direction
|
// Choose the "outward" normal direction
|
||||||
if(normal.z < 0) normal *= -1;
|
if(normal.z < 0) normal *= -1;
|
||||||
if(bool(flat_stroke)){
|
if(bool(flat_stroke)){
|
||||||
return buff * normalize(cross(normal, tangent));
|
return mult * normalize(cross(normal, tangent));
|
||||||
}else{
|
}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
|
vec3 point_on_curve(float t){
|
||||||
// emitted as a triangle fan, with vertices vaguely close
|
return verts[0] + 2 * (verts[1] - verts[0]) * t + (verts[0] - 2 * verts[1] + verts[2]) * t * t;
|
||||||
// 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 tangent_on_curve(float t){
|
||||||
vec3 p0,
|
return 2 * (verts[1] + -verts[0]) + 2 * (verts[0] - 2 * verts[1] + verts[2]) * t;
|
||||||
vec3 p1,
|
}
|
||||||
vec3 p2,
|
|
||||||
// Unit tangent vectors at p0 and p2
|
|
||||||
vec3 v01,
|
void emit_point_with_width(
|
||||||
vec3 v12,
|
vec3 point,
|
||||||
// Anti-alias width
|
vec3 tangent,
|
||||||
float aaw,
|
vec4 joint_product,
|
||||||
out vec3 corners[6]
|
float width,
|
||||||
|
vec4 joint_color,
|
||||||
|
float aaw
|
||||||
){
|
){
|
||||||
bool linear = bool(is_linear);
|
vec3 unit_tan = normalize(tangent);
|
||||||
vec4 jp0 = normalized_joint_product(v_joint_product[0]);
|
vec4 njp = normalized_joint_product(joint_product);
|
||||||
vec4 jp2 = normalized_joint_product(v_joint_product[2]);
|
float buff = 0.5 * width + aaw;
|
||||||
vec3 p0_perp = get_perp(0, jp0, p0, v01, aaw);
|
vec3 perp = buff * get_perp(njp, point, unit_tan);
|
||||||
vec3 p2_perp = get_perp(2, jp2, p2, v12, aaw);
|
|
||||||
vec3 p1_perp = 0.5 * (p0_perp + p2_perp);
|
vec3 corners[2] = vec3[2](point + perp, point - perp);
|
||||||
if(linear){
|
create_joint(
|
||||||
p1_perp *= (0.5 * v_stroke_width[1] + aaw) / length(p1_perp);
|
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() {
|
void main() {
|
||||||
|
@ -157,52 +150,51 @@ void main() {
|
||||||
// the first anchor is set equal to that anchor
|
// the first anchor is set equal to that anchor
|
||||||
if (verts[0] == verts[1]) return;
|
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]);
|
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
|
// Compute subdivision
|
||||||
// coincides with y = x^2, between some values x0 and x2. Or, in
|
int n_steps;
|
||||||
// the case of a linear curve just put it on the x-axis
|
if (is_linear){
|
||||||
mat4 xyz_to_uv;
|
n_steps = 2;
|
||||||
float uv_scale_factor;
|
}else{
|
||||||
if(!bool(is_linear)){
|
n_steps = MAX_STEPS; // TODO
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
float scaled_aaw = anti_alias_width * pixel_size;
|
||||||
vec3 corners[6];
|
for (int i = 0; i < MAX_STEPS; i++){
|
||||||
get_corners(p0, p1, p2, v01, v12, scaled_aaw, corners);
|
if (i >= n_steps) break;
|
||||||
|
float t = subdivision[i];
|
||||||
// Emit each corner
|
emit_point_with_width(
|
||||||
float max_sw = max(v_stroke_width[0], v_stroke_width[2]);
|
points[i],
|
||||||
for(int i = 0; i < 6; i++){
|
tangent_on_curve(t),
|
||||||
float stroke_width = v_stroke_width[i / 2];
|
joint_products[i], // TODO
|
||||||
|
mix(v_stroke_width[0], v_stroke_width[2], t),
|
||||||
if(bool(is_linear)){
|
mix(v_color[0], v_color[2], t),
|
||||||
float sign = (i % 2 == 0 ? -1 : 1);
|
scaled_aaw
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
EndPrimitive();
|
EndPrimitive();
|
||||||
}
|
}
|
|
@ -20,8 +20,7 @@ const float STROKE_WIDTH_CONVERSION = 0.01;
|
||||||
|
|
||||||
void main(){
|
void main(){
|
||||||
verts = point;
|
verts = point;
|
||||||
v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width;
|
v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width * mix(frame_scale, 1, is_fixed_in_frame);
|
||||||
v_stroke_width *= mix(frame_scale, 1, is_fixed_in_frame);
|
|
||||||
v_joint_product = joint_product;
|
v_joint_product = joint_product;
|
||||||
v_color = stroke_rgba;
|
v_color = stroke_rgba;
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue