【问题标题】:Making a glow effect - Problems with alpha values制作发光效果 - alpha 值问题
【发布时间】:2019-06-09 19:17:07
【问题描述】:

我想为我的游戏创建一个发光效果。为了保持这种简约,假设我想为图像发光。 :)

从这个开始:

要得到这样的东西:

这是一个三步法。

  • 保存场景中的所有明亮像素(= 发光)
  • 对这些像素应用模糊效果(= 模糊)
  • 在顶部绘制原始图片和模糊纹理(= 组装)

第 1 步和第 3 步没问题。模糊部分只是不想正常工作。

在我进一步解释之前,这是我的发光结果: (阈值 = 0.67f)

现在,当我模糊这一点时,我得到了一些不幸的结果: 这个黑边来自这样一个事实,即任何透明颜色都是黑色vec4(0.0, 0.0, 0.0, 0.0)。这不是 SFML/GLSL 中的未知问题,建议为此使用 SFML 的 sf::BlendMode 并将片段着色器中最终像素颜色的 .rgb 值与其 alpha 值相乘。所以我做到了,现在这是我的结果:

更好,但绝对不好。模糊着色器现在还平均化发光蒙版的周围像素。组装后只是一张模糊的图片:

.. 我尝试通过检查像素的 alpha 是否为零来在着色器文件中“修复”这个问题。这样我在平均时就不重视它们。但是由于 sf::BlendMode 被激活,我不知道 alpha 现在的行为如何 - 所以我停用了混合模式,但我仍然有奇怪的结果。 (在这个问题的最开始,我提供了代码和这次尝试的结果)


我没有尝试修复这项工作。我真的可以在这里使用一些帮助。也许我在着色器中做了一些根本错误的事情......这是完整的代码 - 如果要编译它,请使用 2 个片段着色器和 background.jpg(在 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;
}

这是我试图排除零 alpha 值的 boxblur.frag:(当然,我在 main.cpp 的第 29 行删除了 shader_states.blendMode = sf::BlendMode(sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha);):

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

导致:

[我正在使用 Visual Studio 2017 社区] - 感谢您的帮助!

【问题讨论】:

  • 不检查 alpha>0,您必须将每种颜色乘以它的 alpha,然后除以在本地找到的总 alpha。大量计算...我有使用 alpha 通道进行卷积效果的代码。在这里发布了好几次,但没有人理解它.... :-/
  • @DanByström 嘿,这听起来很有趣,你能给我一个你的代码的链接吗?
  • 哦,是的,我以前见过这个,是的,我不明白。 T = 红色,M = 绿色,B = 蓝色,D = 阿尔法?什么是后缀LMR?我也不明白 alpha 偏移量。
  • 这是因为这是实时优化的代码,旨在快速而不是可读的 T/M/R 代表顶部、中间和底部扫描线,因为这是一个 3x3 卷积。我已经尝试过回答。

标签: glsl sfml blur


【解决方案1】:

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

发光(阈值 = 0.24f):

模糊(4 层):

组装完毕:

耶!解决方案是将所有透明像素设置为纯黑色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 值。当然,您还必须将 所有 rgb 值与像素的 alpha 相乘以反转 prumultiplication……公式从这里迅速升级。还要从 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;
}

【讨论】:

  • 缺少“multiply_shader”。可以加一下吗
【解决方案2】:

试着做一个你只想平均两个像素的小例子。左 (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)

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

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

【讨论】:

  • 哦,谢谢我明白了,我现在就尝试实现它,看看它是否有效!
  • 祝你好运!请随时在这里告诉我们它是如何工作的! :-)
  • 另请注意,您不应模糊 Alpha 通道本身。有点明显,但很容易犯错误。
  • 到目前为止,它适用于 1 次模糊(模糊是彩色的,不是白色/黑色/DarkBackgroundPixels),但适用于 2 次,例如外部模糊变成白色..但我有一些想法。让我们看看
  • 好的,我知道了!我也在 sfml 论坛上发布了这个问题,有人刚刚告诉我该怎么做。几分钟后我将在这里发布解决方案