Make winding fill optional, and make winding additive rather than toggling

This commit is contained in:
Grant Sanderson 2023-01-24 20:03:23 -08:00
parent e9c70dbfd9
commit f0df5c759d
5 changed files with 100 additions and 66 deletions

View file

@ -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

View file

@ -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])
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),
]

View file

@ -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;
}

View file

@ -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);
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();
}

View file

@ -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;
}