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
|
# This is the frame buffer we'll draw into when emitting frames
|
||||||
self.draw_fbo = self.get_fbo(samples=0)
|
self.draw_fbo = self.get_fbo(samples=0)
|
||||||
|
|
||||||
def init_fill_fbo(self, ctx):
|
def init_fill_fbo(self, ctx: moderngl.context.Context):
|
||||||
# Experimental
|
# Experimental
|
||||||
self.fill_texture = ctx.texture(
|
self.fill_texture = ctx.texture(
|
||||||
size=self.get_pixel_shape(),
|
size=self.get_pixel_shape(),
|
||||||
components=4,
|
components=4,
|
||||||
samples=self.samples,
|
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
|
# TODO, depth buffer is not really used yet
|
||||||
fill_depth = ctx.depth_renderbuffer(self.get_pixel_shape(), samples=self.samples)
|
fill_depth = ctx.depth_renderbuffer(self.get_pixel_shape(), samples=self.samples)
|
||||||
|
@ -287,6 +290,7 @@ class Camera(object):
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
frag_color = texture(Texture, v_textcoord);
|
frag_color = texture(Texture, v_textcoord);
|
||||||
|
frag_color = abs(frag_color);
|
||||||
if(frag_color.a == 0) discard;
|
if(frag_color.a == 0) discard;
|
||||||
}
|
}
|
||||||
''',
|
''',
|
||||||
|
@ -451,28 +455,31 @@ class Camera(object):
|
||||||
self.set_ctx_clip_plane(shader_wrapper.use_clip_plane)
|
self.set_ctx_clip_plane(shader_wrapper.use_clip_plane)
|
||||||
|
|
||||||
if shader_wrapper.is_fill:
|
if shader_wrapper.is_fill:
|
||||||
self.render_fill(render_group["vao"], primitive)
|
self.render_fill(render_group["vao"], primitive, shader_wrapper.vert_indices)
|
||||||
else:
|
else:
|
||||||
render_group["vao"].render(primitive)
|
render_group["vao"].render(primitive)
|
||||||
|
|
||||||
if render_group["single_use"]:
|
if render_group["single_use"]:
|
||||||
self.release_render_group(render_group)
|
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
|
VMobject fill is handled in a special way, where emited triangles
|
||||||
must be blended with moderngl.FUNC_SUBTRACT so as to effectively compute
|
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,
|
a winding number around each pixel. This is rendered to a separate texture,
|
||||||
then that texture is overlayed onto the current fbo
|
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.clear(0.0, 0.0, 0.0, 0.0)
|
||||||
self.fill_fbo.use()
|
self.fill_fbo.use()
|
||||||
self.ctx.enable(moderngl.BLEND)
|
self.ctx.enable(moderngl.BLEND)
|
||||||
self.ctx.blend_func = moderngl.ONE, moderngl.ONE
|
self.ctx.blend_func = moderngl.ONE, moderngl.ONE
|
||||||
self.ctx.blend_equation = moderngl.FUNC_SUBTRACT
|
vao.render(render_primitive)
|
||||||
vao.render(render_primitive, instances=2)
|
|
||||||
self.ctx.blend_func = moderngl.DEFAULT_BLENDING
|
self.ctx.blend_func = moderngl.DEFAULT_BLENDING
|
||||||
self.ctx.blend_equation = moderngl.FUNC_ADD
|
|
||||||
self.fbo.use()
|
self.fbo.use()
|
||||||
self.fill_texture.use(0)
|
self.fill_texture.use(0)
|
||||||
self.fill_prog['Texture'].value = 0
|
self.fill_prog['Texture'].value = 0
|
||||||
|
@ -507,14 +514,15 @@ class Camera(object):
|
||||||
elif single_use:
|
elif single_use:
|
||||||
ibo = self.ctx.buffer(indices.astype(np.uint32))
|
ibo = self.ctx.buffer(indices.astype(np.uint32))
|
||||||
else:
|
else:
|
||||||
# The vao.render call is strangely longer
|
ibo = self.ctx.buffer(indices.astype(np.uint32))
|
||||||
# when an index buffer is used, so if the
|
# # The vao.render call is strangely longer
|
||||||
# mobject is not changing, meaning only its
|
# # when an index buffer is used, so if the
|
||||||
# uniforms are being updated, just create
|
# # mobject is not changing, meaning only its
|
||||||
# a larger data array based on the indices
|
# # uniforms are being updated, just create
|
||||||
# and don't bother with the ibo
|
# # a larger data array based on the indices
|
||||||
vert_data = vert_data[indices]
|
# # and don't bother with the ibo
|
||||||
ibo = None
|
# vert_data = vert_data[indices]
|
||||||
|
# ibo = None
|
||||||
vbo = self.ctx.buffer(vert_data)
|
vbo = self.ctx.buffer(vert_data)
|
||||||
|
|
||||||
# Program and vertex array
|
# Program and vertex array
|
||||||
|
|
|
@ -103,6 +103,7 @@ class VMobject(Mobject):
|
||||||
self.flat_stroke = flat_stroke
|
self.flat_stroke = flat_stroke
|
||||||
self.use_simple_quadratic_approx = use_simple_quadratic_approx
|
self.use_simple_quadratic_approx = use_simple_quadratic_approx
|
||||||
self.anti_alias_width = anti_alias_width
|
self.anti_alias_width = anti_alias_width
|
||||||
|
self._use_winding_fill = True
|
||||||
|
|
||||||
self.needs_new_triangulation = True
|
self.needs_new_triangulation = True
|
||||||
self.triangulation = np.zeros(0, dtype='i4')
|
self.triangulation = np.zeros(0, dtype='i4')
|
||||||
|
@ -128,8 +129,6 @@ class VMobject(Mobject):
|
||||||
return super().family_members_with_points()
|
return super().family_members_with_points()
|
||||||
|
|
||||||
def replicate(self, n: int) -> VGroup:
|
def replicate(self, n: int) -> VGroup:
|
||||||
if self.has_fill():
|
|
||||||
self.get_triangulation()
|
|
||||||
return super().replicate(n)
|
return super().replicate(n)
|
||||||
|
|
||||||
def get_grid(self, *args, **kwargs) -> VGroup:
|
def get_grid(self, *args, **kwargs) -> VGroup:
|
||||||
|
@ -401,6 +400,11 @@ class VMobject(Mobject):
|
||||||
def get_joint_type(self) -> float:
|
def get_joint_type(self) -> float:
|
||||||
return self.uniforms["joint_type"]
|
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
|
# Points
|
||||||
def set_anchors_and_handles(
|
def set_anchors_and_handles(
|
||||||
self,
|
self,
|
||||||
|
@ -807,11 +811,15 @@ class VMobject(Mobject):
|
||||||
|
|
||||||
# Alignment
|
# Alignment
|
||||||
def align_points(self, vmobject: VMobject):
|
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 self.get_num_points() == len(vmobject.get_points()):
|
||||||
# If both have fill, and they have the same shape, just
|
# If both have fill, and they have the same shape, just
|
||||||
# give them the same triangulation so that it's not recalculated
|
# give them the same triangulation so that it's not recalculated
|
||||||
# needlessly throughout an animation
|
# 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
|
vmobject.triangulation = self.triangulation
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -898,7 +906,7 @@ class VMobject(Mobject):
|
||||||
*args, **kwargs
|
*args, **kwargs
|
||||||
):
|
):
|
||||||
super().interpolate(mobject1, mobject2, alpha, *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()
|
tri1 = mobject1.get_triangulation()
|
||||||
tri2 = mobject2.get_triangulation()
|
tri2 = mobject2.get_triangulation()
|
||||||
if not arrays_match(tri1, tri2):
|
if not arrays_match(tri1, tri2):
|
||||||
|
@ -991,11 +999,6 @@ class VMobject(Mobject):
|
||||||
v12s = points[2::2] - points[1::2]
|
v12s = points[2::2] - points[1::2]
|
||||||
curve_orientations = np.sign(cross2d(v01s, v12s))
|
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
|
concave_parts = curve_orientations < 0
|
||||||
|
|
||||||
# These are the vertices to which we'll apply a polygon triangulation
|
# These are the vertices to which we'll apply a polygon triangulation
|
||||||
|
@ -1108,10 +1111,11 @@ class VMobject(Mobject):
|
||||||
def reverse_points(self):
|
def reverse_points(self):
|
||||||
# This will reset which anchors are
|
# This will reset which anchors are
|
||||||
# considered path ends
|
# considered path ends
|
||||||
if not self.has_points():
|
for mob in self.get_family():
|
||||||
return self
|
if not mob.has_points():
|
||||||
inner_ends = self.get_subpath_end_indices()[:-1]
|
continue
|
||||||
self.data["point"][inner_ends + 1] = self.data["point"][inner_ends + 2]
|
inner_ends = mob.get_subpath_end_indices()[:-1]
|
||||||
|
mob.data["point"][inner_ends + 1] = mob.data["point"][inner_ends + 2]
|
||||||
super().reverse_points()
|
super().reverse_points()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -1178,14 +1182,18 @@ class VMobject(Mobject):
|
||||||
|
|
||||||
# Build up data lists
|
# Build up data lists
|
||||||
fill_datas = []
|
fill_datas = []
|
||||||
|
fill_indices = []
|
||||||
stroke_datas = []
|
stroke_datas = []
|
||||||
back_stroke_data = []
|
back_stroke_data = []
|
||||||
for submob in family:
|
for submob in family:
|
||||||
if submob.has_fill():
|
if submob.has_fill():
|
||||||
submob.data["base_point"][:] = submob.data["point"][0]
|
submob.data["base_point"][:] = submob.data["point"][0]
|
||||||
fill_datas.append(submob.data[fill_names])
|
fill_datas.append(submob.data[fill_names])
|
||||||
# Add dummy
|
if self._use_winding_fill:
|
||||||
fill_datas.append(submob.data[fill_names][-1:])
|
# Add dummy
|
||||||
|
fill_datas.append(submob.data[fill_names][-1:])
|
||||||
|
else:
|
||||||
|
fill_indices.append(submob.get_triangulation())
|
||||||
if submob.has_stroke():
|
if submob.has_stroke():
|
||||||
submob.get_joint_products()
|
submob.get_joint_products()
|
||||||
if submob.stroke_behind:
|
if submob.stroke_behind:
|
||||||
|
@ -1200,7 +1208,7 @@ class VMobject(Mobject):
|
||||||
|
|
||||||
shader_wrappers = [
|
shader_wrappers = [
|
||||||
self.back_stroke_shader_wrapper.read_in(back_stroke_data),
|
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),
|
self.stroke_shader_wrapper.read_in(stroke_datas),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
#version 330
|
#version 330
|
||||||
|
|
||||||
|
uniform bool winding;
|
||||||
|
|
||||||
in vec4 color;
|
in vec4 color;
|
||||||
in float fill_all;
|
in float fill_all;
|
||||||
|
in float orientation;
|
||||||
in vec2 uv_coords;
|
in vec2 uv_coords;
|
||||||
|
|
||||||
out vec4 frag_color;
|
out vec4 frag_color;
|
||||||
|
@ -9,9 +12,14 @@ out vec4 frag_color;
|
||||||
void main() {
|
void main() {
|
||||||
if (color.a == 0) discard;
|
if (color.a == 0) discard;
|
||||||
frag_color = color;
|
frag_color = color;
|
||||||
|
|
||||||
|
if(winding && orientation > 0) frag_color *= -1;
|
||||||
|
|
||||||
if (bool(fill_all)) return;
|
if (bool(fill_all)) return;
|
||||||
|
|
||||||
float x = uv_coords.x;
|
float x = uv_coords.x;
|
||||||
float y = uv_coords.y;
|
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
|
#version 330
|
||||||
|
|
||||||
layout (triangles) in;
|
layout (triangles) in;
|
||||||
layout (triangle_strip, max_vertices = 3) out;
|
layout (triangle_strip, max_vertices = 6) out;
|
||||||
|
|
||||||
uniform float anti_alias_width;
|
uniform bool winding;
|
||||||
uniform float pixel_size;
|
|
||||||
uniform vec3 corner;
|
|
||||||
|
|
||||||
in vec3 verts[3];
|
in vec3 verts[3];
|
||||||
in vec4 v_color[3];
|
in vec4 v_color[3];
|
||||||
in vec3 v_base_point[3];
|
in vec3 v_base_point[3];
|
||||||
in float v_vert_index[3];
|
in float v_vert_index[3];
|
||||||
in float v_inst_id[3];
|
|
||||||
|
|
||||||
out vec4 color;
|
out vec4 color;
|
||||||
out float fill_all;
|
out float fill_all;
|
||||||
|
out float orientation;
|
||||||
// uv space is where the curve coincides with y = x^2
|
// uv space is where the curve coincides with y = x^2
|
||||||
out vec2 uv_coords;
|
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
|
// Analog of import for manim only
|
||||||
#INSERT get_gl_Position.glsl
|
#INSERT get_gl_Position.glsl
|
||||||
|
@ -25,47 +29,56 @@ out vec2 uv_coords;
|
||||||
#INSERT finalize_color.glsl
|
#INSERT finalize_color.glsl
|
||||||
|
|
||||||
|
|
||||||
void emit_vertex_wrapper(vec3 point, vec4 v_color, vec3 unit_normal){
|
void emit_triangle(vec3 points[3], vec4 v_color[3]){
|
||||||
color = finalize_color(v_color, point, unit_normal);
|
vec3 unit_normal = get_unit_normal(points[0], points[1], points[2]);
|
||||||
gl_Position = get_gl_Position(point);
|
orientation = sign(unit_normal.z);
|
||||||
EmitVertex();
|
|
||||||
|
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(){
|
void main(){
|
||||||
// We use the triangle strip primative, but
|
// We use the triangle strip primative, but
|
||||||
// actually only need every other strip element
|
// 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
|
// Curves are marked as ended when the handle after
|
||||||
// the first anchor is set equal to that anchor
|
// the first anchor is set equal to that anchor
|
||||||
if (verts[0] == verts[1]) return;
|
if (verts[0] == verts[1]) return;
|
||||||
|
|
||||||
if (v_color[0].a == 0 && v_color[1].a == 0 && v_color[2].a == 0) return;
|
vec3 mid_vert;
|
||||||
|
if(winding){
|
||||||
vec3 unit_normal = get_unit_normal(verts[0], verts[1], verts[2]);
|
|
||||||
|
|
||||||
if(int(v_inst_id[0]) % 2 == 0){
|
|
||||||
// Emit main triangle
|
// Emit main triangle
|
||||||
fill_all = 1.0;
|
fill_all = 1.0;
|
||||||
uv_coords = vec2(0.0);
|
emit_triangle(
|
||||||
emit_vertex_wrapper(v_base_point[0], v_color[0], unit_normal);
|
vec3[3](v_base_point[0], verts[0], verts[2]),
|
||||||
emit_vertex_wrapper(verts[0], v_color[0], unit_normal);
|
vec4[3](v_color[1], v_color[0], v_color[2])
|
||||||
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)
|
|
||||||
);
|
);
|
||||||
for(int i = 0; i < 3; i ++){
|
// Edge triangle
|
||||||
uv_coords = uv_coords_arr[i];
|
fill_all = 0.0;
|
||||||
emit_vertex_wrapper(verts[i], v_color[i], unit_normal);
|
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;
|
in vec3 base_point;
|
||||||
|
|
||||||
out vec3 verts; // Bezier control point
|
out vec3 verts; // Bezier control point
|
||||||
out vec4 v_joint_product;
|
|
||||||
out vec4 v_color;
|
out vec4 v_color;
|
||||||
out vec3 v_base_point;
|
out vec3 v_base_point;
|
||||||
out float v_vert_index;
|
out float v_vert_index;
|
||||||
out float v_inst_id;
|
|
||||||
|
|
||||||
void main(){
|
void main(){
|
||||||
verts = point;
|
verts = point;
|
||||||
v_color = fill_rgba;
|
v_color = fill_rgba;
|
||||||
v_base_point = base_point;
|
v_base_point = base_point;
|
||||||
v_vert_index = gl_VertexID;
|
v_vert_index = gl_VertexID;
|
||||||
v_inst_id = gl_InstanceID;
|
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue