Skip to content

Conversation

@AlaricBaraou
Copy link
Contributor

Currently, the outline is drawn inside the glyph + outlineWidth.
This work perfectly fine if fillOpacity is 1.
But when fillOpacity is less than one, the fill color will blend with the outline color while I would expect for it to blend with the rest of the scene only.

I don't know if the previous way was the expected behavior, but in my case I need to be able to set the fill opacity without it being affected by the outline color.

There might be a better way to achieve this but this is the most minimalist approach I could come up with.
It might even be worth changing to if (uTroikaEdgeOffset > 0.0 && fillRGBA.a < 1.0) { in order to run it only for the outline pass when the fill have some transparency.

Before

fillOpacityBefor.mp4

After

fillOpacityAfter.mp4

It works well with the current BatchedText too.

I'm happy to make edit to this PR if needed, feel free to edit it yourself too if it's more convenient for you.
I can implement this directly in my repo but it feels like something other users might want too.

@AlaricBaraou
Copy link
Contributor Author

Since that PR I realized that on SVG texts, the fill does mix with the outline ( it will depend on the outline / stroke width though )
Like so

text x="50%" y="60" font-family="Geist, sans-serif" font-size="100" text-anchor="middle" fill="#ffffff" fill-opacity="0.5" paint-order="stroke" stroke="#121212" stroke-width="6.4">e</text>
Screenshot 2025-09-14 170300

But I found another use case for this PR

At small size, svg text seem to preserve the text color ( fill ) more than troika does.
The followings are tested with a color / fill of #ffffff and an outline of #121212

Notice how even when the text is small, the white fill is prevalent over the outline when using a SVG

svg

Now the current version of Troika at the same size on screen will mix the outline with the fill.

withoutFix

With this PR, the white is more prevalent like in the SVG version.

withFix

The 3 next to each other
SVG => Current Troika => This PR
svg

This result in a more readable / accurate color on smaller size but at the cost of not being able to merge the fill color with the outline. ( at least in the current state, I can look into that )

@AlaricBaraou
Copy link
Contributor Author

Without adding uniforms, and for basic materials, it's possible to calculate everything on the js side and pass it to the Text / Batched Text and it works just fine with opacity etc
correctTransparency

But for other type of material, there is no way around adding a uniform ( or reusing an existing one, I'm not that familiar with the current uniforms used yet )
the ouline pass would be the same as the current PR, while the fill pass would require to receive outlineColor in order to mix it shader side.
This would result in the same color as the current published troika while benefitting from a more readable text in small size.
I believe the added computation in the fragment shader is negligeable vs the gain on small font size, but a stress test could prove that to make sure.

@lojjic I'm willing to implement that in a way that match your vision, what do you think about this ?
Can I provide anything to highlight the differences ?
I'm at your disposal on this, let me know!
Thank you 🙏

@lojjic
Copy link
Collaborator

lojjic commented Sep 14, 2025

@AlaricBaraou I think this is fantastic. 😄

The reason I didn't go with this to begin with was due to the outlineOffsetX/Y parameters. With those the "outline" is really more of a CSS text-shadow, where the interior needs to stay filled.
image

Both behaviors seem desired in different circumstances, so I'm open to adjusting the API to allow both. Options off the top of my head:

  1. Add a conditional to your code block that keeps it filled if there's an outlineOffsetX/Y present
  2. Add a boolean parameter to let the user set whether they want filled or unfilled behavior
  3. Remove outlineOffsetX/Y from the API, and add a whole new set of shadowWidth/Blur/Color/Opacity/Offset parameters

Thoughts?

@AlaricBaraou
Copy link
Contributor Author

Good point, the shadow option should be preserved indeed.

I like 2. because it gives a clear choice to the user.

And I see that the current packing strategy has two empty spot ready for this.
I can use 27 for this boolean and Main 31 for the outlineColor.

I'll give it a try

@AlaricBaraou
Copy link
Contributor Author

Thinking out loud here.

When the outline pass include the inside of the glyph, the pixels located on the edge will end up fully colored as the outline color, while with my solution of drawing exclusively outside the glyph they have a degree of transparency based on how much of the pixel area is outside the glyph.

This difference will affect the final color of the pixels located on the edge and therefore the composition once merged with the fill render pass.

On highly scaled font it's not noticeable but if we zoom on the pixels of the edge we can see how the final color of the pixels located on the edges are closer in color to the outline color than the fill color.

I guess a name / description for this boolean parameter could be.

bool filledOutline
default true ( for backward compatibility )
By default the outline also fill the fill area of the glyph.
Setting this to false will prevent the outline from being drawn inside the glyph, which produce cleaner edge colors, which can noticeably improve readability of small texts on screen.
When set to false, it also prevent the fill color to be blended with the outline.

I was trying to figure out a way to have this readability improvement work with everything but it's not possible.
I'll continue in that direction.

@AlaricBaraou
Copy link
Contributor Author

AlaricBaraou commented Sep 15, 2025

@lojjic I pushed a first draft that follow the logic of 2.

fillOutlineDemo2.mp4

In this video we can see how it prevent the outlines from "bleeding" too much over the fill color when the text is displayed small.

Comparison at the pixel level
With fillOutline true ( default and current Troika rendering )
withFillOutline

With fillOutline false
withoutFillOutline

The white of the fill is less affected by the outline, resulting in a text much closer that what a SVG text would produce in term of readability like shown in this previous comment #372 (comment)

Let me know if you'd like me to make any adjustment, I tried to update the documentation and example to reflect the change.
Fell free to make the adjustment yourself too if it's more convenient for you.

Thank you!

@AlaricBaraou
Copy link
Contributor Author

AlaricBaraou commented Sep 15, 2025

I noticed that my current example only works when the canvas background is white.
The outline at this size gets blended with the white background more when it's not drawing within the fill I think.
It's causing it to be lighter and in turn makes the fill lighter too.
So at the moment the only thing that might be working as intended in this PR is the ability to have the outline not drawn within the fill.
For the rest.. I have to dig deeper..
This accidental improvement in a specific scenario might be the key to something.

Edit: instead of blending the outline with the background, I might be able to blend it with the expected fill color and get the same result as above. Maybe by changing the render order or something 🤔

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