First pass at a winding-based fill approach

This commit is contained in:
Grant Sanderson 2023-01-24 13:29:34 -08:00
parent d01658bc5b
commit 4774d2bc3b
6 changed files with 128 additions and 137 deletions

View file

@ -231,6 +231,7 @@ class Camera(object):
self.init_textures()
self.init_light_source()
self.refresh_perspective_uniforms()
self.init_fill_fbo() # Experimental
# A cached map from mobjects to their associated list of render groups
# so that these render groups are not regenerated unnecessarily for static
# mobjects
@ -254,6 +255,54 @@ class Camera(object):
# This is the frame buffer we'll draw into when emitting frames
self.draw_fbo = self.get_fbo(samples=0)
def init_fill_fbo(self):
# Experimental
self.fill_texture = self.ctx.texture(
size=self.get_pixel_shape(),
components=4,
samples=self.samples,
)
fill_depth = self.ctx.depth_renderbuffer(self.get_pixel_shape(), samples=self.samples)
self.fill_fbo = self.ctx.framebuffer(self.fill_texture, fill_depth)
self.fill_prog = self.ctx.program(
vertex_shader='''
#version 330
in vec2 texcoord;
out vec2 v_textcoord;
void main() {
gl_Position = vec4((2.0 * texcoord - 1.0), 0.0, 1.0);
v_textcoord = texcoord;
}
''',
fragment_shader='''
#version 330
uniform sampler2D Texture;
in vec2 v_textcoord;
out vec4 frag_color;
void main() {
vec4 color = texture(Texture, v_textcoord);
if(color.a == 0) discard;
frag_color = color;
// frag_color = vec4(1, 0, 0, 0.2);
}
''',
)
tid = self.n_textures
self.fill_texture.use(tid)
self.fill_prog['Texture'].value = tid
self.n_textures += 1
verts = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
self.fill_vao = self.ctx.simple_vertex_array(
self.fill_prog,
self.ctx.buffer(verts.astype('f4').tobytes()),
'texcoord',
)
def set_ctx_blending(self, enable: bool = True) -> None:
if enable:
self.ctx.enable(moderngl.BLEND)
@ -400,7 +449,24 @@ class Camera(object):
self.set_shader_uniforms(shader_program, shader_wrapper)
self.set_ctx_depth_test(shader_wrapper.depth_test)
self.set_ctx_clip_plane(shader_wrapper.use_clip_plane)
render_group["vao"].render(int(shader_wrapper.render_primitive))
# TODO
if shader_wrapper.render_to_texture:
self.fill_fbo.clear(0.0, 0.0, 0.0, 0.0)
self.fill_fbo.use()
self.ctx.enable(moderngl.BLEND)
self.ctx.blend_func = moderngl.ONE, moderngl.ONE
self.ctx.blend_equation = moderngl.FUNC_SUBTRACT
render_group["vao"].render(int(shader_wrapper.render_primitive))
self.ctx.blend_func = moderngl.DEFAULT_BLENDING
self.ctx.blend_equation = moderngl.FUNC_ADD
self.fbo.use()
self.fill_texture.use(0)
self.fill_prog['Texture'].value = 0
self.fill_vao.render(moderngl.TRIANGLE_STRIP)
else:
render_group["vao"].render(int(shader_wrapper.render_primitive))
if render_group["single_use"]:
self.release_render_group(render_group)

View file

@ -59,13 +59,12 @@ class VMobject(Mobject):
('stroke_width', np.float32, (1,)),
('joint_product', np.float32, (4,)),
('fill_rgba', np.float32, (4,)),
('orientation', np.float32, (1,)),
('vert_index', np.float32, (1,)),
('base_point', np.float32, (3,)),
])
fill_data_names = ['point', 'fill_rgba', 'orientation', 'vert_index']
fill_data_names = ['point', 'fill_rgba', 'base_point']
stroke_data_names = ['point', 'stroke_rgba', 'stroke_width', 'joint_product']
fill_render_primitive: int = moderngl.TRIANGLES
fill_render_primitive: int = moderngl.TRIANGLE_STRIP
stroke_render_primitive: int = moderngl.TRIANGLE_STRIP
pre_function_handle_to_anchor_scale_factor: float = 0.01
@ -992,10 +991,10 @@ class VMobject(Mobject):
v12s = points[2::2] - points[1::2]
curve_orientations = np.sign(cross2d(v01s, v12s))
# Reset orientation data
self.data["orientation"][1::2, 0] = curve_orientations
if "orientation" in self.locked_data_keys:
self.locked_data_keys.remove("orientation")
# # Reset orientation data
# self.data["orientation"][1::2, 0] = curve_orientations
# if "orientation" in self.locked_data_keys:
# self.locked_data_keys.remove("orientation")
concave_parts = curve_orientations < 0
@ -1121,14 +1120,6 @@ class VMobject(Mobject):
super().set_data(data)
return self
def resize_points(
self,
new_length: int,
resize_func: Callable[[np.ndarray, int], np.ndarray] = resize_array
):
super().resize_points(new_length, resize_func)
self.data["vert_index"][:, 0] = np.arange(new_length)
# TODO, how to be smart about tangents here?
@triggers_refreshed_triangulation
def apply_function(
@ -1160,10 +1151,10 @@ class VMobject(Mobject):
stroke_data = np.zeros(0, dtype=stroke_dtype)
self.fill_shader_wrapper = ShaderWrapper(
vert_data=fill_data,
vert_indices=np.zeros(0, dtype='i4'),
uniforms=self.uniforms,
shader_folder=self.fill_shader_folder,
render_primitive=self.fill_render_primitive,
render_to_texture=True,
)
self.stroke_shader_wrapper = ShaderWrapper(
vert_data=stroke_data,
@ -1187,13 +1178,15 @@ class VMobject(Mobject):
# Build up data lists
fill_datas = []
fill_indices = []
stroke_datas = []
back_stroke_data = []
for submob in family:
if submob.has_fill():
submob.data["base_point"][:] = submob.data["point"][0]
# submob.data["base_color"][:] = submob.data["fill_color"][0]
fill_datas.append(submob.data[fill_names])
fill_indices.append(submob.get_triangulation())
# Add dummy
fill_datas.append(submob.data[fill_names][-1:])
if submob.has_stroke():
submob.get_joint_products()
if submob.stroke_behind:
@ -1208,7 +1201,7 @@ class VMobject(Mobject):
shader_wrappers = [
self.back_stroke_shader_wrapper.read_in(back_stroke_data),
self.fill_shader_wrapper.read_in(fill_datas, fill_indices),
self.fill_shader_wrapper.read_in(fill_datas),
self.stroke_shader_wrapper.read_in(stroke_datas),
]

View file

@ -34,6 +34,7 @@ class ShaderWrapper(object):
depth_test: bool = False,
use_clip_plane: bool = False,
render_primitive: int = moderngl.TRIANGLE_STRIP,
render_to_texture: bool = False,
):
self.vert_data = vert_data
self.vert_indices = vert_indices
@ -44,6 +45,7 @@ class ShaderWrapper(object):
self.depth_test = depth_test
self.use_clip_plane = use_clip_plane
self.render_primitive = str(render_primitive)
self.render_to_texture = render_to_texture
self.init_program_code()
self.refresh_id()

View file

@ -2,29 +2,20 @@
in vec4 color;
in float fill_all; // Either 0 or 1
in float uv_anti_alias_width;
in float orientation;
in vec2 uv_coords;
in float is_linear;
out vec4 frag_color;
float sdf(){
float x0 = uv_coords.x;
float y0 = uv_coords.y;
if(bool(is_linear)) return abs(y0);
float Fxy = y0 - x0 * x0;
if(orientation * Fxy >= 0) return 0.0;
return abs(Fxy) / sqrt(1 + 4 * x0 * x0);
}
void main() {
if (color.a == 0) discard;
frag_color = color;
if (bool(fill_all)) return;
frag_color.a *= smoothstep(1, 0, sdf() / uv_anti_alias_width);
if (orientation == 0) return;
float x0 = uv_coords.x;
float y0 = uv_coords.y;
float Fxy = y0 - x0 * x0;
if(orientation * Fxy < 0) discard;
}

View file

@ -1,129 +1,68 @@
#version 330
layout (triangles) in;
layout (triangle_strip, max_vertices = 5) out;
layout (triangle_strip, max_vertices = 7) out;
uniform float anti_alias_width;
uniform float pixel_size;
uniform vec3 corner;
in vec3 verts[3];
in float v_orientation[3];
in vec4 v_color[3];
in vec3 v_base_point[3];
in float v_vert_index[3];
out vec4 color;
out float fill_all;
out float uv_anti_alias_width;
out float orientation;
// uv space is where the curve coincides with y = x^2
out vec2 uv_coords;
out float is_linear;
const float ANGLE_THRESHOLD = 1e-3;
// Analog of import for manim only
#INSERT get_gl_Position.glsl
#INSERT get_xyz_to_uv.glsl
#INSERT get_unit_normal.glsl
#INSERT finalize_color.glsl
void emit_vertex_wrapper(vec3 point, int index, vec3 unit_normal){
color = finalize_color(v_color[index], point, unit_normal);
void emit_vertex_wrapper(vec3 point, vec4 v_color, vec3 unit_normal){
color = finalize_color(v_color, point, unit_normal);
gl_Position = get_gl_Position(point);
EmitVertex();
}
void emit_simple_triangle(vec3 unit_normal){
for(int i = 0; i < 3; i++){
emit_vertex_wrapper(verts[i], i, unit_normal);
}
EndPrimitive();
}
void emit_pentagon(
// Triangle vertices
vec3 p0,
vec3 p1,
vec3 p2,
// Unit tangent vector
vec3 t01,
vec3 t12,
vec3 unit_normal
){
// Vectors perpendicular to the curve in the plane of the curve
// pointing outside the curve
vec3 p0_perp = cross(t01, unit_normal);
vec3 p2_perp = cross(t12, unit_normal);
float angle = acos(clamp(dot(t01, t12), -1, 1));
is_linear = float(angle < ANGLE_THRESHOLD);
if(bool(is_linear)){
// Cross with unit z vector
p0_perp = normalize(vec3(-t01.y, t01.x, 0));
p2_perp = p0_perp;
}
bool fill_inside = orientation > 0.0;
float aaw = anti_alias_width * pixel_size;
vec3 corners[5] = vec3[5](p0, p0, p1, p2, p2);
if(fill_inside || bool(is_linear)){
// Add buffer outside the curve
corners[0] += aaw * p0_perp;
corners[2] += 0.5 * aaw * (p0_perp + p2_perp);
corners[4] += aaw * p2_perp;
} else{
// Add buffer inside the curve
corners[1] -= aaw * p0_perp;
corners[3] -= aaw * p2_perp;
}
// Compute xy_to_uv matrix, and potentially re-evaluate bezier degree
bool too_steep;
mat4 xyz_to_uv = get_xyz_to_uv(p0, p1, p2, 10.0, too_steep);
if(too_steep) is_linear = 1.0;
uv_anti_alias_width = aaw * length(xyz_to_uv[0].xyz);
for(int i = 0; i < 5; i++){
int j = int[5](0, 0, 1, 2, 2)[i];
vec3 corner = corners[i];
uv_coords = (xyz_to_uv * vec4(corner, 1.0)).xy;
emit_vertex_wrapper(corner, j, unit_normal);
}
EndPrimitive();
}
void main(){
// If vert indices are sequential, don't fill all
fill_all = float(
(v_vert_index[1] - v_vert_index[0]) != 1.0 ||
(v_vert_index[2] - v_vert_index[1]) != 1.0
);
// We use the triangle strip primative, but
// actually only need every other strip element
if (int(v_vert_index[0]) % 2 == 1) return;
vec3 p0 = verts[0];
vec3 p1 = verts[1];
vec3 p2 = verts[2];
vec3 t01 = p1 - p0;
vec3 t12 = p2 - p1;
vec3 unit_normal = normalize(cross(t01, t12));
// Curves are marked as eneded when the handle after
// the first anchor is set equal to that anchor
if (verts[0] == verts[1]) return;
if(bool(fill_all)){
emit_simple_triangle(unit_normal);
return;
}
orientation = v_orientation[1];
vec3 unit_normal = get_unit_normal(verts[0], verts[1], verts[2]);
emit_pentagon(
p0, p1, p2,
normalize(t01),
normalize(t12),
unit_normal
);
// Emit main triangle
orientation = 0.0;
uv_coords = vec2(0.0);
emit_vertex_wrapper(verts[2], v_color[2], unit_normal);
emit_vertex_wrapper(v_base_point[0], v_color[1], unit_normal);
emit_vertex_wrapper(verts[0], v_color[0], unit_normal);
// Emit edge triangle
orientation = 1.0;
uv_coords = vec2(0, 0);
// Two dummies
emit_vertex_wrapper(verts[0], v_color[0], unit_normal);
emit_vertex_wrapper(verts[0], v_color[0], unit_normal);
// Inner corner
uv_coords = vec2(0.5, 0);
emit_vertex_wrapper(verts[1], v_color[1], unit_normal);
// Last corner
uv_coords = vec2(1.0, 1.0);
emit_vertex_wrapper(verts[2], v_color[2], unit_normal);
EndPrimitive();
}

View file

@ -2,17 +2,17 @@
in vec3 point;
in vec4 fill_rgba;
in float orientation;
in float vert_index;
in vec3 base_point;
out vec3 verts; // Bezier control point
out float v_orientation;
out vec4 v_joint_product;
out vec4 v_color;
out vec3 v_base_point;
out float v_vert_index;
void main(){
verts = point;
v_orientation = orientation;
v_color = fill_rgba;
v_vert_index = vert_index;
v_base_point = base_point;
v_vert_index = gl_VertexID;
}