From dfc5f152dd9dae10a5ad1dd4a0f7c9c4a5aac129 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 20 Aug 2024 22:03:45 -0500 Subject: [PATCH] Have border width pre-multiply by alpha, and don't use a separate texture for that border width --- manimlib/shader_wrapper.py | 44 +++++++++++-------- .../shaders/quadratic_bezier/stroke/frag.glsl | 2 + manimlib/utils/shaders.py | 37 ++-------------- 3 files changed, 30 insertions(+), 53 deletions(-) diff --git a/manimlib/shader_wrapper.py b/manimlib/shader_wrapper.py index 6a1826a6..daef9210 100644 --- a/manimlib/shader_wrapper.py +++ b/manimlib/shader_wrapper.py @@ -249,13 +249,22 @@ class VShaderWrapper(ShaderWrapper): geometry_shader=self.program_code["fill_geom"], fragment_shader=self.program_code["fill_frag"], ) + self.fill_border_program = get_shader_program( + self.ctx, + vertex_shader=self.program_code["stroke_vert"], + geometry_shader=self.program_code["stroke_geom"], + fragment_shader=self.program_code["stroke_frag"].replace( + "// MODIFY FRAG COLOR", + "frag_color.a *= 0.95; frag_color.rgb *= frag_color.a;", + ) + ) self.fill_depth_program = get_shader_program( self.ctx, vertex_shader=self.program_code["depth_vert"], geometry_shader=self.program_code["depth_geom"], fragment_shader=self.program_code["depth_frag"], ) - self.programs = [self.stroke_program, self.fill_program, self.fill_depth_program] + self.programs = [self.stroke_program, self.fill_program, self.fill_border_program, self.fill_depth_program] # Full vert format looks like this (total of 4x23 = 92 bytes): # point 3 @@ -296,7 +305,7 @@ class VShaderWrapper(ShaderWrapper): mode=self.render_primitive, ) self.fill_border_vao = self.ctx.vertex_array( - program=self.stroke_program, + program=self.fill_border_program, content=[(self.vbo, self.fill_border_vert_format, *self.fill_border_vert_attributes)], mode=self.render_primitive, ) @@ -325,12 +334,7 @@ class VShaderWrapper(ShaderWrapper): return original_fbo = self.ctx.fbo - 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() + fill_tx_fbo, fill_tx_vao, depth_tx_fbo = self.fill_canvas # Render to a separate texture, due to strange alpha compositing # for the blended winding calculation @@ -340,13 +344,13 @@ class VShaderWrapper(ShaderWrapper): # Be sure not to apply depth test while rendering fill # but set it back to where it was after apply_depth_test = bool(gl.glGetBooleanv(gl.GL_DEPTH_TEST)) - self.ctx.disable(moderngl.DEPTH_TEST) + + # 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.glBlendFuncSeparate( gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA, - # 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() @@ -359,18 +363,20 @@ class VShaderWrapper(ShaderWrapper): 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) + # Now add border, just taking the max alpha gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE) gl.glBlendEquation(gl.GL_MAX) + self.fill_border_vao.render() + + # Take the texture we were just drawing to, and render it to + # the main scene. Account for how alphas have been premultiplied + original_fbo.use() + gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE_MINUS_SRC_ALPHA) + gl.glBlendEquation(gl.GL_FUNC_ADD) fill_tx_vao.render() - original_fbo.use() + # Return to original blending state 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/shaders/quadratic_bezier/stroke/frag.glsl b/manimlib/shaders/quadratic_bezier/stroke/frag.glsl index a26b2334..2a2ef467 100644 --- a/manimlib/shaders/quadratic_bezier/stroke/frag.glsl +++ b/manimlib/shaders/quadratic_bezier/stroke/frag.glsl @@ -13,4 +13,6 @@ void main() { // sdf for the region around the curve we wish to color. float signed_dist_to_region = abs(dist_to_aaw) - half_width_to_aaw; frag_color.a *= smoothstep(0.5, -0.5, signed_dist_to_region); + // This line is replaced in VShaderWrapper + // MODIFY FRAG COLOR } \ No newline at end of file diff --git a/manimlib/utils/shaders.py b/manimlib/utils/shaders.py index 352246b1..a8d1c3bb 100644 --- a/manimlib/utils/shaders.py +++ b/manimlib/utils/shaders.py @@ -153,13 +153,10 @@ 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 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 + # Use another one to keep track of depth depth_texture = ctx.texture(size=size, components=1, dtype='f4') fill_texture_fbo = ctx.framebuffer(fill_texture) - border_texture_fbo = ctx.framebuffer(border_texture) depth_texture_fbo = ctx.framebuffer(depth_texture) simple_vert = ''' @@ -187,38 +184,18 @@ def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray]: if(color.a == 0) discard; // Counteract scaling in fill frag - color.a *= 1.06; - // Cancel out what was effectively a premultiplication - color.rgb /= color.a; + color *= 1.06; gl_FragDepth = texture(DepthTexture, uv)[0]; } ''' - 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, - ) 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()) @@ -226,12 +203,4 @@ def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray]: fill_program, simple_vbo, 'texcoord', mode=moderngl.TRIANGLE_STRIP ) - 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, - ) + return (fill_texture_fbo, fill_texture_vao, depth_texture_fbo)