diff --git a/manimlib/shader_wrapper.py b/manimlib/shader_wrapper.py index 6e4f5c63..7c4d4bea 100644 --- a/manimlib/shader_wrapper.py +++ b/manimlib/shader_wrapper.py @@ -325,15 +325,17 @@ class VShaderWrapper(ShaderWrapper): return original_fbo = self.ctx.fbo - texture_fbo, depth_texture_fbo, texture_vao = self.fill_canvas + fill_tx_fbo, fill_tx_vao, border_tx_fbo, border_tx_vao, depth_tx_fbo = self.fill_canvas # First, draw the border for antialiasing + border_tx_fbo.clear() + border_tx_fbo.use() self.fill_border_vao.render() # Render to a separate texture, due to strange alpha compositing # for the blended winding calculation - texture_fbo.clear() - texture_fbo.use() + fill_tx_fbo.clear() + fill_tx_fbo.use() # Be sure not to apply depth test while rendering fill # but set it back to where it was after @@ -341,30 +343,34 @@ class VShaderWrapper(ShaderWrapper): self.ctx.disable(moderngl.DEPTH_TEST) gl.glBlendFuncSeparate( - # Ordinary blending for colors gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA, - # The effect of blending with -a / (1 - a) - # should be to cancel out - gl.GL_ONE_MINUS_DST_ALPHA, gl.GL_ONE, + # With this blend function, the effect of blending alpha a with + # -a / (1 - a) cancels out, so we can cancel positively and negatively + # oriented triangles + gl.GL_ONE_MINUS_DST_ALPHA, gl.GL_ONE ) - self.fill_vao.render() + if apply_depth_test: - depth_texture_fbo.clear(1.0) - depth_texture_fbo.use() + depth_tx_fbo.clear(1.0) + depth_tx_fbo.use() gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE) gl.glBlendEquation(gl.GL_MIN) self.fill_depth_vao.render() self.ctx.enable(moderngl.DEPTH_TEST) + # Render fill onto the border_width fbo + # two alphas, before compositing back to the rest of the scene + border_tx_fbo.use() + gl.glEnable(gl.GL_BLEND) + gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE) + gl.glBlendEquation(gl.GL_MAX) + fill_tx_vao.render() original_fbo.use() - gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE_MINUS_SRC_ALPHA) - gl.glBlendEquation(gl.GL_FUNC_ADD) - texture_vao.render() - gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) gl.glBlendEquation(gl.GL_FUNC_ADD) + border_tx_vao.render() def render(self): if self.stroke_behind: diff --git a/manimlib/utils/shaders.py b/manimlib/utils/shaders.py index bee2d972..352246b1 100644 --- a/manimlib/utils/shaders.py +++ b/manimlib/utils/shaders.py @@ -152,9 +152,14 @@ def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray]: # Important to make sure dtype is floating point (not fixed point) # so that alpha values can be negative and are not clipped - texture = ctx.texture(size=size, components=4, dtype='f2') + fill_texture = ctx.texture(size=size, components=4, dtype='f2') + # Use a separate texture to firset render the antialiased border + border_texture = ctx.texture(size=size, components=4, dtype='f1') + # Use yet another one to keep track of depth depth_texture = ctx.texture(size=size, components=1, dtype='f4') - texture_fbo = ctx.framebuffer(texture) + + fill_texture_fbo = ctx.framebuffer(fill_texture) + border_texture_fbo = ctx.framebuffer(border_texture) depth_texture_fbo = ctx.framebuffer(depth_texture) simple_vert = ''' @@ -183,22 +188,50 @@ def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray]: // Counteract scaling in fill frag color.a *= 1.06; + // Cancel out what was effectively a premultiplication + color.rgb /= color.a; gl_FragDepth = texture(DepthTexture, uv)[0]; } ''' - simple_program = ctx.program( + simple_frag = ''' + #version 330 + + uniform sampler2D Texture; + + in vec2 uv; + out vec4 color; + + void main() { + color = texture(Texture, uv); + if(color.a == 0) discard; + } + ''' + fill_program = ctx.program( vertex_shader=simple_vert, fragment_shader=alpha_adjust_frag, ) + border_program = ctx.program( + vertex_shader=simple_vert, + fragment_shader=simple_frag, + ) - simple_program['Texture'].value = get_texture_id(texture) - simple_program['DepthTexture'].value = get_texture_id(depth_texture) + fill_program['Texture'].value = get_texture_id(fill_texture) + fill_program['DepthTexture'].value = get_texture_id(depth_texture) + border_program['Texture'].value = get_texture_id(border_texture) verts = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]) simple_vbo = ctx.buffer(verts.astype('f4').tobytes()) fill_texture_vao = ctx.simple_vertex_array( - simple_program, simple_vbo, 'texcoord', + fill_program, simple_vbo, 'texcoord', mode=moderngl.TRIANGLE_STRIP ) - return (texture_fbo, depth_texture_fbo, fill_texture_vao) + border_texture_vao = ctx.simple_vertex_array( + border_program, simple_vbo, 'texcoord', + mode=moderngl.TRIANGLE_STRIP + ) + return ( + fill_texture_fbo, fill_texture_vao, + border_texture_fbo, border_texture_vao, + depth_texture_fbo, + )