From 4774d2bc3b34792c7f551bfaebb4230e9b6df779 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 13:29:34 -0800 Subject: [PATCH] First pass at a winding-based fill approach --- manimlib/camera/camera.py | 68 ++++++++- manimlib/mobject/types/vectorized_mobject.py | 33 ++--- manimlib/shader_wrapper.py | 2 + .../shaders/quadratic_bezier_fill/frag.glsl | 23 +--- .../shaders/quadratic_bezier_fill/geom.glsl | 129 +++++------------- .../shaders/quadratic_bezier_fill/vert.glsl | 10 +- 6 files changed, 128 insertions(+), 137 deletions(-) diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index e6f4a76a..43e97d73 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -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) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index d3061ff6..0e9be0bc 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -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), ] diff --git a/manimlib/shader_wrapper.py b/manimlib/shader_wrapper.py index 2d225a62..da11836b 100644 --- a/manimlib/shader_wrapper.py +++ b/manimlib/shader_wrapper.py @@ -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() diff --git a/manimlib/shaders/quadratic_bezier_fill/frag.glsl b/manimlib/shaders/quadratic_bezier_fill/frag.glsl index e5341459..8a8136b2 100644 --- a/manimlib/shaders/quadratic_bezier_fill/frag.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/frag.glsl @@ -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; + } diff --git a/manimlib/shaders/quadratic_bezier_fill/geom.glsl b/manimlib/shaders/quadratic_bezier_fill/geom.glsl index 256ac4d5..6550f7ff 100644 --- a/manimlib/shaders/quadratic_bezier_fill/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/geom.glsl @@ -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(); } diff --git a/manimlib/shaders/quadratic_bezier_fill/vert.glsl b/manimlib/shaders/quadratic_bezier_fill/vert.glsl index 3d2e2c9a..8fa8f80f 100644 --- a/manimlib/shaders/quadratic_bezier_fill/vert.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/vert.glsl @@ -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; } \ No newline at end of file