mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
Make winding fill optional, and make winding additive rather than toggling
This commit is contained in:
parent
e9c70dbfd9
commit
f0df5c759d
5 changed files with 100 additions and 66 deletions
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
]
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Add table
Reference in a new issue