Merge pull request #1985 from 3b1b/cleaner-winding-fill

Cleaner winding fill
This commit is contained in:
Grant Sanderson
2023-02-02 11:39:18 -08:00
committed by GitHub
5 changed files with 20 additions and 35 deletions

View File

@ -309,6 +309,8 @@ class VMobjectFromSVGPath(VMobject):
path_string = self.path_obj.d() path_string = self.path_obj.d()
if path_string not in PATH_TO_POINTS: if path_string not in PATH_TO_POINTS:
self.handle_commands() self.handle_commands()
if not self._use_winding_fill:
self.subdivide_intersections()
# Save for future use # Save for future use
PATH_TO_POINTS[path_string] = self.get_points().copy() PATH_TO_POINTS[path_string] = self.get_points().copy()
else: else:

View File

@ -867,8 +867,10 @@ class VMobject(Mobject):
# Alignment # Alignment
def align_points(self, vmobject: VMobject) -> Self: def align_points(self, vmobject: VMobject) -> Self:
winding = self._use_winding_fill and vmobject._use_winding_fill winding = self._use_winding_fill and vmobject._use_winding_fill
self.use_winding_fill(winding) if winding != self._use_winding_fill:
vmobject.use_winding_fill(winding) self.use_winding_fill(winding)
if winding != vmobject._use_winding_fill:
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

View File

@ -287,24 +287,22 @@ class FillShaderWrapper(ShaderWrapper):
return return
original_fbo = self.ctx.fbo original_fbo = self.ctx.fbo
texture_fbo, texture_vao, null_rgb = self.fill_canvas texture_fbo, texture_vao = self.fill_canvas
texture_fbo.clear(*null_rgb, 0.0) texture_fbo.clear()
texture_fbo.use() texture_fbo.use()
gl.glBlendFuncSeparate( gl.glBlendFuncSeparate(
# Ordinary blending for colors # Ordinary blending for colors
gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA, gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA,
# Just take the max of the alphas, given the shenanigans # The effect of blending with -a / (1 - a)
# with how alphas are being used to compute winding numbers # should be to cancel out
gl.GL_ONE, gl.GL_ONE, gl.GL_ONE_MINUS_DST_ALPHA, gl.GL_ONE,
) )
gl.glBlendEquationSeparate(gl.GL_FUNC_ADD, gl.GL_MAX)
super().render() super().render()
original_fbo.use() original_fbo.use()
gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE_MINUS_SRC_ALPHA) gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE_MINUS_SRC_ALPHA)
gl.glBlendEquation(gl.GL_FUNC_ADD)
texture_vao.render() texture_vao.render()

View File

@ -33,7 +33,7 @@ void main() {
cap is to make sure the original fragment color can be recovered even after cap is to make sure the original fragment color can be recovered even after
blending with an (alpha = 1) color. blending with an (alpha = 1) color.
*/ */
float a = 0.99 * frag_color.a; float a = 0.95 * frag_color.a;
if(winding && orientation < 0) a = -a / (1 - a); if(winding && orientation < 0) a = -a / (1 - a);
frag_color.a = a; frag_color.a = a;

View File

@ -103,7 +103,7 @@ def get_colormap_code(rgb_list: Sequence[float]) -> str:
@lru_cache() @lru_cache()
def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray, Tuple[float, float, float]]: def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray]:
""" """
Because VMobjects with fill are rendered in a funny way, using Because VMobjects with fill are rendered in a funny way, using
alpha blending to effectively compute the winding number around alpha blending to effectively compute the winding number around
@ -123,26 +123,16 @@ def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray, Tu
depth_texture = ctx.depth_texture(size=size) depth_texture = ctx.depth_texture(size=size)
texture_fbo = ctx.framebuffer(texture, depth_texture) texture_fbo = ctx.framebuffer(texture, depth_texture)
# We'll paint onto a canvas with initially negative rgbs, and
# discard any pixels remaining close to this value. This is
# because alphas are effectively being used for another purpose,
# and we don't want to overlap with any colors one might actually
# use. It should be negative enough to be distinguishable from
# ordinary colors with some margin, but the farther it's pulled back
# from zero the more it will be true that overlapping filled objects
# with transparency have an unnaturally bright composition.
null_rgb = (-0.25, -0.25, -0.25)
simple_program = ctx.program( simple_program = ctx.program(
vertex_shader=''' vertex_shader='''
#version 330 #version 330
in vec2 texcoord; in vec2 texcoord;
out vec2 v_textcoord; out vec2 uv;
void main() { void main() {
gl_Position = vec4((2.0 * texcoord - 1.0), 0.0, 1.0); gl_Position = vec4((2.0 * texcoord - 1.0), 0.0, 1.0);
v_textcoord = texcoord; uv = texcoord;
} }
''', ''',
fragment_shader=''' fragment_shader='''
@ -150,31 +140,24 @@ def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray, Tu
uniform sampler2D Texture; uniform sampler2D Texture;
uniform sampler2D DepthTexture; uniform sampler2D DepthTexture;
uniform vec3 null_rgb;
in vec2 v_textcoord; in vec2 uv;
out vec4 color; out vec4 color;
const float MIN_DIST_TO_NULL = 0.2;
void main() { void main() {
color = texture(Texture, v_textcoord); color = texture(Texture, uv);
if(color.a == 0) discard; if(color.a == 0) discard;
if(distance(color.rgb, null_rgb) < MIN_DIST_TO_NULL) discard;
// Un-blend from the null value
color.rgb -= (1 - color.a) * null_rgb;
// Counteract scaling in fill frag // Counteract scaling in fill frag
color.a *= 1.01; color.a *= 1.06;
gl_FragDepth = texture(DepthTexture, v_textcoord)[0]; gl_FragDepth = texture(DepthTexture, uv)[0];
} }
''', ''',
) )
simple_program['Texture'].value = get_texture_id(texture) simple_program['Texture'].value = get_texture_id(texture)
simple_program['DepthTexture'].value = get_texture_id(depth_texture) simple_program['DepthTexture'].value = get_texture_id(depth_texture)
simple_program['null_rgb'].value = null_rgb
verts = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]) verts = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
fill_texture_vao = ctx.simple_vertex_array( fill_texture_vao = ctx.simple_vertex_array(
@ -183,4 +166,4 @@ def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray, Tu
'texcoord', 'texcoord',
mode=moderngl.TRIANGLE_STRIP mode=moderngl.TRIANGLE_STRIP
) )
return (texture_fbo, fill_texture_vao, null_rgb) return (texture_fbo, fill_texture_vao)