Fix bugs in stroke shader for 3d scenes

This commit is contained in:
Grant Sanderson 2021-01-08 22:28:34 -08:00
parent f47f848236
commit 632c35daef
5 changed files with 129 additions and 100 deletions

View file

@ -5,18 +5,21 @@
const vec2 DEFAULT_FRAME_SHAPE = vec2(8.0 * 16.0 / 9.0, 8.0);
float perspective_scale_factor(float z, float focal_distance){
// TODO, what happens when focal_distance < z?
return focal_distance / (focal_distance - z);
}
vec4 get_gl_Position(vec3 point){
vec4 result = vec4(point, 1.0);
if(!bool(is_fixed_in_frame)){
result.x *= 2.0 / frame_shape.x;
result.y *= 2.0 / frame_shape.y;
result.z *= 2.0 / frame_shape.y; // Should we give the frame a z shape? Does that make sense?
result.z /= focal_distance;
result.xy /= max(1.0 - result.z, 0.0);
// Todo, does this discontinuity add weirdness? Theoretically, by this result,
// the z-coordiante of gl_Position only matter for z-indexing. The reason
// for thie line is to avoid agressive clipping of distant points.
if(result.z < 0) result.z *= 0.1;
result.xy *= perspective_scale_factor(result.z, focal_distance);
// TODO, what's the better way to do this?
// This is to keep vertices too far out of frame from getting cut.
result.z *= 0.01;
} else{
result.x *= 2.0 / DEFAULT_FRAME_SHAPE.x;
result.y *= 2.0 / DEFAULT_FRAME_SHAPE.y;

View file

@ -2,6 +2,26 @@ float cross2d(vec2 v, vec2 w){
return v.x * w.y - w.x * v.y;
}
mat3 get_xy_to_uv(vec2 b0, vec2 b1){
mat3 shift = mat3(
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
-b0.x, -b0.y, 1.0
);
float sf = length(b1 - b0);
vec2 I = (b1 - b0) / sf;
vec2 J = vec2(-I.y, I.x);
mat3 rotate = mat3(
I.x, J.x, 0.0,
I.y, J.y, 0.0,
0.0, 0.0, 1.0
);
return (1 / sf) * rotate * shift;
}
// Orthogonal matrix to convert to a uv space defined so that
// b0 goes to [0, 0] and b1 goes to [1, 0]
mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 unit_normal){
@ -19,10 +39,10 @@ mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 unit_normal){
// Transpose (hence inverse) of matrix taking
// i-hat to I, k-hat to unit_normal, and j-hat to their cross
mat4 rotate = mat4(
I.x, J.x, K.x, 0,
I.y, J.y, K.y, 0,
I.z, J.z, K.z, 0,
0, 0, 0 , 1
I.x, J.x, K.x, 0.0,
I.y, J.y, K.y, 0.0,
I.z, J.z, K.z, 0.0,
0.0, 0.0, 0.0, 1.0
);
return (1 / scale_factor) * rotate * shift;
}

View file

@ -44,7 +44,9 @@ float modify_distance_for_endpoints(vec2 p, float dist, float t){
// Dist for intersection of two lines
float bevel_d = max(abs(p.y), abs((rot * p).y));
// Dist for union of this intersection with the real curve
return min(dist, bevel_d);
// intersected with radius 2 away from curve to smooth out
// really sharp corners
return max(min(dist, bevel_d), dist / 2);
}
// Otherwise, start will be rounded off
}else if(t == 1){
@ -71,7 +73,7 @@ float modify_distance_for_endpoints(vec2 p, float dist, float t){
abs(cross2d(p - uv_b2, v21_unit)),
abs(cross2d((rot * (p - uv_b2)), v21_unit))
);
return min(dist, bevel_d);
return max(min(dist, bevel_d), dist / 2);
}
// Otherwise, end will be rounded off
}

View file

@ -9,6 +9,7 @@ uniform float focal_distance;
uniform float is_fixed_in_frame;
uniform float anti_alias_width;
uniform float flat_stroke;
//Needed for lighting
uniform vec3 light_source_position;
@ -45,6 +46,7 @@ const float AUTO_JOINT = 0;
const float ROUND_JOINT = 1;
const float BEVEL_JOINT = 2;
const float MITER_JOINT = 3;
const float PI = 3.141592653;
#INSERT quadratic_bezier_geometry_functions.glsl
@ -53,57 +55,46 @@ const float MITER_JOINT = 3;
#INSERT add_light.glsl
void flatten_points(in vec3[3] points, out vec3[3] flat_points){
void flatten_points(in vec3[3] points, out vec2[3] flat_points){
for(int i = 0; i < 3; i++){
flat_points[i] = points[i];
flat_points[i].z = 0;
float sf = perspective_scale_factor(points[i].z, focal_distance);
flat_points[i] = sf * points[i].xy;
}
}
float angle_between_vectors(vec3 v1, vec3 v2, vec3 normal){
float angle_between_vectors(vec2 v1, vec2 v2){
float v1_norm = length(v1);
float v2_norm = length(v2);
if(v1_norm == 0 || v2_norm == 0) return 0;
vec3 nv1 = v1 / v1_norm;
vec3 nv2 = v2 / v2_norm;
// float signed_area = clamp(dot(cross(nv1, nv2), normal), -1, 1);
// return asin(signed_area);
float unsigned_angle = acos(clamp(dot(nv1, nv2), -1, 1));
float sn = sign(dot(cross(nv1, nv2), normal));
return sn * unsigned_angle;
float dp = dot(v1, v2) / (v1_norm * v2_norm);
float angle = acos(clamp(dp, -1.0, 1.0));
float sn = sign(cross2d(v1, v2));
return sn * angle;
}
bool find_intersection(vec3 p0, vec3 v0, vec3 p1, vec3 v1, vec3 normal, out vec3 intersection){
bool find_intersection(vec2 p0, vec2 v0, vec2 p1, vec2 v1, out vec2 intersection){
// Find the intersection of a line passing through
// p0 in the direction v0 and one passing through p1 in
// the direction p1.
// That is, find a solutoin to p0 + v0 * t = p1 + v1 * s
// float det = -v0.x * v1.y + v1.x * v0.y;
float det = dot(cross(v1, v0), normal);
if(det == 0){
// intersection = p0;
return false;
}
float t = dot(cross(p0 - p1, v1), normal) / det;
float det = -v0.x * v1.y + v1.x * v0.y;
if(det == 0) return false;
float t = cross2d(p0 - p1, v1) / det;
intersection = p0 + v0 * t;
return true;
}
void create_joint(float angle, vec3 unit_tan, float buff, float should_bevel,
vec3 static_c0, out vec3 changing_c0,
vec3 static_c1, out vec3 changing_c1){
void create_joint(float angle, vec2 unit_tan, float buff,
vec2 static_c0, out vec2 changing_c0,
vec2 static_c1, out vec2 changing_c1){
float shift;
bool miter = (
(joint_type == AUTO_JOINT && abs(angle) > 2.8 && should_bevel == 1) ||
(joint_type == MITER_JOINT)
);
if(abs(angle) < 1e-3){
// No joint
shift = 0;
}else if(miter){
}else if(joint_type == MITER_JOINT){
shift = buff * (-1.0 - cos(angle)) / sin(angle);
}else{
// For a Bevel joint
@ -117,67 +108,64 @@ void create_joint(float angle, vec3 unit_tan, float buff, float should_bevel,
// This function is responsible for finding the corners of
// a bounding region around the bezier curve, which can be
// emitted as a triangle fan
int get_corners(vec3 controls[3], vec3 normal, int degree, out vec3 corners[5]){
vec3 p0 = controls[0];
vec3 p1 = controls[1];
vec3 p2 = controls[2];
int get_corners(vec2 controls[3], int degree, float stroke_widths[3], out vec2 corners[5]){
vec2 p0 = controls[0];
vec2 p1 = controls[1];
vec2 p2 = controls[2];
// Unit vectors for directions between control points
vec3 v10 = normalize(p0 - p1);
vec3 v12 = normalize(p2 - p1);
vec3 v01 = -v10;
vec3 v21 = -v12;
vec2 v10 = normalize(p0 - p1);
vec2 v12 = normalize(p2 - p1);
vec2 v01 = -v10;
vec2 v21 = -v12;
vec3 p0_perp = cross(normal, v01); // Pointing to the left of the curve from p0
vec3 p2_perp = cross(normal, v12); // Pointing to the left of the curve from p2
vec2 p0_perp = vec2(-v01.y, v01.x); // Pointing to the left of the curve from p0
vec2 p2_perp = vec2(-v12.y, v12.x); // Pointing to the left of the curve from p2
// aaw is the added width given around the polygon for antialiasing.
// In case the normal is faced away from (0, 0, 1), the vector to the
// camera, this is scaled up.
float aaw = anti_alias_width;
float buff0 = 0.5 * v_stroke_width[0] + aaw;
float buff2 = 0.5 * v_stroke_width[2] + aaw;
float buff0 = 0.5 * stroke_widths[0] + aaw;
float buff2 = 0.5 * stroke_widths[2] + aaw;
float aaw0 = (1 - has_prev) * aaw;
float aaw2 = (1 - has_next) * aaw;
vec3 c0 = p0 - buff0 * p0_perp + aaw0 * v10;
vec3 c1 = p0 + buff0 * p0_perp + aaw0 * v10;
vec3 c2 = p2 + buff2 * p2_perp + aaw2 * v12;
vec3 c3 = p2 - buff2 * p2_perp + aaw2 * v12;
vec2 c0 = p0 - buff0 * p0_perp + aaw0 * v10;
vec2 c1 = p0 + buff0 * p0_perp + aaw0 * v10;
vec2 c2 = p2 + buff2 * p2_perp + aaw2 * v12;
vec2 c3 = p2 - buff2 * p2_perp + aaw2 * v12;
// Account for previous and next control points
if(has_prev > 0) create_joint(angle_from_prev, v01, buff0, bevel_start, c0, c0, c1, c1);
if(has_next > 0) create_joint(angle_to_next, v21, buff2, bevel_end, c3, c3, c2, c2);
if(has_prev > 0) create_joint(angle_from_prev, v01, buff0, c0, c0, c1, c1);
if(has_next > 0) create_joint(angle_to_next, v21, buff2, c3, c3, c2, c2);
// Linear case is the simplest
if(degree == 1){
// The order of corners should be for a triangle_strip. Last entry is a dummy
corners = vec3[5](c0, c1, c3, c2, vec3(0.0));
corners = vec2[5](c0, c1, c3, c2, vec2(0.0));
return 4;
}
// Otherwise, form a pentagon around the curve
float orientation = sign(dot(cross(v01, v12), normal)); // Positive for ccw curves
if(orientation > 0) corners = vec3[5](c0, c1, p1, c2, c3);
else corners = vec3[5](c1, c0, p1, c3, c2);
float orientation = sign(cross2d(v01, v12)); // Positive for ccw curves
if(orientation > 0) corners = vec2[5](c0, c1, p1, c2, c3);
else corners = vec2[5](c1, c0, p1, c3, c2);
// Replace corner[2] with convex hull point accounting for stroke width
find_intersection(corners[0], v01, corners[4], v21, normal, corners[2]);
find_intersection(corners[0], v01, corners[4], v21, corners[2]);
return 5;
}
void set_adjascent_info(vec3 c0, vec3 tangent,
void set_adjascent_info(vec2 c0, vec2 tangent,
int degree,
vec3 normal,
vec3 adj[3],
vec2 adj[3],
out float bevel,
out float angle
){
vec3 new_adj[3];
float adj_degree = get_reduced_control_points(adj, new_adj);
// Check if adj_degree is zero?
angle = angle_between_vectors(c0 - new_adj[1], tangent, normal);
bool linear_adj = (angle_between_vectors(adj[1] - adj[0], adj[2] - adj[1]) < 1e-3);
angle = angle_between_vectors(c0 - adj[1], tangent);
// Decide on joint type
bool one_linear = (degree == 1 || adj_degree == 1.0);
bool one_linear = (degree == 1 || linear_adj);
bool should_bevel = (
(joint_type == AUTO_JOINT && one_linear) ||
joint_type == BEVEL_JOINT
@ -186,24 +174,24 @@ void set_adjascent_info(vec3 c0, vec3 tangent,
}
void find_joint_info(vec3 controls[3], vec3 prev[3], vec3 next[3], int degree, vec3 normal){
float tol = 1e-8;
void find_joint_info(vec2 controls[3], vec2 prev[3], vec2 next[3], int degree){
float tol = 1e-6;
// Made as floats not bools so they can be passed to the frag shader
has_prev = float(distance(prev[2], controls[0]) < tol);
has_next = float(distance(next[0], controls[2]) < tol);
if(bool(has_prev)){
vec3 tangent = controls[1] - controls[0];
vec2 tangent = controls[1] - controls[0];
set_adjascent_info(
controls[0], tangent, degree, normal, prev,
controls[0], tangent, degree, prev,
bevel_start, angle_from_prev
);
}
if(bool(has_next)){
vec3 tangent = controls[1] - controls[2];
vec2 tangent = controls[1] - controls[2];
set_adjascent_info(
controls[2], tangent, degree, normal, next,
controls[2], tangent, degree, next,
bevel_end, angle_to_next
);
angle_to_next *= -1;
@ -212,45 +200,59 @@ void find_joint_info(vec3 controls[3], vec3 prev[3], vec3 next[3], int degree, v
void main() {
// Convert control points to a standard form if they are linear or null
vec3 controls[3];
vec3 prev[3];
vec3 next[3];
bezier_degree = get_reduced_control_points(vec3[3](bp[0], bp[1], bp[2]), controls);
if(bezier_degree == 0.0) return; // Null curve
int degree = int(bezier_degree);
get_reduced_control_points(vec3[3](prev_bp[0], prev_bp[1], prev_bp[2]), prev);
get_reduced_control_points(vec3[3](next_bp[0], next_bp[1], next_bp[2]), next);
// Adjust stroke width based on distance from the camera
float scaled_strokes[3];
for(int i = 0; i < 3; i++){
float sf = perspective_scale_factor(controls[i].z, focal_distance);
if(bool(flat_stroke)){
sf *= abs(dot(v_global_unit_normal[i], vec3(0.0, 0.0, 1.0)));
}
scaled_strokes[i] = v_stroke_width[i] * sf;
}
// Control points are projected to the xy plane before drawing, which in turn
// gets tranlated to a uv plane. The z-coordinate information will be remembered
// by what's sent out to gl_Position, and by how it affects the lighting and stroke width
vec3 flat_controls[3];
vec3 flat_prev[3];
vec3 flat_next[3];
vec2 flat_controls[3];
vec2 flat_prev[3];
vec2 flat_next[3];
flatten_points(controls, flat_controls);
flatten_points(vec3[3](prev_bp[0], prev_bp[1], prev_bp[2]), flat_prev);
flatten_points(vec3[3](next_bp[0], next_bp[1], next_bp[2]), flat_next);
vec3 k_hat = vec3(0.0, 0.0, 1.0);
flatten_points(prev, flat_prev);
flatten_points(next, flat_next);
// Null curve
if(degree == 0) return;
find_joint_info(flat_controls, flat_prev, flat_next, degree, k_hat);
// Find uv conversion matrix
mat4 xyz_to_uv = get_xyz_to_uv(flat_controls[0], flat_controls[1], k_hat);
float scale_factor = length(flat_controls[1] - flat_controls[0]);
uv_anti_alias_width = anti_alias_width / scale_factor;
uv_b2 = (xyz_to_uv * vec4(controls[2].xy, 0.0, 1.0)).xy;
find_joint_info(flat_controls, flat_prev, flat_next, degree);
// Corners of a bounding region around curve
vec3 corners[5];
int n_corners = get_corners(flat_controls, k_hat, degree, corners);
vec2 corners[5];
int n_corners = get_corners(flat_controls, degree, scaled_strokes, corners);
int index_map[5] = int[5](0, 0, 1, 2, 2);
if(n_corners == 4) index_map[2] = 2;
// Find uv conversion matrix
mat3 xy_to_uv = get_xy_to_uv(flat_controls[0], flat_controls[1]);
float scale_factor = length(flat_controls[1] - flat_controls[0]);
uv_anti_alias_width = anti_alias_width / scale_factor;
uv_b2 = (xy_to_uv * vec3(flat_controls[2], 1.0)).xy;
// Emit each corner
for(int i = 0; i < n_corners; i++){
uv_coords = (xyz_to_uv * vec4(corners[i], 1.0)).xy;
uv_stroke_width = v_stroke_width[index_map[i]] / scale_factor;
uv_coords = (xy_to_uv * vec3(corners[i], 1.0)).xy;
uv_stroke_width = scaled_strokes[index_map[i]] / scale_factor;
// Apply some lighting to the color before sending out.
vec3 xyz_coords = vec3(corners[i].xy, controls[index_map[i]].z);
// vec3 xyz_coords = vec3(corners[i], controls[index_map[i]].z);
vec3 xyz_coords = vec3(corners[i], controls[index_map[i]].z);
color = add_light(
v_color[index_map[i]],
xyz_coords,
@ -259,7 +261,10 @@ void main() {
gloss,
shadow
);
gl_Position = get_gl_Position(xyz_coords);
gl_Position = vec4(
get_gl_Position(vec3(corners[i], 0.0)).xy,
get_gl_Position(controls[index_map[i]]).zw
);
EmitVertex();
}
EndPrimitive();

View file

@ -2,7 +2,6 @@
uniform mat4 to_screen_space;
uniform float is_fixed_in_frame;
uniform float focal_distance;
in vec3 point;
in vec3 prev_point;