Have been looking at this a
bit lot more, and I've got something working. I would like to ask for your comments, suggestions and/or improvements.
The fragment shader is now as follows:
uniform sampler2D texture;
uniform vec2 pixel_size;
uniform int blur_radius;
uniform float[128] weights;
uniform vec2 direction;
void main ( ) {
const vec2 texture_coordinates = gl_TexCoord [ 0 ].xy;
gl_FragColor = texture2D ( texture, texture_coordinates ) * weights [ 0 ];
for ( int i = 1; i < blur_radius; ++i ) {
const vec2 offset = vec2 ( float ( i ) * pixel_size.x * direction.x, float ( i ) * pixel_size.y * direction.y );
gl_FragColor += texture2D ( texture, texture_coordinates + offset ) * weights [ i ];
gl_FragColor += texture2D ( texture, texture_coordinates - offset ) * weights [ i ];
}
}
And the ShadedText class (and a main) is like this:
class ShadedText {
sf::Text m_text;
sf::RenderTexture m_text_render_texture;
sf::Sprite m_text_sprite;
const std::vector<float> m_gaussian_weights;
sf::RenderStates m_render_state;
sf::Shader m_shader;
sf::RenderTexture m_horz_render_texture;
sf::RenderTexture m_vert_render_texture;
sf::Sprite m_horz_sprite;
sf::Sprite m_final_sprite;
std::vector<float> gaussianWeights ( const std::size_t size_ ) const {
// http://www.cocos2d-x.org/wiki/User_Tutorial-RenderTexture_Plus_Blur
std::vector<float> gaussian_weights ( size_ );
double nill = 1.0;
for ( std::size_t i = 1; i < size_; ++i ) {
const double x = ( double ) i / ( double ) ( size_ - 1 ), gwi = 1.196826841204297942 / ( double ) size_ * std::exp ( -x * x * 4.5 );
gaussian_weights [ i ] = gwi;
nill -= 2.0 * gwi;
}
gaussian_weights [ 0 ] = nill;
return gaussian_weights;
}
public:
ShadedText ( const std::wstring &text_, const sf::Font &font_, const std::size_t points_, const std::size_t radius_, const sf::Vector2f &position_ = sf::Vector2f ( 0.0f, 0.0f ) ) :
m_text ( text_, font_, points_ ),
m_gaussian_weights ( gaussianWeights ( radius_ ) ) {
const float dispx = 12.0f, dispy = 13.0f;
// Finish setting text object...
m_text.setFillColor ( sf::Color::Black );
m_text.setOrigin ( m_text.getLocalBounds ( ).left - radius_, m_text.getLocalBounds ( ).top - radius_ ); // Otherwise text is not properly positioned in the rendertexture
// Init render texture...
m_text_render_texture.create ( m_text.getLocalBounds ( ).width + 2.0f * radius_ + dispx, m_text.getLocalBounds ( ).height + 2.0f * radius_ + dispx );
m_text_render_texture.clear ( sf::Color::White );
m_text_render_texture.setSmooth ( true );
m_text.setPosition ( dispx, dispy );
m_text_render_texture.draw ( m_text );
m_text_render_texture.display ( );
// 1. Create text sprite...
m_text_sprite.setTexture ( m_text_render_texture.getTexture ( ) );
// Load shader...
if ( not ( m_shader.loadFromFile ( "c:/tmp/resources/blur.frag", sf::Shader::Fragment ) ) ) {
exit ( EXIT_FAILURE );
}
// Set shader parameters...
m_shader.setUniform ( "pixel_size", sf::Vector2f ( 2.0f / m_text_sprite.getLocalBounds ( ).width, 2.0f / m_text_sprite.getLocalBounds ( ).height ) );
m_shader.setUniform ( "blur_radius", ( int ) radius_ );
m_shader.setUniformArray ( "weights", m_gaussian_weights.data ( ), radius_ );
m_render_state.shader = &m_shader;
// 2. Draw it to a RenderTexture with horizontal blur shader...
m_shader.setUniform ( "direction", sf::Vector2f ( 1.0f, 0.0f ) );
m_horz_render_texture.create ( m_text_sprite.getLocalBounds ( ).width + 2.0f * radius_, m_text_sprite.getLocalBounds ( ).height + 2.0f * radius_ );
m_horz_render_texture.clear ( sf::Color::White );
m_horz_render_texture.setSmooth ( true );
m_horz_render_texture.draw ( m_text_sprite, m_render_state );
m_horz_render_texture.display ( );
// 3. Create a sprite from resulting texture...
m_horz_sprite.setTexture ( m_horz_render_texture.getTexture ( ) );
// 4. Draw this sprite to a Render texture with vertical shader...
m_shader.setUniform ( "direction", sf::Vector2f ( 0.0f, 1.0f ) );
m_vert_render_texture.create ( m_horz_sprite.getLocalBounds ( ).width, m_horz_sprite.getLocalBounds ( ).height );
m_vert_render_texture.clear ( sf::Color::White );
m_vert_render_texture.setSmooth ( true );
m_vert_render_texture.draw ( m_horz_sprite, m_render_state );
m_text.setFillColor ( sf::Color::Green );
m_text.setOutlineColor ( sf::Color::Black );
m_text.setOutlineThickness ( 1.0f );
m_text.setPosition ( 0.0f, 0.0f );
m_vert_render_texture.draw ( m_text );
m_vert_render_texture.display ( );
m_final_sprite.setTexture ( m_vert_render_texture.getTexture ( ) );
m_final_sprite.setPosition ( 50.0f, 50.0f );
}
void draw ( sf::RenderWindow &window_ ) {
window_.draw ( m_final_sprite );
}
};
int main ( ) {
sf::ContextSettings settings;
settings.antialiasingLevel = 8;
sf::RenderWindow window ( sf::VideoMode ( 500, 300 ), "SFML Shaded Text", sf::Style::Titlebar | sf::Style::Close, settings );
window.setVerticalSyncEnabled ( true );
window.setFramerateLimit ( 60 );
sf::Font font;
if ( not ( font.loadFromFile ( "c:/windows/fonts/impact.ttf" ) ) ) {
return EXIT_FAILURE;
}
ShadedText text ( L"Text", font, 224u, 8u, sf::Vector2f ( window.getSize ( ).x * 0.5f, window.getSize ( ).y * 0.5f ) );
while ( window.isOpen ( ) ) {
sf::Event event;
while ( window.pollEvent ( event ) ) {
if ( event.type == sf::Event::Closed ) {
window.close ( );
}
}
window.clear ( sf::Color::White );
text.draw ( window );
window.display ( );
}
return 0;
}
The image of the resulting text is attached below. The result is reasonably good I would say (but could be improved IMO)...
The constructor runs in about
90ms 80ms on my laptop. It seems long to me... In a real-time situation it might be better, but improvements (or ideas) are welcomed.
[EDIT1] I wonder whether writing the m_text_sprite to an sf::Image, apply the blur on cpu and writing it back to the sf:Image wouln't be much faster (as opposed to creating all those sf::RenderTargets adn other boilerplate). Writing the m_text to sf::Image takes about 1-2ms, so there's time left!
[EDIT2] Eliminated 1 RenderTexture by re-use... The constructor now runs in about 80ms.
[EDIT3] About a quarter of the time is spent in this call:
m_text.setOrigin ( m_text.getLocalBounds ( ).left - radius_, m_text.getLocalBounds ( ).top - radius_ );
That's expensive!
[EDIT4] updated the latest result... looks good now.