Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Drawing Text on transparent RenderTexture leaves outline  (Read 11206 times)

0 Members and 4 Guests are viewing this topic.

Trilaterus

  • Newbie
  • *
  • Posts: 3
    • View Profile
    • My Portfolio!
    • Email
Drawing Text on transparent RenderTexture leaves outline
« on: April 26, 2017, 08:47:28 pm »
In my application code I have a vector (of vectors) of sf::Text's that I render to a sf::RenderTexture. When trying to draw the RenderTexture the edges of the text are not fully transparent.

I've got a minimal code example mocked up in my main cpp file:
int main()
{
        sf::RenderWindow window(sf::VideoMode(1366, 768), "SFML works!");

        float fFrameRate = 60.f;
        window.setFramerateLimit(static_cast<unsigned int>(fFrameRate));

        sf::Font font;
        font.loadFromFile("Assets/Fonts/Lin.ttf");

        sf::Text text;
        //text.setFillColor(sf::Color::Red);
        text.setFont(font);
        text.setString("Some TALL | TEXT | some y_, small text.");
        text.setPosition(std::floor(text.getPosition().x), std::floor(text.getPosition().y));
        text.setOrigin(std::floor(text.getOrigin().x), std::floor(text.getOrigin().y));
        text.setScale(std::floor(text.getScale().x), std::floor(text.getScale().y));

        sf::RenderTexture renTex;
        renTex.create(static_cast<unsigned int>(text.getGlobalBounds().width), static_cast<unsigned int>(font.getLineSpacing(30)));
        renTex.clear(sf::Color::Transparent);
        renTex.draw(text);
        renTex.display();

        sf::Sprite sprite;
        sprite.setTexture(renTex.getTexture());
        sprite.setPosition(std::floor(sprite.getPosition().x), std::floor(sprite.getPosition().y));
        sprite.setOrigin(std::floor(sprite.getOrigin().x), std::floor(sprite.getOrigin().y));
        sprite.setScale(std::floor(sprite.getScale().x), std::floor(sprite.getScale().y));

        while (window.isOpen())
        {
                sf::Event event;
                while (window.pollEvent(event))
                {
                        if (event.type == sf::Event::Closed)
                                window.close();
                }

                window.clear(sf::Color::White);

                window.draw(sprite);

                window.display();
        }

        return 0;
}
 



I found this post which seemed to have a similar problem with Text and RenderTexture, but clearing the RenderTexture with Color::Transparent hasn't seemed to solve the problem. In this test case I could just clear the RenderTexture with White (the same colour I'm clearing the window with) but in my application code the text will be drawn over all variety of things.

I also remember reading in all sorts of places that it's best to check that the sprites are drawn on the pixel to reduce the chance of it being rendered over two pixels and then looking odd, so a lot of this code is trying to keep the position, origin and scale on the pixel.

I'm using SFML-2.4.1 and my graphics card is an NVIDIA GeForce GTX 760.

Let me know if I'm missing any information.
« Last Edit: April 26, 2017, 08:53:15 pm by Trilaterus »

Hapax

  • Hero Member
  • *****
  • Posts: 3379
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Drawing Text on transparent RenderTexture leaves outline
« Reply #1 on: April 26, 2017, 09:12:15 pm »
Using your code, I get the same result.

It looks like this is the anti-aliasing on the glyph's rendering and is clearly not the desired result.

Is there a possibility that SFML is pre-mixing/blending colours for anti-aliasing and then discarding the alpha value instead of blending the actual alpha values of source and destination (and colours would no longer need to be blended) when preparing the glyph sheet? Does the glyph sheet contain (full-range) alpha?
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

Trilaterus

  • Newbie
  • *
  • Posts: 3
    • View Profile
    • My Portfolio!
    • Email
Re: Drawing Text on transparent RenderTexture leaves outline
« Reply #2 on: April 26, 2017, 09:49:34 pm »
That does remind me that setting the blend state to none kind of worked, although it would clip with the other glyphs and I thought it would also have jagged edges (but apparently it's fine):



In this example I changed the way the Text was drawn to the RenderTexture by adding the sf::BlendNone to the draw function. I also upped the character size to 70 and changed the font colour to red to better highlight this other glyph cropping issue. It may be related to this issue or might be the intended functionality (since doing no blending means each letter is drawn over each other without transparency [I guess]).

I'm not exactly sure how to check if a glyph sheet contains a full-range alpha, although I copied the arial.ttf font from my systems font folder into the project and got a similar result to the original font (with the outline).
« Last Edit: April 26, 2017, 09:51:10 pm by Trilaterus »

Hapax

  • Hero Member
  • *****
  • Posts: 3379
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Drawing Text on transparent RenderTexture leaves outline
« Reply #3 on: April 26, 2017, 10:21:46 pm »
I'm not exactly sure how to check if a glyph sheet contains a full-range alpha
I'm sorry. That was an open question intended for the SFML team or all those people who know more than I do about how it works behind the scenes.
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11034
    • View Profile
    • development blog
    • Email
Re: Drawing Text on transparent RenderTexture leaves outline
« Reply #4 on: April 27, 2017, 11:11:50 am »
It may seem a bit unintuitive, but I believe everything is working as intended.
There's no such thing as a "transparent" color, sf::Color::Transparent is just a short hand for black with an alpha value of zero (e.g. sf::Color(0, 0, 0, 0)). Because the default blend mode is BlendAlpha, it leads to the partially transparent anti-aliasing edges to blend with the background and changing the fully transparent background value to a not fully transparent one. Since the text itself is not transparent at all, the blending of the alpha value works without issue. You can easily test this behavior by using a partially transparent fill color.
The solution, as you've noticed yourself already, is to tell SFML to ignore the background color and just 1:1 render the text to the render texture by using sf::BlendNone.

Not sure what you mean with "this other glyph cropping issue".
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Trilaterus

  • Newbie
  • *
  • Posts: 3
    • View Profile
    • My Portfolio!
    • Email
Re: Drawing Text on transparent RenderTexture leaves outline
« Reply #5 on: April 27, 2017, 02:00:18 pm »
There's no such thing as a "transparent" color, sf::Color::Transparent is just a short hand for black with an alpha value of zero...

Ah I see what you mean, I remember rendering a Window using clear(Colour::Transparent) and it came out with just a black background. I'm not sure then if the issue with the glyphs being cropped is part of this or should be another issue? In any case let me try to explain it again in a little more detail.

Using the code example from the first post, if we render the Text to the RenderTexture using BlendNone:

renTex.draw(text, sf::BlendNone);

We have a rendering problem with fonts that are italic-like or calligraphic (sorry not tested more fonts and away from code atm). In the screenshot I had before the T gets cut off by the A in TALL, and the l crops the previous l in small.

My previous thought was that using BlendNone was removing the alpha component, so as each glyphs texture is drawn (from left to right) it draws white pixels over the previous text. Similar to just drawing two opaque boxes over each other.



(Tried to highlight the issue better but I've done it very rushed in paint so apologies!)

Xorton

  • Newbie
  • *
  • Posts: 7
    • View Profile
Re: Drawing Text on transparent RenderTexture leaves outline
« Reply #6 on: June 26, 2017, 10:48:11 pm »
Seems like pygame have special case for drawing on fully transparent surface.



It looks like:
if dstAlpha==0 then blend mode is color=srcColor
else blend mode is color=srcColor*srcAlpha+dstColor*(1-srcAlpha)

I don't know how to achieve this in SFML. Custom blend mode in shader?

UPDATE:
I think it's SDL thing (which pygame is based on). SDL2 shows same picture as SFML.
« Last Edit: June 27, 2017, 06:22:18 pm by Xorton »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: Drawing Text on transparent RenderTexture leaves outline
« Reply #7 on: June 28, 2017, 12:57:53 pm »
Quote
Custom blend mode in shader?
A fragment shader which discards fragments with alpha == 0 should be enough as long as glyphs don't overlap.
Laurent Gomila - SFML developer

Xorton

  • Newbie
  • *
  • Posts: 7
    • View Profile
Re: Drawing Text on transparent RenderTexture leaves outline
« Reply #8 on: June 28, 2017, 05:39:49 pm »
... as long as glyphs don't overlap.

That's the point - they do overlap



Here is simple shader with custom blending.
Vertex Shader
varying vec2 backPos;
void main()
{
        // transform the vertex position
        gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;

        // transform the texture coordinates
        gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;

        // forward the vertex color
        gl_FrontColor = gl_Color;

        // convert screen coordinates to texture coordinates
        backPos = gl_Position.xy / 2.0 + vec2(0.5, 0.5);
}
 

Fragment shader:
uniform sampler2D texture;
uniform sampler2D background;

varying vec2 backPos;

void main()
{
        // lookup the pixel in the texture
        vec4 pixel = texture2D(texture, gl_TexCoord[0].xy) * gl_Color;

        // lookup the pixel in the background
        vec4 back = texture2D(background, backPos);

        if(back.a < 0.001)
        {
                // special case
                gl_FragColor = pixel;
        }
        else
        {
                // usual alpha blend
                gl_FragColor.rgb = pixel.rgb * pixel.a + back.rgb * (1.0 - pixel.a);
                gl_FragColor.a = pixel.a + back.a * (1.0 - pixel.a);
        }
}
 

Code (SFML.Net):
RenderTexture target = new RenderTexture(...);
Shader customBlendShader = ...;
customBlendShader.SetParameter("texture", Shader.CurrentTexture);
customBlendShader.SetParameter("background", target.Texture);
RenderStates customBlendState = new RenderStates(customBlendShader);
customBlendState.BlendMode = BlendMode.None;

...

target.Clear(Color.Transparent);
target.Draw(someSprite, customBlendState);
target.Display();

window.Clear(Color.White);
window.Draw(targetSprite);
window.Display();
 

But this is not ideal solution, because:
1. If operator in fragment shaders should be avoided
2. AFAIK same texture for reading and writing (target.Texture) may cause undefined behavior.

In my test application there is a small flickering on rendered RenderTexture.

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: Drawing Text on transparent RenderTexture leaves outline
« Reply #9 on: June 28, 2017, 06:35:01 pm »
Quote
That's the point - they do overlap
I really mean the glyph itself, not its quad.

Quote
If operator in fragment shaders should be avoided
This kind of generic statement is not relevant, just try it and see if it works for you.

Quote
AFAIK same texture for reading and writing (target.Texture) may cause undefined behavior.
Of course. I had something simpler in mind: if the source fragment is fully transparent then discard it -- that's enough to avoid the issue that you describe above with quads overlapping each other in italic mode.

uniform sampler2D texture;

void main()
{
    vec4 pixel = texture2D(texture, gl_TexCoord[0].xy);
    if (pixel.a == 0)
        discard;

    gl_FragColor = pixel * gl_Color;
}
(untested)

That's of course to use when you draw unblended text to the render-texture.
« Last Edit: June 28, 2017, 06:36:36 pm by Laurent »
Laurent Gomila - SFML developer

Xorton

  • Newbie
  • *
  • Posts: 7
    • View Profile
Re: Drawing Text on transparent RenderTexture leaves outline
« Reply #10 on: June 28, 2017, 08:28:14 pm »
Thanks for answer!
Yes, in most cases discarding transparent pixels (pixel.a < e) should do the trick.

I think about more complex case 8):

Text with border and shadow (prerendered or generated) with letter-by-letter appearance animation :)
Shadows usually rendered on separate render-texture, and glyphs overlaps each other.

dialer

  • Newbie
  • *
  • Posts: 7
    • View Profile
Re: Drawing Text on transparent RenderTexture leaves outline
« Reply #11 on: August 19, 2017, 03:38:01 pm »
You say that this is correct behavior when alpha blending the semi-transparent white text edges onto the (0, 0, 0, 0) background, but I disagree. I believe this is a bug (be it in SFML or OpenGL).

Firstly, the terms "Alpha Blending" and "Blend Mode Alpha" are vague. It should really be clarified that this refers to the Porter-Duff compositing mode "Source Over", and if it does not, well, that's a bug because it should.

The color and alpha formulae for this mode are:

co = αs x Cs + αb x Cb x (1 – αs)
αo = αs + αb x (1 – αs)


Where
co is the output color (with premultiplied alpha),
αo is the output alpha,
αs is source alpha,
αb is destination alpha,
Cs is source color,
Cb is destination color.

In this specific example, αs is a value between 0 and 1. It is 1 for pixels completely inside the glyphs, 0 completely outside the glyphs, and something inbetween on the edges. When blending this onto a (0, 0, 0, 0) background, αb is always 0, meaning that the Cb expression has no effect on the output color, no matter how you look at it. But you can clearly see that this is not the case. When changing the background to, say, (255, 0, 0, 0), the artifacts become red.

Xorton

  • Newbie
  • *
  • Posts: 7
    • View Profile
Re: Drawing Text on transparent RenderTexture leaves outline
« Reply #12 on: August 19, 2017, 04:40:59 pm »
The color and alpha formulae for this mode are:

co = αs x Cs + αb x Cb x (1 – αs)
αo = αs + αb x (1 – αs)



This formula will add same black borders due to alpha premultiplication. Blending color rgba(1.0, 1.0, 1.0, 0.5) on color rgba(..., ..., ..., 0.0) will result in color rgba(0.5, 0.5, 0.5, 0.5) instead of rgba(1.0, 1.0, 1.0, 0.5).

dialer

  • Newbie
  • *
  • Posts: 7
    • View Profile
Re: Drawing Text on transparent RenderTexture leaves outline
« Reply #13 on: August 19, 2017, 05:15:48 pm »
source = (1, 0, 0, 0.5) on destionation = (x1, x2, x3, 0.0):

co = 0.5 * (1, 0, 0) + 0.0 * (x1, x2, x3) * (1 - 0.5) = 0.5 * (1, 0, 0)

Maybe I should have clarified that Cs and Cb are not premultiplied, only co is.

Xorton

  • Newbie
  • *
  • Posts: 7
    • View Profile
Re: Drawing Text on transparent RenderTexture leaves outline
« Reply #14 on: August 19, 2017, 11:15:42 pm »

co = 0.5 * (1, 0, 0) + 0.0 * (x1, x2, x3) * (1 - 0.5) = 0.5 * (1, 0, 0)


And you get  0.5 * (1, 0, 0) = (0.5, 0, 0) as result color (instead of (1, 0, 0)).