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

Author Topic: [solved] Making a glow effect - Problems with alpha values [C++ SFML GLSL]  (Read 6948 times)

0 Members and 1 Guest are viewing this topic.

StackDanny

  • Newbie
  • *
  • Posts: 8
    • View Profile
    • Email
Hey I want to create a glow effect for my application, I already posted everything relevant to my question on StackOverflow here: https://stackoverflow.com/questions/54204592/making-a-glow-effect-problems-with-alpha-values-c-sfml-glsl
But it seems pretty dead there. So I would like to propose it on this forum aswell.

To reduce code size to the minimum, let's say I want to create a glow effect on an image. Starting with this one:



To produce an effect that looks like this:



It's a three step way.
- save all bright pixels from the scene (= luminescence)
- apply a blur effect on those pixels (= blur)
- draw original picture and the blur texture ontop (= assemble)

Step 1 and 3 are no Problem. The blur part just doesn't want to work correctly.
Before I explain further, here's my luminescence result:

Here I have set the threshold to be 0.67f - looks pretty good. But now when I try to blur this, I get some unlucky results:

This black edge comes from the fact, that any transparent color is black
vec4(0.0, 0.0, 0.0, 0.0)
I've read about this on this sfml forum, and the suggestion was to use SFML's sf::BlendMode for this and multiply the .rgb value of the final pixel color in the fragment shader with its alpha value. So I did and now this my result:

It's better, but definetely not good. The blur shader now also avarages out the surrounding pixels of the luminescence mask. After assembling it's just a blurry picture:

.. I tried "fixing" this in the shader files by checking if the pixel's alpha is zero. This way I don't value them when avaraging out. But since sf::BlendMode is activated, I don't know how alpha behaves now - So I deactivated the blendmode but I still have weird results. (at the very of this question I provided the code and a result from this attempt)

none of my attempts to fix this work. I really could use some help here. Maybe I'm doing something fundamentally wrong in the shaders.. here's the full code - If you want to compile it, make a folder "resources" with the 2 Fragment shaders and the background.jpg (: https://pixnio.com/architecture/city-downtown/urban-water-architecture-buildings-city-lights-evening)  (in 1280x720). You can use of course any other picture. I just used this one because it's free to use and dark enough to get some exciting glow effect.

luminescence.frag

#version 120

uniform sampler2D texture;
uniform float threshold;

void main(void){
    vec3 current_color = texture2D(texture, gl_TexCoord[0].xy).rgb;
    vec4 pixel =  vec4(current_color.rgb, 0.0);
    float brightness = dot(current_color.rgb, vec3(0.2126, 0.7152, 0.0722));
    if (brightness >= threshold){
        pixel = texture2D(texture, gl_TexCoord[0].xy);
    }
    gl_FragColor = pixel;
}

boxblur.frag
#version 120

uniform sampler2D texture;
uniform float texture_inverse;
uniform int blur_radius;
uniform vec2 blur_direction;

void main(void){
    vec4 sum = texture2D(texture, gl_TexCoord[0].xy);

    for (int i = 0; i < blur_radius; ++i){
        sum += texture2D(texture, gl_TexCoord[0].xy + (i * texture_inverse) * blur_direction);
        sum += texture2D(texture, gl_TexCoord[0].xy - (i * texture_inverse) * blur_direction);
    }
    vec4 pixel = vec4(sum / (blur_radius * 2 + 1));
    pixel.rgb *= pixel.a; //to negate the sf::BlendMode effect
    gl_FragColor = pixel;
}
main.cpp
#include <SFML/Graphics.hpp>
#include <iostream>
#include <exception>

void run() {
    const sf::Vector2f SIZE(1280, 720);

    sf::Texture background_tex;
    background_tex.loadFromFile("resources/background.jpg");
    sf::Sprite background(background_tex);



    sf::Shader luminescence;
    luminescence.loadFromFile("resources/luminescence.frag", sf::Shader::Fragment);
    luminescence.setUniform("texture", sf::Shader::CurrentTexture);
    luminescence.setUniform("threshold", 0.67f);

    sf::Shader blur;
    blur.loadFromFile("resources/boxblur.frag", sf::Shader::Fragment);
    blur.setUniform("texture", sf::Shader::CurrentTexture);
    blur.setUniform("texture_inverse", 1.0f / SIZE.x);




    sf::RenderStates shader_states;
    //this line sets the blendmode!
    shader_states.blendMode = sf::BlendMode(sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha);



    sf::ContextSettings context_settings;
    context_settings.antialiasingLevel = 12;

    //draws background
    sf::RenderTexture scene_render;
    scene_render.create(SIZE.x, SIZE.y, context_settings);

    //draws luminescence and blur
    sf::RenderTexture shader_render;
    shader_render.create(SIZE.x, SIZE.y, context_settings);



    sf::RenderWindow window(sf::VideoMode(SIZE.x, SIZE.y), "glsl fun", sf::Style::Default, context_settings);

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

        scene_render.clear();
        scene_render.draw(background);
        scene_render.display();


        //apply luminescence
        shader_states.shader = &luminescence;
        shader_render.clear(sf::Color::Transparent);
        shader_render.draw(sf::Sprite(scene_render.getTexture()), shader_states);
        shader_render.display();

        //apply two pass gaussian blur 3 times to simulate gaussian blur.
        shader_states.shader = &blur;
        float blur_radius = 30.0f;
        for (int i = 0; i < 3; ++i) {
            blur.setUniform("blur_radius", static_cast<int>(blur_radius));

            //vertical blur
            blur.setUniform("blur_direction", sf::Glsl::Vec2(1.0, 0.0));
            shader_render.draw(sf::Sprite(shader_render.getTexture()), shader_states);
            shader_render.display();

            //horizontal blur
            blur.setUniform("blur_direction", sf::Glsl::Vec2(0.0, 1.0));
            shader_render.draw(sf::Sprite(shader_render.getTexture()), shader_states);
            shader_render.display();

            //decrease blur_radius to simulate a gaussian blur
            blur_radius *= 0.45f;
        }

        //assembly
        window.clear();
        window.draw(sf::Sprite(scene_render.getTexture()));
        window.draw(sf::Sprite(shader_render.getTexture()));
        window.display();
    }
}

int main() {
    try {
        run();
    }
    catch (std::exception e) {
        std::cerr << "caught exception - - - " << e.what() << '\n';
        return 1;
    }
    return 0;
}

Ok and this is the attempt to modify boxblur.frag to exclude zero alpha values: (I removed
shader_states.blendMode = sf::BlendMode(sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha);
on line 29 in main.cpp for this of course):
#version 120

uniform sampler2D texture;
uniform float texture_inverse;
uniform int blur_radius;
uniform vec2 blur_direction;

void main(void){
    float div = 0.0;
    vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);
    vec4 temp_color = texture2D(texture, gl_TexCoord[0].xy);
    if (temp_color.a > 0.0){
        sum += temp_color;
        div += 1.0;
    }


    for (int i = 0; i < blur_radius; ++i){
        temp_color = texture2D(texture, gl_TexCoord[0].xy + (i * texture_inverse) * blur_direction);
        if (temp_color.a > 0.0){
            sum += temp_color;
            div += 1.0;
        }
        temp_color = texture2D(texture, gl_TexCoord[0].xy - (i * texture_inverse) * blur_direction);
        if (temp_color.a > 0.0){
            sum += temp_color;
            div += 1.0;
        }
    }
    vec4 pixel;
    if (div == 0.0){
        pixel = vec4(texture2D(texture, gl_TexCoord[0].xy).rgb, 0.0);
    }
    else{
        pixel = vec4(sum / div);
    }
    gl_FragColor = pixel;
}

And this results in:

[I am using Visual Studio 2017 Community] - Thanks for any help!
« Last Edit: January 21, 2019, 02:26:41 pm by StackDanny »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11034
    • View Profile
    • development blog
    • Email
Re: Making a glow effect - Problems with alpha values [C++ SFML GLSL]
« Reply #1 on: January 16, 2019, 09:51:31 am »
I'm not familiar enough with GLSL to provide you an actual solution, but to me it seems you've already identified the issue that all transparent pixels are black. That isn't something that has to be the case, you can also have a "white" transparent pixel.

When you generate your luminescence image, can you make sure that the transparent pixel aren't (0,0,0,0) but (1,1,1,0) (or in SFML terms (255,255,255,0)?
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

StackDanny

  • Newbie
  • *
  • Posts: 8
    • View Profile
    • Email
Re: Making a glow effect - Problems with alpha values [C++ SFML GLSL]
« Reply #2 on: January 16, 2019, 10:00:11 am »
Hi, I don't know how I can ensure that. However, I can simulate such an effect by keeping the sf::BlendMode but removing
pixel.rgb *= pixel.a;
from boxblur.frag. This way I get a White Edge effect. But this only Looks fine if the luminescence mask is mostly White. But that's rarely the case. I lowered the threshold to 0.3f and it Looks like this: (https://imgur.com/a/eZF18MY)
I just want the edge to be the same color as at the luminescence mask for a given point. It just seems unbelievable tricky to achieve this, something that seemed like an easy task at first glance.
« Last Edit: January 16, 2019, 10:05:49 am by StackDanny »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11034
    • View Profile
    • development blog
    • Email
Re: Making a glow effect - Problems with alpha values [C++ SFML GLSL]
« Reply #3 on: January 16, 2019, 10:11:28 am »
Most stuff that one is unfamiliar with will take a bit more time than expected. :D

Maybe the blur effect you picked is the wrong one.
As I said, I don't really understand how it works, but what you basically want to prevent is to combine the luminescence pixels with the transparent pixels, be it white or black. Instead you probably just want to have a gradient with the same color as the luminescence that decreases its alpha value from start pixel to the distance pixel.
So you basically have to make sure, to only deal with the luminescence pixels and to never sum/multiple it with the transparent pixels. At least that's my theory, whether that gets you the wanted effect, I'm not sure. ;)
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

StackDanny

  • Newbie
  • *
  • Posts: 8
    • View Profile
    • Email
Re: Making a glow effect - Problems with alpha values [C++ SFML GLSL]
« Reply #4 on: January 16, 2019, 10:25:31 am »
I am sure that the boxblur is right choice (either that or gaussian blur ; basically the same thing), the theory behind this seems correct; https://learnopengl.com/Advanced-Lighting/Bloom scroll down to paragraph "Gaussian blur" - they use the same idea!

fallahn

  • Hero Member
  • *****
  • Posts: 507
  • Buns.
    • View Profile
    • Trederia
Re: Making a glow effect - Problems with alpha values [C++ SFML GLSL]
« Reply #5 on: January 16, 2019, 10:45:16 am »
There's a good example of this in the SFML Game Development book, the source of which can be found here. The main difference that I can see is that the luminescence values extracted in book example don't have a transparent background, rather they remain black. This is because when the blurred image is reapplied it is done additively, not by multiplying. The black pixels are effectively 0, so adding 0 the original image has the same result as if the dark pixels were transparent.

StackDanny

  • Newbie
  • *
  • Posts: 8
    • View Profile
    • Email
Re: Making a glow effect - Problems with alpha values [C++ SFML GLSL]
« Reply #6 on: January 16, 2019, 10:59:49 am »
If I draw a black texture above my scene, I see the black texture. If I set the luminescence Pixels to Black and blur it, I also blur the black Pixels. So I am not sure if it's really additvely. The Question is, If I can set up sf::RenderWindow in a way, that drawing is done additevely and not alpha channel based. Or I can also just ignore the alpha values in the blur shader, which I am trying to do. So 2 possible ways.

fallahn

  • Hero Member
  • *****
  • Posts: 507
  • Buns.
    • View Profile
    • Trederia
Re: Making a glow effect - Problems with alpha values [C++ SFML GLSL]
« Reply #7 on: January 16, 2019, 12:09:09 pm »
You can set the blend mode to sf::BlendAdd when rendering in SFML, or, as the example I linked shows, use a shader which adds the values together:

https://github.com/SFML/SFML-Game-Development-Book/blob/master/08_Graphics/Media/Shaders/Add.frag

I highly recommend getting a copy of the book if you can, don't let the fact that it's a few years old put you off - the contents are still very relevant :D

StackDanny

  • Newbie
  • *
  • Posts: 8
    • View Profile
    • Email
Re: Making a glow effect - Problems with alpha values [C++ SFML GLSL]
« Reply #8 on: January 16, 2019, 12:34:00 pm »
Wow that works!. I will post the working Code here (and on StackoverFlow) for people that have the same/similiar problem.  Thank you so much!

StackDanny

  • Newbie
  • *
  • Posts: 8
    • View Profile
    • Email
Re: Making a glow effect - Problems with alpha values [C++ SFML GLSL]
« Reply #9 on: January 16, 2019, 02:44:11 pm »
Thanks to Fallahn, who showed me correct approach to this problem. Before tackling that, I would like to show you my results:

Luminescence (threshold = 0.24f):

Blur (4 Layers):

Assembled:


Yay! The solution is to set all transparent pixels to solid black vec4(0.0, 0.0, 0.0, 1.0) and than after they've been blurred, just add them ontop the scene:
vec4 tex_color = texture2D(texture, gl_TexCoord[0].xy);
vec4 add_color = texture2D(add_texture, gl_TexCoord[0].xy);
gl_FragColor = tex_color + add_color;
This way, if add_color is black ("transparent"), we add tex_color + vec4(0.0, 0.0, 0.0, 1.0) which results in no change!
This is great, because now you can ignore the alpha channel completely.
To understand why I find this so great, you can read this little rant here (feel free to skip it):
Without haying to worry about alpha you can ignore any sf::BlendMode like the confusing sf::BlendMode::OneMinusSrcAlpha which caused me headaches for 2 solid days. Try calculating any reasonable "true" alpha value when you know they are all premultiplied. Of course you also have to multiply all rgb values with the pixel's alpha to reverse the prumultiplication… the formulas escalate quite quickly from here. Also subtract 1 from alpha because it's OneMinusSrcAlpha... and don't forget to check for cases where the sum of all alphas (yes, you need to sum that) is 0 (or in OneMinusSrcAlpha matter, something else), because otherwise you get division by 0 (or in OneMinusSrcAlpha matter a division by 0 when all surrounding pixels are solid). Also sometimes weird alpha values might work, but only for a single pass of blur, but in my case I have multiple passes.. etc.
Here's the final code:

luminescence.frag
#version 120

uniform sampler2D texture;
uniform float threshold;

void main(void){
    vec3 current_color = texture2D(texture, gl_TexCoord[0].xy).rgb;
    vec4 pixel =  vec4(0.0, 0.0, 0.0, 1.0);
    float brightness = dot(current_color.rgb, vec3(0.2126, 0.7152, 0.0722));
    if (brightness >= threshold){
        pixel = texture2D(texture, gl_TexCoord[0].xy);
    }
    gl_FragColor = pixel;
}
boxblur.frag
#version 120

uniform sampler2D texture;
uniform float texture_inverse;
uniform int blur_radius;
uniform vec2 blur_direction;

void main(void){
    vec4 sum = texture2D(texture, gl_TexCoord[0].xy);

    for (int i = 0; i < blur_radius; ++i){
        sum += texture2D(texture, gl_TexCoord[0].xy + (i * texture_inverse) * blur_direction);
        sum += texture2D(texture, gl_TexCoord[0].xy - (i * texture_inverse) * blur_direction);
    }
    gl_FragColor = sum / (blur_radius * 2 + 1);
}
multiply.frag
#version 120

uniform sampler2D texture;
uniform float multiply;

void main(void){
    gl_FragColor = texture2D(texture, gl_TexCoord[0].xy) * multiply;
}
assemble.frag
#version 120

uniform sampler2D texture;
uniform sampler2D add_texture;
uniform float add_weight;

void main(void){
    vec4 tex_color = texture2D(texture, gl_TexCoord[0].xy);
    vec4 add_color = texture2D(add_texture, gl_TexCoord[0].xy) * add_weight;
    gl_FragColor = tex_color + add_color;
}

main.cpp
#include <SFML/Graphics.hpp>
#include <iostream>
#include <array>

void run() {
    const sf::Vector2f SIZE(1280, 720);

    sf::Texture background_tex;
    background_tex.loadFromFile("resources/background.jpg");
    sf::Sprite background(background_tex);

    sf::Shader luminescence_shader;
    luminescence_shader.loadFromFile("resources/luminescence.frag", sf::Shader::Fragment);
    luminescence_shader.setUniform("texture", sf::Shader::CurrentTexture);
    luminescence_shader.setUniform("threshold", 0.24f);

    sf::Shader blur_shader;
    blur_shader.loadFromFile("resources/boxblur.frag", sf::Shader::Fragment);
    blur_shader.setUniform("texture", sf::Shader::CurrentTexture);
    blur_shader.setUniform("texture_inverse", 1.0f / SIZE.x);

    sf::Shader assemble_shader;
    assemble_shader.loadFromFile("resources/assemble.frag", sf::Shader::Fragment);
    assemble_shader.setUniform("texture", sf::Shader::CurrentTexture);

    sf::Shader multiply_shader;
    multiply_shader.loadFromFile("resources/multiply.frag", sf::Shader::Fragment);
    multiply_shader.setUniform("texture", sf::Shader::CurrentTexture);


    sf::RenderStates shader_states;
    //no blendmode! we make our own - assemble.frag

    sf::ContextSettings context_settings;
    context_settings.antialiasingLevel = 12;

    //draws background
    sf::RenderTexture scene_render;
    scene_render.create(SIZE.x, SIZE.y, context_settings);

    sf::RenderTexture luminescence_render;
    luminescence_render.create(SIZE.x, SIZE.y, context_settings);

    //draws luminescence and blur
    sf::RenderTexture assemble_render;
    assemble_render.create(SIZE.x, SIZE.y, context_settings);



    //addding multiple boxblurs with different radii looks really nice! in this case 4 layers
    std::array<sf::RenderTexture, 4> blur_renders;
    for (int i = 0; i < blur_renders.size(); ++i) {
        blur_renders[i].create(SIZE.x, SIZE.y, context_settings);
    }
    const int BLUR_RADIUS_VALUES[] = { 250, 180, 125, 55 };
    float blur_weight = blur_renders.empty() ? 0.0 : 1.0 / blur_renders.size();

    sf::RenderWindow window(sf::VideoMode(SIZE.x, SIZE.y), "glsl fun", sf::Style::Default, context_settings);

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

        //first draw the scene
        scene_render.clear();
        scene_render.draw(background);
        scene_render.display();


        //apply luminescence
        shader_states.shader = &luminescence_shader;
        luminescence_render.clear();
        luminescence_render.draw(sf::Sprite(scene_render.getTexture()), shader_states);
        luminescence_render.display();

        //apply two pass gaussian blur n times to simulate gaussian blur.
        shader_states.shader = &blur_shader;
        for (int i = 0; i < blur_renders.size(); ++i) {
            blur_shader.setUniform("blur_radius", BLUR_RADIUS_VALUES[i]);

            blur_renders[i].clear();
            blur_renders[i].draw(sf::Sprite(luminescence_render.getTexture()));
            blur_renders[i].display();

            //vertical blur
            blur_shader.setUniform("blur_direction", sf::Glsl::Vec2(1.0, 0.0));
            blur_renders[i].draw(sf::Sprite(blur_renders[i].getTexture()), shader_states);
            blur_renders[i].display();

            //horizontal blur
            blur_shader.setUniform("blur_direction", sf::Glsl::Vec2(0.0, 1.0));
            blur_renders[i].draw(sf::Sprite(blur_renders[i].getTexture()), shader_states);
            blur_renders[i].display();
        }

        //load blur_renders[0] into assemble_render so we can add the other blurs ontop of it
        shader_states.shader = &multiply_shader;
        multiply_shader.setUniform("multiply", blur_weight);
        assemble_render.clear();
        assemble_render.draw(sf::Sprite(blur_renders[0].getTexture()), shader_states);
        assemble_render.display();

        //adding the rest ontop creating a final blur
        shader_states.shader = &assemble_shader;
        assemble_shader.setUniform("add_weight", blur_weight);
        for (int i = 1; i < blur_renders.size(); ++i) {
            assemble_shader.setUniform("add_texture", blur_renders[i].getTexture());
            assemble_render.draw(sf::Sprite(assemble_render.getTexture()), shader_states);
            assemble_render.display();
        }

        //final result; scene + blur
        assemble_shader.setUniform("add_weight", 1.0f);
        assemble_shader.setUniform("add_texture", assemble_render.getTexture());
        assemble_render.draw(sf::Sprite(scene_render.getTexture()), shader_states);
        assemble_render.display();

        window.clear();
        window.draw(sf::Sprite(assemble_render.getTexture()));
        window.display();
    }
}

int main() {
    try {
        run();
    }
    catch (std::exception e) {
        std::cerr << "caught exception - - - " << e.what() << '\n';
        return 1;
    }
    return 0;
}
« Last Edit: January 16, 2019, 03:55:38 pm by StackDanny »