With Gaussian blur out of the way, let’s look at motion blur.

What is motion blur? Motion blur or motion blur is a static scene or a series of images such as a movie or an animation in which fast-moving objects create noticeable blurred drag marks.

Why does motion blur appear? The camera works by exposing a scene on film in a short amount of time. Light from the scene is projected onto the film, causing a chemical reaction that eventually produces the picture. That’s exposure. If the scene changes during exposure, it will produce a blurry image.


Question 1: Is motion blur a Gaussian blur in a single direction?

According to the physical imaging principle of motion blur, we can know that the closer the image is to shutter closing, the clearer the image is. The opacity of the residual image changes, which is also affected by speed:

Let’s try to simulate motion blur by gaussian blur in a single direction and see what happens:

// Show only the core code
vec4 blur13(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {
  vec4 color = vec4(0.0);
  vec2 off1 = vec2(1.411764705882353) * direction;
  vec2 off2 = vec2(3.2941176470588234) * direction;
  vec2 off3 = vec2(5.176470588235294) * direction;
  color += texture2D(image, uv) * 0.1964825501511404;
  color += texture2D(image, uv + (off1 / resolution)) * 0.2969069646728344;
  color += texture2D(image, uv - (off1 / resolution)) * 0.2969069646728344;
  color += texture2D(image, uv + (off2 / resolution)) * 0.09447039785044732;
  color += texture2D(image, uv - (off2 / resolution)) * 0.09447039785044732;
  color += texture2D(image, uv + (off3 / resolution)) * 0.010381362401148057;
  color += texture2D(image, uv - (off3 / resolution)) * 0.010381362401148057;
  return color;
}

void main() {
    gl_FragColor = blur13(texture, st, iResolution, vec2(0..5.));
}
Copy the code

Although gaussian blur in a single direction does not completely meet the definition of motion blur. But simply looking at the effect is not very distinguishable, let’s strengthen the effect:

Question two: how to make the picture move naturally?

Motion blur, naturally need motion to be reflected. First we implement a simple shift:

// Show only the core code
vec4 blur13(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {
  vec4 color = vec4(0.0);
  vec2 off1 = vec2(1.411764705882353) * direction;
  vec2 off2 = vec2(3.2941176470588234) * direction;
  vec2 off3 = vec2(5.176470588235294) * direction;
  color += texture2D(image, uv) * 0.1964825501511404;
  color += texture2D(image, uv + (off1 / resolution)) * 0.2969069646728344;
  color += texture2D(image, uv - (off1 / resolution)) * 0.2969069646728344;
  color += texture2D(image, uv + (off2 / resolution)) * 0.09447039785044732;
  color += texture2D(image, uv - (off2 / resolution)) * 0.09447039785044732;
  color += texture2D(image, uv + (off3 / resolution)) * 0.010381362401148057;
  color += texture2D(image, uv - (off3 / resolution)) * 0.010381362401148057;
  return color;
}

void main() {
    st += time*3.;         // time: 0~1
    st = fract(st);
    gl_FragColor = blur13(texture, st, iResolution, vec2(0..20.));
}
Copy the code

OK, it’s a little hot in the eye. The first problem to be solved is that the image boundary is connected to the place without motion blur:

This means that we need to take the current coordinates in real time to sample the image, so we pass in a new parameter that represents the current movement distance:

vec4 blur13(sampler2D image, vec2 uv, vec2 resolution, vec2 direction, vec2 speed) {
  vec4 color = vec4(0.0);
  vec2 off1 = vec2(1.411764705882353) * direction;
  vec2 off2 = vec2(3.2941176470588234) * direction;
  vec2 off3 = vec2(5.176470588235294) * direction;
  color += texture2D(image, fract(uv + speed)) * 0.1964825501511404;
  color += texture2D(image, fract(uv + (off1 / resolution) + speed)) * 0.2969069646728344;
  color += texture2D(image, fract(uv - (off1 / resolution) + speed)) * 0.2969069646728344;
  color += texture2D(image, fract(uv + (off2 / resolution) + speed)) * 0.09447039785044732;
  color += texture2D(image, fract(uv - (off2 / resolution) + speed)) * 0.09447039785044732;
  color += texture2D(image, fract(uv + (off3 / resolution) + speed)) * 0.010381362401148057;
  color += texture2D(image, fract(uv - (off3 / resolution) + speed)) * 0.010381362401148057;
  return color;
}

void main() {
    vec2 speed = vec2(0, time*3.);

    gl_FragColor = blur13(inputImageTexture, myst, iResolution, vec2(0..20.), speed);
}
Copy the code

Then after solving the boundary problem, we will analyze the timing of motion blur. There will be no blur at the beginning and end of motion, and only blur in the middle process, so we adjust the blur according to the time:

We first construct a change curve of its value from 0~1~0 in unit time from 0~1 as our fuzzy multiplier. Through this tool, we make a little transformation of the previous normal distribution probability density function:

vec4 blur13(sampler2D image, vec2 uv, vec2 resolution, vec2 direction, vec2 speed) {
  vec4 color = vec4(0.0);
  vec2 off1 = vec2(1.411764705882353) * direction;
  vec2 off2 = vec2(3.2941176470588234) * direction;
  vec2 off3 = vec2(5.176470588235294) * direction;
  color += texture2D(image, fract(uv + speed)) * 0.1964825501511404;
  color += texture2D(image, fract(uv + (off1 / resolution) + speed)) * 0.2969069646728344;
  color += texture2D(image, fract(uv - (off1 / resolution) + speed)) * 0.2969069646728344;
  color += texture2D(image, fract(uv + (off2 / resolution) + speed)) * 0.09447039785044732;
  color += texture2D(image, fract(uv - (off2 / resolution) + speed)) * 0.09447039785044732;
  color += texture2D(image, fract(uv + (off3 / resolution) + speed)) * 0.010381362401148057;
  color += texture2D(image, fract(uv - (off3 / resolution) + speed)) * 0.010381362401148057;
  return color;
}

float normpdf(float x) {
    return exp(- 20.*pow(x-. 5.2.));
}

void main() {
    vec2 speed = vec2(0, time);
    float blur = normpdf(time);
    
    gl_FragColor = blur13(inputImageTexture, myst, iResolution, vec2(0..20.*blur), speed);
}
Copy the code

Get a feel for it, then increase the ease of time, obviously we want the easeInOut curve:

float A(float aA1, float aA2) {
    return 1.0 - 3.0 * aA2 + 3.0 * aA1;
}

float B(float aA1, float aA2) {
    return 3.0 * aA2 - 6.0 * aA1;
}

float C(float aA1) {
    return 3.0 * aA1;
}

float GetSlope(float aT, float aA1, float aA2) {
    return 3.0 * A(aA1, aA2)*aT*aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
}

float CalcBezier(float aT, float aA1, float aA2) {
    return ((A(aA1, aA2)*aT + B(aA1, aA2))*aT + C(aA1))*aT;
}

float GetTForX(float aX, float mX1, float mX2) {
    float aGuessT = aX;
    for (int i = 0; i < 4; ++i) {
        float currentSlope = GetSlope(aGuessT, mX1, mX2);
        if (currentSlope == 0.0) return aGuessT;
        float currentX = CalcBezier(aGuessT, mX1, mX2) - aX;
        aGuessT -= currentX / currentSlope;
    }
    return aGuessT;
}

/ * * @ param aX: incoming time variable * @ param mX1 / mY1 / mX2 / mY2: bezier four values * description: this function is more than the other function is the function of using the auxiliary function of * /
float KeySpline(float aX, float mX1, float mY1, float mX2, float mY2) {
    if (mX1 == mY1 && mX2 == mY2) return aX; // linear
    return CalcBezier(GetTForX(aX, mX1, mX2), mY1, mY2);
}

vec4 blur13(sampler2D image, vec2 uv, vec2 resolution, vec2 direction, vec2 speed) {
  vec4 color = vec4(0.0);
  vec2 off1 = vec2(1.411764705882353) * direction;
  vec2 off2 = vec2(3.2941176470588234) * direction;
  vec2 off3 = vec2(5.176470588235294) * direction;
  color += texture2D(image, fract(uv + speed)) * 0.1964825501511404;
  color += texture2D(image, fract(uv + (off1 / resolution) + speed)) * 0.2969069646728344;
  color += texture2D(image, fract(uv - (off1 / resolution) + speed)) * 0.2969069646728344;
  color += texture2D(image, fract(uv + (off2 / resolution) + speed)) * 0.09447039785044732;
  color += texture2D(image, fract(uv - (off2 / resolution) + speed)) * 0.09447039785044732;
  color += texture2D(image, fract(uv + (off3 / resolution) + speed)) * 0.010381362401148057;
  color += texture2D(image, fract(uv - (off3 / resolution) + speed)) * 0.010381362401148057;
  return color;
}

float normpdf(float x) {
    return exp(- 20.*pow(x-. 5.2.));
}

void main() {
    float easingTime = KeySpline(time, 65...01.26..99.);

    vec2 speed = vec2(0, easingTime);
    float blur = normpdf(easingTime);
    
    gl_FragColor = blur13(inputImageTexture, myst, iResolution, vec2(0..20.*blur), speed);
}
Copy the code

Finally, add a little bit of vertical deformation to make it stretch:

float A(float aA1, float aA2) {
    return 1.0 - 3.0 * aA2 + 3.0 * aA1;
}

float B(float aA1, float aA2) {
    return 3.0 * aA2 - 6.0 * aA1;
}

float C(float aA1) {
    return 3.0 * aA1;
}

float GetSlope(float aT, float aA1, float aA2) {
    return 3.0 * A(aA1, aA2)*aT*aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
}

float CalcBezier(float aT, float aA1, float aA2) {
    return ((A(aA1, aA2)*aT + B(aA1, aA2))*aT + C(aA1))*aT;
}

float GetTForX(float aX, float mX1, float mX2) {
    float aGuessT = aX;
    for (int i = 0; i < 4; ++i) {
        float currentSlope = GetSlope(aGuessT, mX1, mX2);
        if (currentSlope == 0.0) return aGuessT;
        float currentX = CalcBezier(aGuessT, mX1, mX2) - aX;
        aGuessT -= currentX / currentSlope;
    }
    return aGuessT;
}

/ * * @ param aX: incoming time variable * @ param mX1 / mY1 / mX2 / mY2: bezier four values * description: this function is more than the other function is the function of using the auxiliary function of * /
float KeySpline(float aX, float mX1, float mY1, float mX2, float mY2) {
    if (mX1 == mY1 && mX2 == mY2) return aX; // linear
    return CalcBezier(GetTForX(aX, mX1, mX2), mY1, mY2);
}

vec4 blur13(sampler2D image, vec2 uv, vec2 resolution, vec2 direction, vec2 speed) {
  vec4 color = vec4(0.0);
  vec2 off1 = vec2(1.411764705882353) * direction;
  vec2 off2 = vec2(3.2941176470588234) * direction;
  vec2 off3 = vec2(5.176470588235294) * direction;
  color += texture2D(image, fract(uv + speed)) * 0.1964825501511404;
  color += texture2D(image, fract(uv + (off1 / resolution) + speed)) * 0.2969069646728344;
  color += texture2D(image, fract(uv - (off1 / resolution) + speed)) * 0.2969069646728344;
  color += texture2D(image, fract(uv + (off2 / resolution) + speed)) * 0.09447039785044732;
  color += texture2D(image, fract(uv - (off2 / resolution) + speed)) * 0.09447039785044732;
  color += texture2D(image, fract(uv + (off3 / resolution) + speed)) * 0.010381362401148057;
  color += texture2D(image, fract(uv - (off3 / resolution) + speed)) * 0.010381362401148057;
  return color;
}

vec2 stretchUv(vec2 _st, float t, int direction) {
    vec2 stUse = _st;
    float stretchRatio;

    float currentMaxStretchRatio = 1.0;
    if (t < 0.5)
        currentMaxStretchRatio = 4.*pow(t, StretchSpeedPowValue) * pow(2.0, StretchSpeedPowValue) * (MaxStretchRatio - 1.0) + 1.0;
    else
        currentMaxStretchRatio = 4.*pow((1.0 - t), StretchSpeedPowValue) * pow(2.0, StretchSpeedPowValue) * (MaxStretchRatio - 1.0) + 1.0;

    / / in the left
    if (direction == 1) {
        stretchRatio = (currentMaxStretchRatio - 1.0) * (1.-_st.x) + 1.0;
        stUse.y = (_st.y - 0.5) / stretchRatio + 0.5;
    }
    / / in the right
    else if (direction == 2) {
        stretchRatio = (currentMaxStretchRatio - 1.0) * _st.x+ 1.0;
        stUse.y = (_st.y - 0.5) / stretchRatio + 0.5;
    }
    / / in the
    else if (direction == 3) {
        stretchRatio = (currentMaxStretchRatio - 1.0) * _st.y + 1.0;
        stUse.x = (_st.x - 0.5) / stretchRatio + 0.5;
    }
    / / in the next
    else if (direction == 4) {
        stretchRatio = (currentMaxStretchRatio - 1.0) * (1.-_st.y) + 1.0;
        stUse.x = (_st.x - 0.5) / stretchRatio + 0.5;
    }
    / / vertical
    else if (direction == 5) {
        stretchRatio = (currentMaxStretchRatio - 1.0) * 3. + 1.0;
        stUse.y = (_st.y - 0.5) / stretchRatio + 0.5;
    }
    / / level
    else if (direction == 6) {
        stretchRatio = (currentMaxStretchRatio - 1.0) * . 5 + 1.0;
        stUse.x = (_st.x - 0.5) / stretchRatio + 0.5;
    }

    return stUse; 
}

float normpdf(float x) {
    return exp(- 20.*pow(x-. 5.2.));
}

void main() {
    float easingTime = KeySpline(time, 65...01.26..99.);

    vec2 speed = vec2(0, easingTime);
    float blur = normpdf(easingTime);
    
    // The deformation is still the time variable with uniform speed
    myst = stretchUv(myst, time, 5);
    
    gl_FragColor = blur13(inputImageTexture, myst, iResolution, vec2(0..20.*blur), speed);
}
Copy the code


Problem three: how to solve the motion ambiguity in any direction?

Suppose I want to implement a rotation:

void main() {
    vec2 myst = uv;                                 // for coordinate calculation
    float ratio = iResolution.x / iResolution.y;    // Screen ratio

    float animationTime = getAnimationTime();
    float easingTime = KeySpline(animationTime, 68...01.17..98.);

    float r = 0.;
    float rotation = 180./180.*3.14159;

    if (easingTime <= . 5) {
        r = rotation * easingTime;
    } else {
        r = -rotation + rotation * easingTime;
    }

    myst.y *= 1./ratio;
    myst = rotateUv(myst, r, vec2(1..0.), 1.);
    myst.y *= ratio;

    myst = fract(myst);

    if (easingTime <= . 5) {
        gl_FragColor = texture2D(inputImageTexture, myst);
    } else {
        gl_FragColor = texture2D(inputImageTexture2, myst); }}Copy the code

The direction of motion ambiguity of a rotating motion is not simply horizontal or vertical or incline. Suppose we set a direction roughly and see what happens:

// Modified the blur13 function to remove the speed parameter, since the rotation is already done externally
vec4 blur13(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {
  vec4 color = vec4(0.0);
  vec2 off1 = vec2(1.411764705882353) * direction;
  vec2 off2 = vec2(3.2941176470588234) * direction;
  vec2 off3 = vec2(5.176470588235294) * direction;
  color += texture2D(image, fract(uv)) * 0.1964825501511404;
  color += texture2D(image, fract(uv + (off1 / resolution))) * 0.2969069646728344;
  color += texture2D(image, fract(uv - (off1 / resolution))) * 0.2969069646728344;
  color += texture2D(image, fract(uv + (off2 / resolution))) * 0.09447039785044732;
  color += texture2D(image, fract(uv - (off2 / resolution))) * 0.09447039785044732;
  color += texture2D(image, fract(uv + (off3 / resolution))) * 0.010381362401148057;
  color += texture2D(image, fract(uv - (off3 / resolution))) * 0.010381362401148057;
  return color;
}

void main() {
    vec2 myst = uv;                                 // for coordinate calculation
    float ratio = iResolution.x / iResolution.y;    // Screen ratio

    float animationTime = getAnimationTime();
    float easingTime = KeySpline(animationTime, 68...01.17..98.);

    float blur = normpdf(easingTime);

    float r = 0.;
    float rotation = 180./180.*3.14159;

    if (easingTime <= . 5) {
        r = rotation * easingTime;
    } else {
        r = -rotation + rotation * easingTime;
    }

    myst.y *= 1./ratio;
    myst = rotateUv(myst, r, vec2(1..0.), 1.);
    myst.y *= ratio;

    if (easingTime <= . 5) {
        gl_FragColor = blur13(inputImageTexture, myst, iResolution, vec2(0..50.*blur));
    } else {
        gl_FragColor = blur13(inputImageTexture2, myst, iResolution, vec2(0..50.*blur)); }}Copy the code

This is clearly not the direction we want to go. Another way to think about it is, we can take the coordinates of one frame and subtract the coordinates of the last frame, and the value that we get is our direction of motion.

So here’s what you can do:

void main() {
    vec2 myst = uv;                                 // for coordinate calculation
    float ratio = iResolution.x / iResolution.y;    // Screen ratio

    float animationTime = getAnimationTime();
    float easingTime = KeySpline(animationTime, 68...01.17..98.);

    float blur = normpdf(easingTime);

    float r = 0.;
    float rotation = 180./180.*3.14159;

    if (easingTime <= . 5) {
        r = rotation * easingTime;
    } else {
        r = -rotation + rotation * easingTime;
    }


    // Rotate the current frame
    vec2 mystCurrent = myst;
    mystCurrent.y *= 1./ratio;
    mystCurrent = rotateUv(mystCurrent, r, vec2(1..0.), 1.);
    mystCurrent.y *= ratio;

    // use FPS =60 as the interval
    float timeInterval = 0.00016;

    if (easingTime <= . 5) {
        r = rotation * (easingTime+timeInterval);
    } else {
        r = -rotation + rotation * (easingTime+timeInterval);
    }

    // Next frame is rotated
    vec2 mystNext = myst;
    mystNext.y *= 1./ratio;
    mystNext = rotateUv(mystNext, r, vec2(1..0.), 1.);
    mystNext.y *= ratio;

    // get the unit coordinate direction
    vec2 speed  = (mystNext - mystCurrent / timeInterval);

    if (easingTime <= . 5) {
        gl_FragColor = blur13(inputImageTexture, mystCurrent, iResolution, speed*blur*0.01);
    } else {
        gl_FragColor = blur13(inputImageTexture2, mystCurrent, iResolution, speed*blur*0.01); }}Copy the code

It seems that the traditional single-direction Gaussian blur can not meet the desired effect. Here we quote another function (including random blur effect) :

// Motion blur
vec4 motionBlur(sampler2D texture.vec2 _st, vec2 speed) {
    vec2 texCoord = _st.xy / vec2(1.0).xy;
    vec3 color = vec3(0.0);
    float total = 0.0;
    float offset = rand(_st);
    for (float t = 0.0; t <= 20.0; t++) {
        float percent = (t + offset) / 20.0;
        float weight = 4.0 * (percent - percent * percent);
        color += getColor(texture, texCoord + speed * percent).rgb * weight;
        total += weight;
    }
    return vec4(color / total, 1.0);
}

void main() {
    vec2 myst = uv;                                 // for coordinate calculation
    float ratio = iResolution.x / iResolution.y;    // Screen ratio

    float animationTime = getAnimationTime();
    float easingTime = KeySpline(animationTime, 68...01.17..98.);

    float blur = normpdf(easingTime);

    float r = 0.;
    float rotation = 180./180.*3.14159;

    if (easingTime <= . 5) {
        r = rotation * easingTime;
    } else {
        r = -rotation + rotation * easingTime;
    }

    // Rotate the current frame
    vec2 mystCurrent = myst;
    mystCurrent.y *= 1./ratio;
    mystCurrent = rotateUv(mystCurrent, r, vec2(1..0.), 1.);
    mystCurrent.y *= ratio;


    // use FPS =60 as the interval
    float timeInterval = 0.0167;
    

    if (easingTime <= . 5) {
        r = rotation * (easingTime+timeInterval);
    } else {
        r = -rotation + rotation * (easingTime+timeInterval);
    }


    // Next frame is rotated
    vec2 mystNext = myst;
    mystNext.y *= 1./ratio;
    mystNext = rotateUv(mystNext, r, vec2(1..0.), 1.);
    mystNext.y *= ratio;


    // get the unit coordinate direction
    vec2 speed  = (mystNext - mystCurrent) / timeInterval * blur * 0.5;


    // if (easingTime <= .5) {
    // gl_FragColor = blur13(inputImageTexture, mystCurrent, iResolution, speed*blur*0.01);
    // } else {
    // gl_FragColor = blur13(inputImageTexture2, mystCurrent, iResolution, speed*blur*0.01);
    // }
    if (easingTime <= . 5) {
        gl_FragColor = motionBlur(inputImageTexture, mystCurrent, speed);
    } else {
        gl_FragColor= motionBlur(inputImageTexture2, mystCurrent, speed); }}Copy the code

Obviously, for more complex motion (non-displacement), simple Gaussian blur cannot bring very realistic effect, and random blur, deformation, distortion and other factors are also needed:

Next we’ll add some canvas zooming, bouncing and so on:

void main() {
    vec2 myst = uv;                                 // for coordinate calculation
    float ratio = iResolution.x / iResolution.y;    // Screen ratio

    float animationTime = getAnimationTime();
    float animationTime2 = smoothstep(2..1., animationTime);
    float easingTime = KeySpline(animationTime2, 4..71..26..1.07);
    float easingTime2 = KeySpline(animationTime2, 0..47..99..57.);
    
    float blur = normpdf(easingTime);

    float r = 0.;
    float rotation = 180./180.*3.14159;

    if (easingTime <= . 5) {
        r = rotation * easingTime;
    } else {
        r = -rotation + rotation * easingTime;
    }

    if (animationTime < 2.) {
        myst -= . 5;
        myst *= scaleUv(vec2(0.92-animationTime*3.));
        myst += . 5;

        gl_FragColor = texture2D(inputImageTexture, myst);
    }
    else {
        myst = stretchUv(myst, easingTime2, 1);     // Left side stretch
        myst = stretchUv(myst, easingTime2, 3);     // Top stretch
        myst = stretchUv(myst, easingTime, 5);      // Vertical stretch


        // Rotate the current frame
        vec2 mystCurrent = myst;
        mystCurrent.y *= 1./ratio;
        mystCurrent = rotateUv(mystCurrent, r, vec2(1..0.), 1.);
        mystCurrent.y *= ratio;


        // Calculate the actual frame rate with FPS =60 as the interval
        float timeInterval = 0.016;
        if (animationTime < 0.5 && animationTime + timeInterval > 0.5)
            timeInterval = 0.5 - animationTime;


        if (easingTime <= . 5) {
            r = rotation * (easingTime+timeInterval);
        } else {
            r = -rotation + rotation * (easingTime+timeInterval);
        }

        // Next frame is rotated
        vec2 mystNext = myst;
        mystNext.y *= 1./ratio;
        mystNext = rotateUv(mystNext, r, vec2(1..0.), 1.);
        mystNext.y *= ratio;


        // get the unit coordinate direction
        vec2 speed  = (mystNext - mystCurrent) / timeInterval * blur * 0.5;


        if (easingTime <= . 5) {
            mystCurrent -= . 5;
            mystCurrent *= scaleUv(vec2(0.92-animationTime*3.));
            mystCurrent += . 5;
            gl_FragColor = motionBlur(inputImageTexture, mystCurrent, speed);
        } else {
            mystCurrent -= . 5;
            mystCurrent *= scaleUv(vec2(0.92));
            mystCurrent += . 5;
            gl_FragColor= motionBlur(inputImageTexture2, mystCurrent, speed); }}}Copy the code





Related links:

  • www.jhlabs.com/ip/blurring…
  • baike.baidu.com/item/ dynamic blur
  • Stevenlu.net/files/motio…
  • www.cnblogs.com/lancidie/ar…