Skip to content

Fix color leakage issue in shader rendering#2425

Open
mathsvisualization wants to merge 1 commit into3b1b:masterfrom
mathsvisualization:fix/color-bleeding
Open

Fix color leakage issue in shader rendering#2425
mathsvisualization wants to merge 1 commit into3b1b:masterfrom
mathsvisualization:fix/color-bleeding

Conversation

@mathsvisualization
Copy link
Copy Markdown
Contributor

@mathsvisualization mathsvisualization commented Feb 14, 2026

Fix Color Leakage in Fragment Shader

Motivation

While rendering overlapping triangles with opposite orientations,
minor color leakage artifacts were observed.

The issue was caused by aggressive alpha scaling in the fragment shader:

float a = 0.95 * frag_color.a;

This resulted in excessive blending, especially when negatively oriented triangles were involved.

Proposed Changes

  • Alpha scaling factor was reduced from 0.95 to 0.90:
float a = 0.90 * frag_color.a;

This lowers the blending strength and prevents unintended color bleed.

  • Orientation-Based Alpha Correction
    For negatively oriented triangles, the following transformation is used:
if (orientation < 0)
    a = -a / (1 - a);

Mathematically, the modified alpha value is:
a' = -a / (1 - a)
This ensures correct cancellation behavior under the standard blending equation:
(1 - alpha) * dst + alpha * src
The transformation guarantees that blending a positively oriented triangle followed by a negatively oriented one restores the original fragment color.

Testing

  • Render overlapping triangles with opposite orientations
  • Verify no residual tint remains
  • Confirm stable blending behavior
from manimlib import *

class GlyphTest1(InteractiveScene):
    def construct(self):
        char = "B"
        t = TexText(char)
        t.set_height(FRAME_HEIGHT - 2)
        self.add(t)

Result

  • Cleaner blending
  • No color leakage
  • Stable fragment output

Before

IMG_20260214_124531

IMG_20260214_124517

IMG_20260214_124540

After

GlyphTest2 GlyphTest15 GlyphTest17

@3b1b
Copy link
Copy Markdown
Owner

3b1b commented Mar 26, 2026

Interesting, I don't see that on my system. I'm happy to merge this, but a few notes:

This whole "0.95" is clearly hacky, so I'm not proud of this, but there are a few other places in the code where it is sneakily relevant. One is line 476 of manim/manimlib/shader_wrapper.py, where that correction is counteracted. The other is line 288 of that same file, where we make sure the fill border aligns with the conventions of fill.

I've contemplated where it makes sense to roll back from the winding fill approach. There's an elegance to it compared to finding an internal triangulation, both to make interpolation more natural and also to be able to avoid having to recompute that triangulation if ever shape changes, but it does have these weird side quirks.

The motivation for having a more aggressive scaling factor is that this whole tactic for fill has a side effect where opacity 1 objects overlapping end up brighter over the overlap, even when the same color. For example:

image

The effect gets amplified when that scaling factor is turned down. For example, here it is at 0.5, where the appropriate line 476 correction would be to multiply back by 2:

image

Of course, if 0.95 causes your artifacts, the lesser of two evils is to scale it down as you have, I'd just add the corresponding corrections.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants