diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 2711a43a..d326ffdd 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -255,12 +255,15 @@ 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, ctx): + def init_fill_fbo(self, ctx: moderngl.context.Context): # Experimental self.fill_texture = ctx.texture( size=self.get_pixel_shape(), components=4, samples=self.samples, + # Important to make sure floating point (not fixed point) is + # used so that alpha values are not clipped + dtype='f2', ) # TODO, depth buffer is not really used yet fill_depth = ctx.depth_renderbuffer(self.get_pixel_shape(), samples=self.samples) @@ -287,6 +290,7 @@ class Camera(object): void main() { frag_color = texture(Texture, v_textcoord); + frag_color = abs(frag_color); if(frag_color.a == 0) discard; } ''', @@ -451,28 +455,31 @@ class Camera(object): self.set_ctx_clip_plane(shader_wrapper.use_clip_plane) if shader_wrapper.is_fill: - self.render_fill(render_group["vao"], primitive) + self.render_fill(render_group["vao"], primitive, shader_wrapper.vert_indices) else: render_group["vao"].render(primitive) if render_group["single_use"]: self.release_render_group(render_group) - def render_fill(self, vao, render_primitive: int): + def render_fill(self, vao, render_primitive: int, indices: np.ndarray): """ VMobject fill is handled in a special way, where emited triangles must be blended with moderngl.FUNC_SUBTRACT so as to effectively compute a winding number around each pixel. This is rendered to a separate texture, then that texture is overlayed onto the current fbo """ + winding = (len(indices) == 0) + vao.program['winding'].value = winding + if not winding: + vao.render(moderngl.TRIANGLES) + return 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 - vao.render(render_primitive, instances=2) + vao.render(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 @@ -507,14 +514,15 @@ class Camera(object): elif single_use: ibo = self.ctx.buffer(indices.astype(np.uint32)) else: - # The vao.render call is strangely longer - # when an index buffer is used, so if the - # mobject is not changing, meaning only its - # uniforms are being updated, just create - # a larger data array based on the indices - # and don't bother with the ibo - vert_data = vert_data[indices] - ibo = None + ibo = self.ctx.buffer(indices.astype(np.uint32)) + # # The vao.render call is strangely longer + # # when an index buffer is used, so if the + # # mobject is not changing, meaning only its + # # uniforms are being updated, just create + # # a larger data array based on the indices + # # and don't bother with the ibo + # vert_data = vert_data[indices] + # ibo = None vbo = self.ctx.buffer(vert_data) # Program and vertex array diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index aec56291..4d3102fd 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -103,6 +103,7 @@ class VMobject(Mobject): self.flat_stroke = flat_stroke self.use_simple_quadratic_approx = use_simple_quadratic_approx self.anti_alias_width = anti_alias_width + self._use_winding_fill = True self.needs_new_triangulation = True self.triangulation = np.zeros(0, dtype='i4') @@ -128,8 +129,6 @@ class VMobject(Mobject): return super().family_members_with_points() def replicate(self, n: int) -> VGroup: - if self.has_fill(): - self.get_triangulation() return super().replicate(n) def get_grid(self, *args, **kwargs) -> VGroup: @@ -401,6 +400,11 @@ class VMobject(Mobject): def get_joint_type(self) -> float: return self.uniforms["joint_type"] + def use_winding_fill(self, value: bool = True, recurse: bool = True): + for submob in self.get_family(recurse): + submob._use_winding_fill = value + return self + # Points def set_anchors_and_handles( self, @@ -807,11 +811,15 @@ class VMobject(Mobject): # Alignment def align_points(self, vmobject: VMobject): + winding = self._use_winding_fill and vmobject._use_winding_fill + self.use_winding_fill(winding) + vmobject.use_winding_fill(winding) if self.get_num_points() == len(vmobject.get_points()): # If both have fill, and they have the same shape, just # give them the same triangulation so that it's not recalculated # needlessly throughout an animation - if self.has_fill() and vmobject.has_fill() and self.has_same_shape_as(vmobject): + if self._use_winding_fill and self.has_fill() \ + and vmobject.has_fill() and self.has_same_shape_as(vmobject): vmobject.triangulation = self.triangulation return self @@ -898,7 +906,7 @@ class VMobject(Mobject): *args, **kwargs ): super().interpolate(mobject1, mobject2, alpha, *args, **kwargs) - if self.has_fill(): + if self.has_fill() and not self._use_winding_fill: tri1 = mobject1.get_triangulation() tri2 = mobject2.get_triangulation() if not arrays_match(tri1, tri2): @@ -991,11 +999,6 @@ 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") - concave_parts = curve_orientations < 0 # These are the vertices to which we'll apply a polygon triangulation @@ -1108,10 +1111,11 @@ class VMobject(Mobject): def reverse_points(self): # This will reset which anchors are # considered path ends - if not self.has_points(): - return self - inner_ends = self.get_subpath_end_indices()[:-1] - self.data["point"][inner_ends + 1] = self.data["point"][inner_ends + 2] + for mob in self.get_family(): + if not mob.has_points(): + continue + inner_ends = mob.get_subpath_end_indices()[:-1] + mob.data["point"][inner_ends + 1] = mob.data["point"][inner_ends + 2] super().reverse_points() return self @@ -1178,14 +1182,18 @@ 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] fill_datas.append(submob.data[fill_names]) - # Add dummy - fill_datas.append(submob.data[fill_names][-1:]) + if self._use_winding_fill: + # Add dummy + fill_datas.append(submob.data[fill_names][-1:]) + else: + fill_indices.append(submob.get_triangulation()) if submob.has_stroke(): submob.get_joint_products() if submob.stroke_behind: @@ -1200,7 +1208,7 @@ class VMobject(Mobject): shader_wrappers = [ self.back_stroke_shader_wrapper.read_in(back_stroke_data), - self.fill_shader_wrapper.read_in(fill_datas), + self.fill_shader_wrapper.read_in(fill_datas, fill_indices or None), self.stroke_shader_wrapper.read_in(stroke_datas), ] diff --git a/manimlib/shaders/quadratic_bezier_fill/frag.glsl b/manimlib/shaders/quadratic_bezier_fill/frag.glsl index eaf3a8e6..c672455a 100644 --- a/manimlib/shaders/quadratic_bezier_fill/frag.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/frag.glsl @@ -1,7 +1,10 @@ #version 330 +uniform bool winding; + in vec4 color; in float fill_all; +in float orientation; in vec2 uv_coords; out vec4 frag_color; @@ -9,9 +12,14 @@ out vec4 frag_color; void main() { if (color.a == 0) discard; frag_color = color; + + if(winding && orientation > 0) frag_color *= -1; + if (bool(fill_all)) return; float x = uv_coords.x; float y = uv_coords.y; - if(y - x * x < 0) discard; + float Fxy = (y - x * x); + if(!winding && orientation > 0) Fxy *= -1; + if(Fxy < 0) discard; } diff --git a/manimlib/shaders/quadratic_bezier_fill/geom.glsl b/manimlib/shaders/quadratic_bezier_fill/geom.glsl index a4115013..95d18296 100644 --- a/manimlib/shaders/quadratic_bezier_fill/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/geom.glsl @@ -1,23 +1,27 @@ #version 330 layout (triangles) in; -layout (triangle_strip, max_vertices = 3) out; +layout (triangle_strip, max_vertices = 6) out; -uniform float anti_alias_width; -uniform float pixel_size; -uniform vec3 corner; +uniform bool winding; in vec3 verts[3]; in vec4 v_color[3]; in vec3 v_base_point[3]; in float v_vert_index[3]; -in float v_inst_id[3]; out vec4 color; out float fill_all; +out float orientation; // uv space is where the curve coincides with y = x^2 out vec2 uv_coords; +// A quadratic bezier curve with these points coincides with y = x^2 +const vec2 SIMPLE_QUADRATIC[3] = vec2[3]( + vec2(0.0, 0.0), + vec2(0.5, 0), + vec2(1.0, 1.0) +); // Analog of import for manim only #INSERT get_gl_Position.glsl @@ -25,47 +29,56 @@ out vec2 uv_coords; #INSERT finalize_color.glsl -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_triangle(vec3 points[3], vec4 v_color[3]){ + vec3 unit_normal = get_unit_normal(points[0], points[1], points[2]); + orientation = sign(unit_normal.z); + + for(int i = 0; i < 3; i++){ + uv_coords = SIMPLE_QUADRATIC[i]; + color = finalize_color(v_color[i], points[i], unit_normal); + gl_Position = get_gl_Position(points[i]); + EmitVertex(); + } + EndPrimitive(); +} + + +void emit_in_triangle(){ + emit_triangle( + vec3[3](verts[0], verts[1], verts[2]), + vec4[3](v_color[0], v_color[1], v_color[2]) + ); } void main(){ // We use the triangle strip primative, but // actually only need every other strip element - if (int(v_vert_index[0]) % 2 == 1) return; + if (winding && int(v_vert_index[0]) % 2 == 1) return; // Curves are marked as ended when the handle after // the first anchor is set equal to that anchor if (verts[0] == verts[1]) return; - if (v_color[0].a == 0 && v_color[1].a == 0 && v_color[2].a == 0) return; - - vec3 unit_normal = get_unit_normal(verts[0], verts[1], verts[2]); - - if(int(v_inst_id[0]) % 2 == 0){ + vec3 mid_vert; + if(winding){ // Emit main triangle fill_all = 1.0; - uv_coords = vec2(0.0); - emit_vertex_wrapper(v_base_point[0], v_color[0], unit_normal); - emit_vertex_wrapper(verts[0], v_color[0], unit_normal); - emit_vertex_wrapper(verts[2], v_color[2], unit_normal); - }else{ - // Emit edge triangle - fill_all = 0.0; - // A quadratic bezier curve with these points coincides with y = x^2 - vec2 uv_coords_arr[3] = vec2[3]( - vec2(0.0, 0.0), - vec2(0.5, 0), - vec2(1.0, 1.0) + emit_triangle( + vec3[3](v_base_point[0], verts[0], verts[2]), + vec4[3](v_color[1], v_color[0], v_color[2]) ); - for(int i = 0; i < 3; i ++){ - uv_coords = uv_coords_arr[i]; - emit_vertex_wrapper(verts[i], v_color[i], unit_normal); - } + // Edge triangle + fill_all = 0.0; + emit_in_triangle(); + }else{ + // In this case, one should fill all if the vertices are + // not in sequential order + fill_all = float( + (v_vert_index[1] - v_vert_index[0]) != 1.0 || + (v_vert_index[2] - v_vert_index[1]) != 1.0 + ); + emit_in_triangle(); } - EndPrimitive(); } diff --git a/manimlib/shaders/quadratic_bezier_fill/vert.glsl b/manimlib/shaders/quadratic_bezier_fill/vert.glsl index 46d677bd..5818216a 100644 --- a/manimlib/shaders/quadratic_bezier_fill/vert.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/vert.glsl @@ -5,16 +5,13 @@ in vec4 fill_rgba; in vec3 base_point; out vec3 verts; // Bezier control point -out vec4 v_joint_product; out vec4 v_color; out vec3 v_base_point; out float v_vert_index; -out float v_inst_id; void main(){ verts = point; v_color = fill_rgba; v_base_point = base_point; v_vert_index = gl_VertexID; - v_inst_id = gl_InstanceID; } \ No newline at end of file