glsl - 制作发光效果 - alpha 值问题

标签 glsl sfml blur

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

从这个开始:

default picture, no effect

要得到这样的东西:desired output, picture with glow effect

这是一个三步走的方法。

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

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

    在我进一步解释之前,这是我的发光结果:the picture, but all darker pixels are transparent
    (阈值 = 0.67f)

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

    blurred, but overall too dark, especially the edges

    它更好,但绝对不是很好。模糊着色器现在还会平均处理发光蒙版的周围像素。组装后就是一张模糊的图:

    Blurry Picture, not even close to resemble a glow effect .. 我尝试通过检查像素的 alpha 是否为零来“修复”着色器文件中的这个问题。这样我在平均分配时就不会重视它们。但是由于 sf::BlendMode 被激活,我不知道 alpha 现在的行为 - 所以我停用了 blendmode 但我仍然有奇怪的结果。 (在这个问题的核心我提供了代码和这次尝试的结果)

    我没有尝试修复这项工作。我真的可以在这里使用一些帮助。也许我在着色器中做了一些根本性的错误......这是完整的代码 -
    如果要编译它,请使用 2 个 Fragment 着色器和 background.jpg 创建一个文件夹资源。 (1280x720)。

    发光片段
    #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;
    }
    

    主程序
    #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;
    }
    

    导致:
    mostly bright values. but too blocky and not smooth at all

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

    最佳答案

    我也在 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 channel 。

    要理解为什么我觉得这很棒,你可以在这里阅读这个小小的咆哮(随意跳过它):

    无需担心 alpha,您可以忽略任何 sf::BlendMode像令人困惑的sf::BlendMode::OneMinusSrcAlpha这让我头痛了整整两天。当您知道它们都被预乘时,尝试计算任何合理的“真实”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);
    }
    

    乘法.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;
    }
    

    关于glsl - 制作发光效果 - alpha 值问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54204592/

    相关文章:

    opengl - 确保所有矢量分量都大于某个数字

    c++ - 无法编译顶点着色器 : Unexpected $end at token "<EOF>"

    glsl - GLSL 中两个整数的比较

    c++ - SFML + OpenGL : Unable to draw cube

    c++ - 为什么不包含文件

    opencv - 使用自定义内核的OpenCV散焦模糊?

    android - 是否有可能在 opengl es 2.0 中获取存储在纹理中的数据

    c++ - 分析函数内存和 CPU 使用率

    swift - vImageBoxConvolve_ARGB8888 背景颜色参数

    c# - 高斯模糊导致图像周围出现白框