Hi,
I'm trying to create a glow effect with shaders, and for that I blur an image and I draw a copy of that image above. If I clear the renderTextures with any color, everything works, but when I use renderTexture.clear( sf::Color::Transparent ), my shader reacts like if the background was black, so there is a black thing around my blur.
Generally, the transparent pixels are considered as black transparent ones, so in the beginning, I thought that my shader was not aware of the alpha of the pixels (which was true ^^), but now I have no idea of why it doesn't work with transparent renderTextures.
In a nutshell: why there is a black thing around my blurs when I clear my renderTextures with transparent colors, and how to make it works? I think the problem is not in my shader, but I'm not 100% sure
main.cpp:
#include <SFML/Graphics.hpp>
int main(){
// Main window, where I draw the render of the glowing and non-glowing objects together
sf::RenderWindow mainWindow( sf::VideoMode( 512, 512 ), "SFML 2" );
mainWindow.setFramerateLimit( 60 );
mainWindow.setVerticalSyncEnabled( true );
// Render for the non-glowing objects
sf::RenderTexture renderNormal; renderNormal.create( 512, 512 );
// Render for the glowing objects
sf::RenderTexture renderBlur; renderBlur.create( 512, 512 );
// Render of the blur on the x axis
sf::RenderTexture renderBlurX; renderBlurX.create( 512, 512 );
// Render of the blur on the y axis, after the blur on the x axis
sf::RenderTexture renderBlurY; renderBlurY.create( 512, 512 );
// Texture 512*256 with the text "to blur"
sf::Texture textureToBlur; textureToBlur.loadFromFile( "data/1.png" );
// Texture 512*256 with the text "to not"
sf::Texture textureToNot; textureToNot.loadFromFile( "data/2.png" );
// Sprite with the text "to blur":
sf::Sprite spriteBlurObject( textureToBlur );
// Sprite with the text "to not":
sf::Sprite spriteNormalObject( textureToNot );
// Same sprite as 'spriteBlurObject', but this one will be blured and put behind 'spriteBlurObject':
sf::Sprite spriteBlurObjectback( textureToBlur );
// Sprite where I render the non-glowing objects
sf::Sprite spriteRenderNormal( renderNormal.getTexture());
// Sprite where I render the glowing objects
sf::Sprite spriteRenderGlow( renderBlur.getTexture());
// Sprite for the blur on the x axis
sf::Sprite spriteRenderBlurX( renderBlurX.getTexture());
// Sprite for the blur on the y axis, after the blur on the x axis
sf::Sprite spriteRenderBlurY( renderBlurY.getTexture());
// The text 'to not' must not overlap with the text 'to glow'
spriteNormalObject.setPosition( 0, 256 );
// Shader which transform a texture, to create a blur on the x axis
sf::Shader shaderBlurX;
shaderBlurX.loadFromFile( "blur.frag", sf::Shader::Fragment );
shaderBlurX.setUniform( "texture", textureToBlur );
shaderBlurX.setUniform( "invertTextureSize", 1.0f/textureToBlur.getSize().x );
shaderBlurX.setUniform( "blurSize", 7 );
shaderBlurX.setUniform( "direction", sf::Vector2f( 1, 0 ));
// Same as 'shaderBlurX', but for the y axis
sf::Shader shaderBlurY;
shaderBlurY.loadFromFile( "blur.frag", sf::Shader::Fragment );
shaderBlurY.setUniform( "texture", renderBlurX.getTexture());
shaderBlurY.setUniform( "invertTextureSize", 1.0f/renderBlur.getTexture().getSize().y );
shaderBlurY.setUniform( "blurSize", 7 );
shaderBlurY.setUniform( "direction", sf::Vector2f( 0, 1 ));
// A simple shader to enhance alpha, it is not necessary
sf::Shader shaderAlpha;
shaderAlpha.loadFromFile( "prog/alpha.frag", sf::Shader::Fragment );
shaderAlpha.setUniform( "texture", renderBlurY.getTexture());
while( mainWindow.isOpen()){
sf::Event event;
while( mainWindow.pollEvent( event )){
switch( event.type ){
case sf::Event::Closed:
mainWindow.close();
break;
default:
break;
}
}
// Create the blur on the x axis
//renderBlurX.clear( sf::Color::Transparent ); // With this clear, there is a black thing around the blur
renderBlurX.clear( sf::Color::White ); // With this clear, everything works but there is no transparency
renderBlurX.draw( spriteBlurObjectback, &shaderBlurX );
renderBlurX.display();
// Create the blur on the y axis, after the blur on the x axis
renderBlurY.clear( sf::Color::Transparent );
renderBlurY.draw( spriteRenderBlurX, &shaderBlurY );
renderBlurY.display();
// Draw the glowing objects
renderBlur.clear( sf::Color::Transparent );
renderBlur.draw( spriteRenderBlurY, &shaderAlpha ); // Glowing (which is just a blur)/
renderBlur.draw( spriteBlurObject ); // Object himself
renderBlur.display();
// Draw the non-glowing objects
renderNormal.clear( sf::Color::Transparent );
renderNormal.draw( spriteNormalObject );
renderNormal.display();
// Draw the glowing and non-glowing objects
mainWindow.clear( sf::Color::White );
mainWindow.draw( spriteRenderNormal );
mainWindow.draw( spriteRenderGlow );
mainWindow.display();
}
}
blur.frag:
#version 330
uniform sampler2D texture; // Texture which will be modified
uniform float invertTextureSize; // Invert of the size of the texture, on the axis we working on
uniform int blurSize; // Size of the blur
uniform vec2 direction; // Direction of the blur (only (0;1) or (1;0))
void main(){
vec4 color = vec4( 0 );
vec4 tmpColor;
float sumColors = 0;
float sumAlphas = 0;
for( int i = -blurSize; i <= blurSize; ++i ){
tmpColor = texture2D( texture, vec2(
gl_TexCoord[0].s + ( direction.x*i*invertTextureSize ),
gl_TexCoord[0].t + ( direction.y*i*invertTextureSize )
));
color.rgb += tmpColor.rgb * tmpColor.a;
color.a += tmpColor.a;
sumColors += tmpColor.a;
sumAlphas += 1;
}
color.rgb /= sumColors;
color.a /= sumAlphas;
gl_FragColor = vec4( color.r, color.g, color.b, color.a );
}
alpha.frag:
#version 330
uniform sampler2D texture;
void main(){
vec4 color = texture2D( texture, gl_TexCoord[0].st );
color.w *= 2.5;
gl_FragColor = color;
}
sf::RenderStates states;
states.shader = &your_shader;
states.blendMode = sf::BlendMode(sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha);
window.draw(sprite, states)
The last line in your shader is sometmes needed, sometimes not, depending on what you draw on the rendertexture. If you see a white border around your objects you need to premultiply alpha to colour
I'm sorry to tell you that I already tried to simplify my code, I tried with only the blur on the x axis (not even the texture above the blur, just the blur himself) and I tried to add a condition in my shader to take only the non-transparent pixels, nothing worked, but I discovered something weird:
I cleared the renderTextures with transparent colors (like sf::Color(0,255,0,0) for example) and I've noticed that the color around the blur is not the color of the renderTexture where I apply the shader, but the color of the renderTexture where I draw the sprite of the previous renderTexture. For example, with the following code:
// Blend mode + shader
sf::RenderStates stateBlurX;
stateBlurX.blendMode = sf::BlendMode( sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha );
stateBlurX.shader = &shaderBlurX;
// Draw the blur on the x axis
renderBlurX.clear( sf::Color( 255, 0, 0, 0 )); // This color is transparent
renderBlurX.draw( spriteBlurObjectback, stateBlurX );
renderBlurX.display();
// Drawing on the renderTexture for the glowing objects
renderBlur.clear( sf::Color( 0, 0, 255, 0 )); // This color is around the blur
renderBlur.draw( spriteRenderBlurX );
renderBlur.display();
// Draw everything in the main window
mainWindow.clear( sf::Color::White );
mainWindow.draw( spriteRenderGlow );
mainWindow.display();
... there will be a blue thing around the blur, and honestly, I don't know why it does that, and I know even less how to fix that
When you use BlendNone better check if you do not have any undesired results when textures are overlapping.
Anyway, here's my skimmed version (alpha safe)
#include <SFML/Graphics.hpp>
int main() {
// Main window, where I draw the render of the glowing and non-glowing objects together
sf::RenderWindow mainWindow( sf::VideoMode( 512, 512 ), "SFML 2" );
//it's not generally advised to use both frame limit and vsync
//mainWindow.setFramerateLimit( 60 );
mainWindow.setVerticalSyncEnabled( true );
// Render for the non-glowing objects
sf::RenderTexture renderNormal; renderNormal.create( 512, 512 );
// Render of the blur on the x axis
sf::RenderTexture renderBlurX; renderBlurX.create( 512, 512 );
// Render of the blur on the y axis, after the blur on the x axis
sf::RenderTexture renderBlurY; renderBlurY.create( 512, 512 );
// Texture 512*256 with the text "to blur"
sf::Texture texture; texture.loadFromFile( "Blur/test.png" );
// Sprite from texture
sf::Sprite sprite( texture );
// Sprite where I render the non-glowing objects
sf::Sprite spriteRenderNormal( renderNormal.getTexture() );
// Sprite for the blur on the x axis
sf::Sprite spriteRenderBlurX( renderBlurX.getTexture() );
// Sprite for the blur on the y axis, after the blur on the x axis
sf::Sprite spriteRenderBlurY( renderBlurY.getTexture() );
// Shader which transform a texture, to create a blur
sf::Shader shaderBlur;
shaderBlur.loadFromFile( "Blur/blur.frag", sf::Shader::Fragment );
shaderBlur.setUniform( "invertTextureSize", 1.0f / renderBlurX.getSize().x );
shaderBlur.setUniform( "blurSize", 7 );
//shaderBlurX.setUniform( "texture", textureToBlur ); //not needed if you use just one texture
sf::BlendMode blendPremultiplied = sf::BlendMode( sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha );
sf::RenderStates statesBlur;
statesBlur.blendMode = blendPremultiplied;
statesBlur.shader = &shaderBlur;
sf::RenderStates statesPremultiplied;
statesPremultiplied.blendMode = blendPremultiplied;
while ( mainWindow.isOpen() ) {
sf::Event event;
while ( mainWindow.pollEvent( event ) ) {
switch ( event.type ) {
case sf::Event::Closed:
mainWindow.close();
break;
default:
break;
}
}
// Create the blur on the x axis
renderBlurX.clear( sf::Color::Transparent );
shaderBlur.setUniform( "direction", sf::Vector2f( 1, 0 ) );
renderBlurX.draw( sprite, statesBlur );
renderBlurX.display();
// Create the blur on the y axis, after the blur on the x axis
renderBlurY.clear( sf::Color::Transparent );
shaderBlur.setUniform( "direction", sf::Vector2f( 0, 1 ) );
renderBlurY.draw( spriteRenderBlurX, statesBlur );
renderBlurY.display();
// Draw the non-glowing objects on a rendertexture with default blending
// I'm not sure why you used it here, but it's useful if you want to group objects
// and scale them or add some effects to them all at once
renderNormal.clear( sf::Color::Transparent );
sprite.setPosition( 0, 256 );
renderNormal.draw( sprite );
renderNormal.display();
// mainWindow draws
mainWindow.clear( sf::Color( 100, 100, 150, 255 ));
mainWindow.draw( spriteRenderBlurY ); // Glowing (which is just a blur)/
sprite.setPosition( 0, 0 );
mainWindow.draw( sprite ); // Object himself
//if you draw a rendertexture with objects on it you always do it with that special blending mode
mainWindow.draw( spriteRenderNormal, statesPremultiplied );
mainWindow.display();
}
}