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:
It's a three step way.
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:
(threshold = 0.67f)
An now when I 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)
. 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:
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 (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;
}
[I am using Visual Studio 2017 Community] - Thanks for any help!
I posted this question also on en.sfml-dev.org (here) and fallahn showed me the correct approach. Before tackling that, here are the picture results:
Luminescence (threshold = 0.24f):
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 worrying 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;
}