Making a glow effect - Problems with alpha values

时间:2019-01-15 18:08:45

标签: glsl sfml blur

I want to create a glow effect for my game. In order to keep this minimalistic let's say I want to glow an Image. :)

Starting with this one:

default picture, no effect

To get something like this:desired output, picture with glow effect

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: the picture, but all darker pixels are transparent (threshold = 0.67f)

An now when I blur this, I get some unlucky results:enter image description here This black edge comes from the fact, that any transparent color is black vec4(0.0, 0.0, 0.0, 0.0). It's not an unkown Problem within SFML/GLSL, 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:

blurred, but overall too dark, especially the edges

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:

Blurry Picture, not even close to resemble a glow effect.. 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 (in 1280x720).

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;
    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;
    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;
}

This is the boxblur.frag where I tried to exclude zero alpha values: (I removed shader_states.blendMode = sf::BlendMode(sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha);on line 29 in main.cpp 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;
}

Resulting in: mostly bright values. but too blocky and not smooth at all

[I am using Visual Studio 2017 Community] - Thanks for any help!

2 个答案:

答案 0 :(得分:2)

我也在en.sfml-dev.org(here)上发布了这个问题,fallahn向我展示了正确的方法。  在解决之前,下面是图片结果:

发光(阈值= 0.24f): luminescence picture

模糊(4层): blur

已组装: glowing picture

是的!解决方案是将所有透明像素设置为纯黑色vec4(0.0, 0.0, 0.0, 1.0),然后将其模糊,然后将它们添加到场景的顶部:

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;

这样,如果add_color是黑色(“透明”),我们添加tex_color + vec4(0.0, 0.0, 0.0, 1.0)不会导致任何变化!

这很棒,因为现在您可以完全忽略alpha通道。

要了解为什么我觉得这么好,您可以在这里阅读以下内容(随意跳过):

不用担心Alpha,您可以忽略任何sf::BlendMode,例如令人困惑的sf::BlendMode::OneMinusSrcAlpha,这使我头痛了2天。当您知道它们均已预乘时,请尝试计算任何合理的“真实” alpha值。当然,您还必须将 all 个rgb值与像素的alpha乘以反向乘乘…公式从此处迅速升级。还要从alpha中减去1,因为它是OneMinusSrcAlpha ...,并且别忘了检查所有alpha的总和(是的,您需要求和)为0(或在OneMinusSrcAlpha中,等等),因为否则您将被0除(或在OneMinusSrcAlpha中,当所有周围像素都是实心时被0除)。有时也可能会使用奇怪的alpha值,但仅适用于一次模糊处理,但就我而言,我具有多次通过处理..等

这是最终代码:

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;
}

答案 1 :(得分:1)

尝试做一个小例子,您只想平均两个像素。左(L)和右(R)。然后,左像素由R(L),G(L),B(L),A(L)组成,右像素由R(R),G(R),B(R)和A(R)。

如果没有Alpha,则Blue的平均值将为:

(B(L)+B(R)) / 2

考虑到alpha,它变为:

(B(L)*A(L)+B(R)*A(R)) / (A(L)+A(R))

我们可以直接看到,在完全实心像素(alpha = 1)的情况下,我们得到与上面完全相同的公式:

(B(L)*1+B(R)*1) / (1+1)  =  (B(L)+B(R)) / 2

此外,假设右边的像素是完全透明的,左边的是纯色的,那么右边的像素的颜色分量将不会有任何影响,结果与左边的像素完全相同,这正是我们想要的:

(B(L)*1+B(R)*0) / (1+0)  =  (B(L)) / 1  = B(L)

两个像素都完全透明会变成退化的情况,必须以某种优雅的方式进行处理。

现在您要做的就是将其扩展到两个像素以上。 :-)