skip to Main Content

I’m using to make a 2D game with sprites and trying to get this effect:

enter image description here

I use Photoshop’s “Motion Blur” in the example. As you can see, the effect is directional.

My game uses paperdolled sprites so it would be much easier to have this as a post effect instead of blurring every single equipment combination on every sprite.

Would it be possible to get this effect with a shader? An example would be appreciated.

2

Answers


  1. Generating points over a line

    A little bit on the manual side but, I can suggest an approach.
    Lets imagine a circle, crossed by a line on a determined angle. Over that line, we place some random points.

    Something like this:

    enter image description here
    Circle with radius 100, angle 45ยบ (PI / 4 radians) and 100 random points

    Then, wherever each one of that points are, we will draw a sprite of our texture, with some alpha (transparency). The result will look like so:

    enter image description here

    Changing the radius of the circle, and the number of points, the result may vary


    Further explanation

    In my example, I use a class, which represents the BlurredSprite. It will hold those points, and the sf::Texture to draw.

    class BlurredSprite : public sf::Drawable, sf::Transformable {
        public:
            BlurredSprite(const sf::Texture &t, float radius, float angle, unsigned int points = 30) :
                m_texture(t)
            {
                auto getPointOverLine = [=]{
                    auto angleOverX = angle - std::_Pi*0.5;  // Angle 0 is a vertical line, so I rotate it 45 degrees clockwise (substract)
                    if (rand() % 2){
                        angleOverX += std::_Pi;   // Half of the points will be on the "first quadrant", the other half over the "third", if you consider (0,0) the center of the circle
                    }
                    auto l = radius * ((rand() * 1.f) / RAND_MAX);
                    auto x = cos(angleOverX) * (l);
                    auto y = sin(angleOverX) * (l);
    
                    return sf::Vector2f(x, y);
                };
    
                while (m_points.size() < points){
                    m_points.push_back(getPointOverLine());
                }
    
            }
        private:
            std::vector<sf::Vector2f> m_points;
            sf::Texture m_texture;
    };
    

    With getPointOverLine lambda, I create a random point over the line, but can be other options to do this. In fact, the way you spread those points will affect on the final result.

    I need to override the draw method, in order to make this class Drawable on the window. I also override the setPosition method (from sf::Transformable) because, if you move it, you should move all the points with it.

    public:
        virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const{
            auto it = m_points.begin();
            while (it != m_points.end()){    
                sf::Sprite sp(m_texture);
                auto col = sp.getColor();
                auto rect = sp.getTextureRect();
                auto orig = sp.getOrigin() + sf::Vector2f(rect.width, rect.height)*0.5f;
                col.a = 25;
    
                sp.setColor(col);
                sp.setOrigin(orig);
                sp.setPosition(*it);
                target.draw(sp);
    
                it++;
            }
        }
    
        virtual void setPosition(sf::Vector2f pos){
            sf::Transformable::setPosition(pos);
            for (int i = 0; i < m_points.size(); ++i){
                m_points[i] = pos + m_points[i];
            }
        }
    
    Login or Signup to reply.
  2. Here I post the best result I could do in one night. I did it with shaders as you ask. I didn’t implement the angles but that something not too hard.

    Here is part of the cpp file:

    ...
    if (!shader.loadFromFile("dMotionBlur_v05.frag", sf::Shader::Fragment)){
    ...
    
    window.clear(sf::Color(120,120,120));
    
    // Passing parameters to shader.
    shader.setUniform("dir", dir); //direction of blur
    shader.setUniform("nSamplesF", (float)std::atoi(argv[3])); // number of samples
    shader.setUniform("radius", (float)std::atof(argv[4]) ); //radius of blur
    
    window.draw(sprite, &shader);
    window.display();
    ...
    

    This is the fragment shader:

    uniform sampler2D u_texture;
    uniform float nSamplesF;
    uniform float radius;
    uniform vec2 dir;
    
    void main(){
        vec2 tc = gl_TexCoord[0].xy;
        float blur = radius;
        float hstep = dir.x;
        float vstep = dir.y;
        float total = 0.0;
        int nSamplesI = int(nSamplesF);
    
        vec4 sum = vec4(0.0);
    
        for (int i=1; i<=nSamplesI; i++){
            float floatI = float(i);
            float counter = nSamplesF-floatI+1.0;
    
            float p = floatI/nSamplesF;
            float tO = (p * 0.1783783784) + 0.0162162162;
            total += tO;
    
            sum += texture2D(u_texture, vec2(tc.x - counter*blur*hstep, tc.y - counter*blur*vstep)) * tO;
        }
    
        sum += texture2D(u_texture, vec2(tc.x, tc.y)) * 0.2270270270;
    
        for (int i=nSamplesI; i>=1; i--){
            float floatI = float(i);
            float counter = nSamplesF-floatI+1.0;
    
            float p = floatI/nSamplesF;
            float tO = (p * 0.1783783784) + 0.0162162162;
            total += tO;
    
            sum += texture2D(u_texture, vec2(tc.x + counter*blur*hstep, tc.y + counter*blur*vstep)) * tO;
        }
    
        gl_FragColor =  gl_Color * (sum/total);
    }
    

    I upload the entire code to my repository so you can downloaded and try.
    You can set x/Y direction, sample, and blur radius.
    X/Y directions are from 0-1.
    You can play with the number of samples.
    The blur radius is very sensitive you can start try with 0.01

    with my example would be:

    $ ./sfml-app [x(0-1)] [Y(0-1] [number of sample] [ radius blur]
    

    Some pics:


    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search