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

Author Topic: [SOLVED] sf::Shaders, sf::RenderTextures, transparency, and black pixels  (Read 6716 times)

0 Members and 2 Guests are viewing this topic.

Millsialix

  • Guest
Hi,
I'm trying to create a glow effect with shaders, and for that I blur an image and I draw a copy of that image above. If I clear the renderTextures with any color, everything works, but when I use renderTexture.clear( sf::Color::Transparent ), my shader reacts like if the background was black, so there is a black thing around my blur.

Generally, the transparent pixels are considered as black transparent ones, so in the beginning, I thought that my shader was not aware of the alpha of the pixels (which was true ^^), but now I have no idea of why it doesn't work with transparent renderTextures.

In a nutshell: why there is a black thing around my blurs when I clear my renderTextures with transparent colors, and how to make it works? I think the problem is not in my shader, but I'm not 100% sure

main.cpp:
#include <SFML/Graphics.hpp>

int main(){

    // Main window, where I draw the render of the glowing and non-glowing objects together
    sf::RenderWindow mainWindow( sf::VideoMode( 512, 512 ), "SFML 2" );
        mainWindow.setFramerateLimit( 60 );
        mainWindow.setVerticalSyncEnabled( true );


    // Render for the non-glowing objects
    sf::RenderTexture renderNormal; renderNormal.create( 512, 512 );
    // Render for the glowing objects
    sf::RenderTexture renderBlur; renderBlur.create( 512, 512 );

    // Render of the blur on the x axis
    sf::RenderTexture renderBlurX; renderBlurX.create( 512, 512 );
    // Render of the blur on the y axis, after the blur on the x axis
    sf::RenderTexture renderBlurY; renderBlurY.create( 512, 512 );


    // Texture 512*256 with the text "to blur"
    sf::Texture textureToBlur; textureToBlur.loadFromFile( "data/1.png" );
    // Texture 512*256 with the text "to not"
    sf::Texture textureToNot; textureToNot.loadFromFile( "data/2.png" );


    // Sprite with the text "to blur":
    sf::Sprite spriteBlurObject( textureToBlur );
    // Sprite with the text "to not":
    sf::Sprite spriteNormalObject( textureToNot );
    // Same sprite as 'spriteBlurObject', but this one will be blured and put behind 'spriteBlurObject':
    sf::Sprite spriteBlurObjectback( textureToBlur );

    // Sprite where I render the non-glowing objects
    sf::Sprite spriteRenderNormal( renderNormal.getTexture());
    // Sprite where I render the glowing objects
    sf::Sprite spriteRenderGlow( renderBlur.getTexture());
    // Sprite for the blur on the x axis
    sf::Sprite spriteRenderBlurX( renderBlurX.getTexture());
    // Sprite for the blur on the y axis, after the blur on the x axis
    sf::Sprite spriteRenderBlurY( renderBlurY.getTexture());

    // The text 'to not' must not overlap with the text 'to glow'
    spriteNormalObject.setPosition( 0, 256 );

    // Shader which transform a texture, to create a blur on the x axis
    sf::Shader shaderBlurX;
        shaderBlurX.loadFromFile( "blur.frag", sf::Shader::Fragment );
        shaderBlurX.setUniform( "texture", textureToBlur );
        shaderBlurX.setUniform( "invertTextureSize", 1.0f/textureToBlur.getSize().x );
        shaderBlurX.setUniform( "blurSize", 7 );
        shaderBlurX.setUniform( "direction", sf::Vector2f( 1, 0 ));

    // Same as 'shaderBlurX', but for the y axis
    sf::Shader shaderBlurY;
        shaderBlurY.loadFromFile( "blur.frag", sf::Shader::Fragment );
        shaderBlurY.setUniform( "texture", renderBlurX.getTexture());
        shaderBlurY.setUniform( "invertTextureSize", 1.0f/renderBlur.getTexture().getSize().y );
        shaderBlurY.setUniform( "blurSize", 7 );
        shaderBlurY.setUniform( "direction", sf::Vector2f( 0, 1 ));

    // A simple shader to enhance alpha, it is not necessary
    sf::Shader shaderAlpha;
        shaderAlpha.loadFromFile( "prog/alpha.frag", sf::Shader::Fragment );
        shaderAlpha.setUniform( "texture", renderBlurY.getTexture());

    while( mainWindow.isOpen()){
        sf::Event event;
        while( mainWindow.pollEvent( event )){
            switch( event.type ){
                case sf::Event::Closed:
                    mainWindow.close();
                    break;
                default:
                    break;
            }
        }

        // Create the blur on the x axis
        //renderBlurX.clear( sf::Color::Transparent ); // With this clear, there is a black thing around the blur
        renderBlurX.clear( sf::Color::White ); // With this clear, everything works but there is no transparency
        renderBlurX.draw( spriteBlurObjectback, &shaderBlurX );
        renderBlurX.display();

        // Create the blur on the y axis, after the blur on the x axis
        renderBlurY.clear( sf::Color::Transparent );
        renderBlurY.draw( spriteRenderBlurX, &shaderBlurY );
        renderBlurY.display();

        // Draw the glowing objects
        renderBlur.clear( sf::Color::Transparent );
        renderBlur.draw( spriteRenderBlurY, &shaderAlpha ); // Glowing (which is just a blur)/
        renderBlur.draw( spriteBlurObject ); // Object himself
        renderBlur.display();

        // Draw the non-glowing objects
        renderNormal.clear( sf::Color::Transparent );
        renderNormal.draw( spriteNormalObject );
        renderNormal.display();

        // Draw the glowing and non-glowing objects
        mainWindow.clear( sf::Color::White );
        mainWindow.draw( spriteRenderNormal );
        mainWindow.draw( spriteRenderGlow );
        mainWindow.display();
    }
}
 

blur.frag:
#version 330

uniform sampler2D texture; // Texture which will be modified
uniform float invertTextureSize; // Invert of the size of the texture, on the axis we working on
uniform int blurSize; // Size of the blur
uniform vec2 direction; // Direction of the blur (only (0;1) or (1;0))

void main(){
    vec4 color = vec4( 0 );
    vec4 tmpColor;
    float sumColors = 0;
    float sumAlphas = 0;

    for( int i = -blurSize; i <= blurSize; ++i ){
        tmpColor = texture2D( texture, vec2(
                gl_TexCoord[0].s + ( direction.x*i*invertTextureSize ),
                gl_TexCoord[0].t + ( direction.y*i*invertTextureSize )
        ));

        color.rgb += tmpColor.rgb * tmpColor.a;
        color.a += tmpColor.a;

        sumColors += tmpColor.a;
        sumAlphas += 1;
    }
    color.rgb /= sumColors;
    color.a /= sumAlphas;
    gl_FragColor = vec4( color.r, color.g, color.b, color.a );
}
 

alpha.frag:
#version 330

uniform sampler2D texture;

void main(){
    vec4 color = texture2D( texture, gl_TexCoord[0].st );
    color.w *= 2.5;
    gl_FragColor = color;
}
 
« Last Edit: July 19, 2018, 07:46:07 pm by Millsialix »

Hapax

  • Hero Member
  • *****
  • Posts: 3379
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: sf::Shaders, sf::RenderTextures, transparency, and black pixels
« Reply #1 on: July 15, 2018, 04:50:52 am »
sf::Transparent is black; it's just a transparent black. If you use the RGB components of sf::Transparent, they'll all be zero.
Still, even if you use sf::Color(255, 255, 255, 0), which is transparent white, you're still multiplying the RGB components by the alpha so the result for that pixel will always be zero since the alpha is zero.
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

Millsialix

  • Guest
Re: sf::Shaders, sf::RenderTextures, transparency, and black pixels
« Reply #2 on: July 15, 2018, 01:34:57 pm »
I know, and that's why in my shader I don't add 1 to 'sumColors' at each loop, but the alpha of the pixel, so if the pixel is transparent, I add nothing to the final color, and nothing to 'sumColors', so (in theory), if the shader detects a transparent pixel of any color, it basically does nothing

oomek

  • Jr. Member
  • **
  • Posts: 90
    • View Profile
    • Email
Re: sf::Shaders, sf::RenderTextures, transparency, and black pixels
« Reply #3 on: July 15, 2018, 08:23:44 pm »
I think your case is very simillar to mine which I solved a while ago. Even if you clear the render texture with Transparent the color information is still black.

What I did is I set the blend mode for RenderTexture to:
sf::BlendMode(sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha)

You may also need to premultiply the colour with alpha in your shader at the end
gl_FragColor.xyz *= gl_FragColor.w

Millsialix

  • Guest
Re: sf::Shaders, sf::RenderTextures, transparency, and black pixels
« Reply #4 on: July 15, 2018, 10:45:39 pm »
Quote
What I did is I set the blend mode for RenderTexture to:
sf::BlendMode(sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha)

How do you do that? I tried to create a sf::RenderState to use a blendMode with a shader but it changes nothing, I don't really understand blendModes so it is possible that I did something wrong

Quote
You may also need to premultiply the colour with alpha in your shader at the end
gl_FragColor.xyz *= gl_FragColor.w

Done, but it's not gonna darken the partially transparent pixels? Or it is for the blendModes?

oomek

  • Jr. Member
  • **
  • Posts: 90
    • View Profile
    • Email
Re: sf::Shaders, sf::RenderTextures, transparency, and black pixels
« Reply #5 on: July 15, 2018, 11:19:43 pm »
sf::RenderStates states;
states.shader = &your_shader;
states.blendMode = sf::BlendMode(sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha);
window.draw(sprite, states)
 

The last line in your shader is sometmes needed, sometimes not, depending on what you draw on the rendertexture. If you see a white border around your objects you need to premultiply alpha to colour
« Last Edit: July 15, 2018, 11:23:35 pm by oomek »

Millsialix

  • Guest
Re: sf::Shaders, sf::RenderTextures, transparency, and black pixels
« Reply #6 on: July 16, 2018, 02:16:40 pm »
That's what I did and it changes nothing, I tried on the renderTextures (those for the blur on the x and y axis) and on the renderWindow, and there is always this black blur around the objects

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: sf::Shaders, sf::RenderTextures, transparency, and black pixels
« Reply #7 on: July 16, 2018, 03:58:01 pm »
You should simplify your code if you want to find what's wrong with it. For example, you use two render-textures and this could be the cause of your problem; so, try first with a single render-texture and see what happens. Then remove everything that is not necessary (the alpha shader), and maybe use the same instance of the shader instead of duplicating it. Try simple things, debug intermediate results, instead of being stuck on the final result ;)
Laurent Gomila - SFML developer

Millsialix

  • Guest
Re: sf::Shaders, sf::RenderTextures, transparency, and black pixels
« Reply #8 on: July 16, 2018, 06:00:36 pm »
I'm sorry to tell you that I already tried to simplify my code, I tried with only the blur on the x axis (not even the texture above the blur, just the blur himself) and I tried to add a condition in my shader to take only the non-transparent pixels, nothing worked, but I discovered something weird:
I cleared the renderTextures with transparent colors (like sf::Color(0,255,0,0) for example) and I've noticed that the color around the blur is not the color of the renderTexture where I apply the shader, but the color of the renderTexture where I draw the sprite of the previous renderTexture. For example, with the following code:

        // Blend mode + shader
        sf::RenderStates stateBlurX;
        stateBlurX.blendMode = sf::BlendMode( sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha );
        stateBlurX.shader = &shaderBlurX;

        // Draw the blur on the x axis
        renderBlurX.clear( sf::Color( 255, 0, 0, 0 )); // This color is transparent
        renderBlurX.draw( spriteBlurObjectback, stateBlurX );
        renderBlurX.display();

        // Drawing on the renderTexture for the glowing objects
        renderBlur.clear( sf::Color( 0, 0, 255, 0 )); // This color is around the blur
        renderBlur.draw( spriteRenderBlurX );
        renderBlur.display();

        // Draw everything in the main window
        mainWindow.clear( sf::Color::White );
        mainWindow.draw( spriteRenderGlow );
        mainWindow.display();
 

... there will be a blue thing around the blur, and honestly, I don't know why it does that, and I know even less how to fix that

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: sf::Shaders, sf::RenderTextures, transparency, and black pixels
« Reply #9 on: July 16, 2018, 10:40:14 pm »
This is typical when you mix render-textures and transparency. Your alpha channel can easily get messed up, and that's often because of the blending mode.

The problem is that the default blending mode, which is standard alpha, will most likely not produce what you expect. For example, if you draw a sprite with transparency on a render-texture, and then draw that render-texture on your window, you'll see different results than what you expect. Now try without alpha-blending ("None" blend mode), and things work as expected again, because you just copy the alpha information instead of applying (and losing) it; you want to do that only when you get to the final drawing surface (the window).

Your case is more complex, but if you take the blending mode in consideration at each drawing step, then things become clearer and what you see on screen suddenly finds an explanation ;)
« Last Edit: July 16, 2018, 10:42:03 pm by Laurent »
Laurent Gomila - SFML developer

oomek

  • Jr. Member
  • **
  • Posts: 90
    • View Profile
    • Email
Re: sf::Shaders, sf::RenderTextures, transparency, and black pixels
« Reply #10 on: July 16, 2018, 11:14:02 pm »


I've had a few moments to look at your code and I fixed it. If this is what you wanted to achieve I'll post it.

But this is an alpha glow, and I'm not sure if you wanted an additive glow. ( mind you an additive glow won't work with black )

And btw, this shader "enhancing" alpha I believe you wrote to hide the black fringing around the edges which are actually an artifact of not using a proper blending mode when you draw a rendertexture.
« Last Edit: July 17, 2018, 12:24:01 am by oomek »

Millsialix

  • Guest
Re: sf::Shaders, sf::RenderTextures, transparency, and black pixels
« Reply #11 on: July 17, 2018, 12:34:17 am »
Solved! I added sf::BlendNone to my sf::RenderState and it worked, but I think I will work on these blend modes for a while, to be sure I understand the concept

oomek

  • Jr. Member
  • **
  • Posts: 90
    • View Profile
    • Email
Re: sf::Shaders, sf::RenderTextures, transparency, and black pixels
« Reply #12 on: July 17, 2018, 12:53:41 am »
When you use BlendNone better check if you do not have any undesired results when textures are overlapping.

Anyway, here's my skimmed version (alpha safe)

#include <SFML/Graphics.hpp>

int main() {

    // Main window, where I draw the render of the glowing and non-glowing objects together
    sf::RenderWindow mainWindow( sf::VideoMode( 512, 512 ), "SFML 2" );
    //it's not generally advised to use both frame limit and vsync
    //mainWindow.setFramerateLimit( 60 );
    mainWindow.setVerticalSyncEnabled( true );


    // Render for the non-glowing objects
    sf::RenderTexture renderNormal; renderNormal.create( 512, 512 );
    // Render of the blur on the x axis
    sf::RenderTexture renderBlurX; renderBlurX.create( 512, 512 );
    // Render of the blur on the y axis, after the blur on the x axis
    sf::RenderTexture renderBlurY; renderBlurY.create( 512, 512 );

    // Texture 512*256 with the text "to blur"
    sf::Texture texture; texture.loadFromFile( "Blur/test.png" );
    // Sprite from texture
    sf::Sprite sprite( texture );

    // Sprite where I render the non-glowing objects
    sf::Sprite spriteRenderNormal( renderNormal.getTexture() );
    // Sprite for the blur on the x axis
    sf::Sprite spriteRenderBlurX( renderBlurX.getTexture() );
    // Sprite for the blur on the y axis, after the blur on the x axis
    sf::Sprite spriteRenderBlurY( renderBlurY.getTexture() );

    // Shader which transform a texture, to create a blur
    sf::Shader shaderBlur;
    shaderBlur.loadFromFile( "Blur/blur.frag", sf::Shader::Fragment );
    shaderBlur.setUniform( "invertTextureSize", 1.0f / renderBlurX.getSize().x );
    shaderBlur.setUniform( "blurSize", 7 );
    //shaderBlurX.setUniform( "texture", textureToBlur ); //not needed if you use just one texture

    sf::BlendMode blendPremultiplied = sf::BlendMode( sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha );

    sf::RenderStates statesBlur;
    statesBlur.blendMode = blendPremultiplied;
    statesBlur.shader = &shaderBlur;

    sf::RenderStates statesPremultiplied;
    statesPremultiplied.blendMode = blendPremultiplied;


    while ( mainWindow.isOpen() ) {
        sf::Event event;
        while ( mainWindow.pollEvent( event ) ) {
            switch ( event.type ) {
            case sf::Event::Closed:
                mainWindow.close();
                break;
            default:
                break;
            }
        }

        // Create the blur on the x axis
        renderBlurX.clear( sf::Color::Transparent );
        shaderBlur.setUniform( "direction", sf::Vector2f( 1, 0 ) );
        renderBlurX.draw( sprite, statesBlur );
        renderBlurX.display();

        // Create the blur on the y axis, after the blur on the x axis
        renderBlurY.clear( sf::Color::Transparent );
        shaderBlur.setUniform( "direction", sf::Vector2f( 0, 1 ) );
        renderBlurY.draw( spriteRenderBlurX, statesBlur );
        renderBlurY.display();

        // Draw the non-glowing objects on a rendertexture with default blending
        // I'm not sure why you used it here, but it's useful if you want to group objects
        // and scale them or add some effects to them all at once
        renderNormal.clear( sf::Color::Transparent );
        sprite.setPosition( 0, 256 );
        renderNormal.draw( sprite );
        renderNormal.display();

        // mainWindow draws
        mainWindow.clear( sf::Color( 100, 100, 150, 255 ));

        mainWindow.draw( spriteRenderBlurY ); // Glowing (which is just a blur)/
        sprite.setPosition( 0, 0 );
        mainWindow.draw( sprite ); // Object himself

        //if you draw a rendertexture with objects on it you always do it with that special blending mode
        mainWindow.draw( spriteRenderNormal, statesPremultiplied );

        mainWindow.display();
    }
}