Создание эффекта свечения - проблемы со значениями альфа - PullRequest
0 голосов
/ 15 января 2019

Я хочу создать эффект свечения для своей игры. Чтобы сохранить этот минимализм, скажем, я хочу светить изображение. :)

Начиная с этого:

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 конечного цвета пикселя в фрагментном шейдере на его альфа-значение. Так я и сделал, и вот мой результат:

blurred, but overall too dark, especially the edges

Это лучше, но определенно не хорошо. Шейдер размытия теперь также удаляет окружающие пиксели люминесцентной маски. После сборки это просто размытое изображение:

Blurry Picture, not even close to resemble a glow effect .. Я попытался «исправить» это в файлах шейдеров, проверив, равен ли альфа пикселя нулю. Таким образом, я не ценю их, когда ухожу. Но так как sf :: BlendMode активирован, я не знаю, как сейчас себя ведет альфа - поэтому я деактивировал blendmode, но у меня все еще странные результаты. (в самом этом вопросе я предоставил код и результат этой попытки)


ни одна из моих попыток исправить эту работу. Я действительно мог бы использовать некоторую помощь здесь. Может быть, я делаю что-то в корне неправильно в шейдерах .. вот полный код - Если вы хотите скомпилировать его, создайте ресурсы папки с помощью двух шейдеров Fragment и background.jpg (в 1280x720).

* +1062 * 1064 * 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; } Это boxblur.frag, где я пытался исключить нулевые альфа-значения: (я удалил shader_states.blendMode = sf::BlendMode(sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha); в строке 29 в main.cpp, конечно): #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] - Спасибо за любую помощь!

Ответы [ 2 ]

0 голосов
/ 16 января 2019

Я разместил этот вопрос также на en.sfml-dev.org ( здесь ), а 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), что не приводит к изменениям!

Это замечательно, потому что теперь вы можете полностью игнорировать альфа-канал.

Чтобы понять, почему я нахожу это так здорово, вы можете прочитать эту маленькую напыщенную речь здесь (не стесняйтесь пропустить ее):

Не беспокоясь об альфе, вы можете игнорировать любые sf::BlendMode, такие как запутанные sf::BlendMode::OneMinusSrcAlpha, которые вызывали у меня головную боль в течение 2 дней. Попробуйте рассчитать любое разумное «истинное» альфа-значение, если вы знаете, что все они предварительно умножены. Конечно, вам также нужно умножить все значения rgb на альфа пикселя, чтобы обратить обратное умножение… отсюда формулы быстро растут. Также вычтите 1 из альфы, потому что это OneMinusSrcAlpha ... и не забудьте проверить случаи, когда сумма всех альф (да, вам нужно сложить это) равна 0 (или в OneMinusSrcAlpha имеет значение, что-то еще) потому что в противном случае вы получите деление на 0 (или в OneMinusSrcAlpha имеет значение деление на 0, когда все окружающие пиксели сплошные). Также иногда могут работать странные альфа-значения, но только для одного прохода размытия, но в моем случае у меня есть несколько проходов ... и т. Д.

Вот окончательный код:

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;
}
0 голосов
/ 16 января 2019

Попробуйте сделать крошечный пример, где вы просто хотите усреднить ДВА пикселя. Слева (L) и справа (R). Тогда левый пиксель состоит из R (L), G (L), B (L), A (L), а правый пиксель состоит из R (R), G (R), B (R) и A (R).

Без альфы усреднение синего было бы просто:

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

Принимая во внимание альфа, она становится:

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

Мы можем непосредственно видеть, что в случае полностью сплошных пикселей (альфа = 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)

Оба пикселя, будучи полностью прозрачными, становятся вырожденным случаем, с которым нужно обращаться элегантным образом.

Теперь все, что вам нужно сделать, это расширить это за пределы двух пикселей. : -)

...