пониматьГауссПосле этого посмотрите на размытие в движении.
Что такое размытие в движении?Согласно определению Википедии: Размытие в движении или размытие в движении — это статическая сцена или серия изображений, таких как фильм или анимация, вызванная быстро движущимися объектами, вызывающими очевидные размытые следы перетаскивания.
Почему возникает размытие при движении?Камера работает, показывая сцену на пленку за очень короткий промежуток времени. Свет от сцены падает на пленку, вызывая химическую реакцию, которая в конечном итоге создает изображение. Это экспозиция. Если в процессе экспонирования сцена меняется, изображение получается размытым.
Вопрос 1: Является ли размытие в движении однонаправленным размытием по Гауссу?
В соответствии с принципом физического изображения размытия в движении мы можем знать, что чем ближе затвор к более четкому изображению, тем меняется прозрачность остаточного изображения, и на нее также влияет скорость:
Давайте попробуем смоделировать размытие в движении с помощью размытия по Гауссу в одном направлении и посмотрим, как это работает:
// 只展示核心代码
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.));
}
Хотя размытие по Гауссу в одном направлении не совсем соответствует определению размытия в движении. Но трудно заметить разницу, просто взглянув на эффект. Давайте усилим эффект:
Вопрос 2: Как сделать так, чтобы картинка двигалась естественно?
Размытие движения, естественно, требует проявления движения. Сначала реализуем простое смещение:
// 只展示核心代码
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.));
}
Хорошо, немного горячие глаза. Первое, что нужно исправить, это то, что границы изображения связаны с местами и нет размытия движения:
Это означает, что нам нужно взять текущие координаты в режиме реального времени для выборки изображения, поэтому передайте новый параметр, представляющий расстояние текущего движения:
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);
}
После решения краевой задачи давайте проанализируем время размытия движения, начало и конец движения точно не будут размыты, будет размыт только средний процесс, поэтому настраиваем размытие по времени:
Сначала мы строим кривую изменения его значения от 0 до 1 до 0 в единицу времени от 0 до 1, как наш нечеткий множитель, через этоинструмент, внесите небольшую модификацию в предыдущую функцию плотности вероятности нормального распределения:
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);
}
Это чувствуется немного, а затем добавьте эффект смягчения времени, очевидно, что мы хотим кривую easyInOut:
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: 传入时间变量
* @param mX1/mY1/mX2/mY2: 贝塞尔曲线四个值
* 说明: 这个函数以上的其他函数都是本函数使用的辅助函数
*/
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);
}
Наконец, добавьте к нему небольшую вертикальную деформацию, чтобы он растянулся:
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: 传入时间变量
* @param mX1/mY1/mX2/mY2: 贝塞尔曲线四个值
* 说明: 这个函数以上的其他函数都是本函数使用的辅助函数
*/
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;
// 居左
if (direction == 1) {
stretchRatio = (currentMaxStretchRatio - 1.0) * (1.-_st.x) + 1.0;
stUse.y = (_st.y - 0.5) / stretchRatio + 0.5;
}
// 居右
else if (direction == 2) {
stretchRatio = (currentMaxStretchRatio - 1.0) * _st.x+ 1.0;
stUse.y = (_st.y - 0.5) / stretchRatio + 0.5;
}
// 居上
else if (direction == 3) {
stretchRatio = (currentMaxStretchRatio - 1.0) * _st.y + 1.0;
stUse.x = (_st.x - 0.5) / stretchRatio + 0.5;
}
// 居下
else if (direction == 4) {
stretchRatio = (currentMaxStretchRatio - 1.0) * (1.-_st.y) + 1.0;
stUse.x = (_st.x - 0.5) / stretchRatio + 0.5;
}
// 垂直
else if (direction == 5) {
stretchRatio = (currentMaxStretchRatio - 1.0) * .3 + 1.0;
stUse.y = (_st.y - 0.5) / stretchRatio + 0.5;
}
// 水平
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);
// 形变还是用匀速的 time 时间变量
myst = stretchUv(myst, time, 5);
gl_FragColor = blur13(inputImageTexture, myst, iResolution, vec2(0., 20.*blur), speed);
}
Вопрос 3: Как решить размытие движения в любом направлении?
Предположим, я хочу реализовать поворотный переход:
void main() {
vec2 myst = uv; // 用于坐标计算
float ratio = iResolution.x / iResolution.y; // 屏幕比例
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);
}
}
Направление размытия движения вращательного движения не просто горизонтальное, вертикальное или наклонное.Предположим, мы задаем грубое направление и смотрим, каким оно будет:
// 改造了一下 blur13 函数,去掉了 speed 参数,因为旋转已经在外部完成了
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; // 用于坐标计算
float ratio = iResolution.x / iResolution.y; // 屏幕比例
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));
}
}
Это явно не то направление, которое нам нужно. Здесь мы можем изменить идею, мы можем взять координаты следующего кадра и вычесть координаты предыдущего кадра, и полученное значение будет нашим направлением движения.
Итак, сделайте следующее:
void main() {
vec2 myst = uv; // 用于坐标计算
float ratio = iResolution.x / iResolution.y; // 屏幕比例
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;
}
// 当前帧进行旋转
vec2 mystCurrent = myst;
mystCurrent.y *= 1./ratio;
mystCurrent = rotateUv(mystCurrent, r, vec2(1., 0.), -1.);
mystCurrent.y *= ratio;
// 以 fps=60 作为间隔
float timeInterval = 0.00016;
if (easingTime <= .5) {
r = rotation * (easingTime+timeInterval);
} else {
r = -rotation + rotation * (easingTime+timeInterval);
}
// 下一帧帧进行旋转
vec2 mystNext = myst;
mystNext.y *= 1./ratio;
mystNext = rotateUv(mystNext, r, vec2(1., 0.), -1.);
mystNext.y *= ratio;
// 得到单位坐标方向
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);
}
}
Кажется, что традиционное однонаправленное размытие по Гауссу не может дать желаемого эффекта.Вот еще одна функция (включая эффект случайного размытия):
// 运动模糊
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; // 用于坐标计算
float ratio = iResolution.x / iResolution.y; // 屏幕比例
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;
}
// 当前帧进行旋转
vec2 mystCurrent = myst;
mystCurrent.y *= 1./ratio;
mystCurrent = rotateUv(mystCurrent, r, vec2(1., 0.), -1.);
mystCurrent.y *= ratio;
// 以 fps=60 作为间隔
float timeInterval = 0.0167;
if (easingTime <= .5) {
r = rotation * (easingTime+timeInterval);
} else {
r = -rotation + rotation * (easingTime+timeInterval);
}
// 下一帧帧进行旋转
vec2 mystNext = myst;
mystNext.y *= 1./ratio;
mystNext = rotateUv(mystNext, r, vec2(1., 0.), -1.);
mystNext.y *= ratio;
// 得到单位坐标方向
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);
}
}
Очевидно, что для более сложного движения (без смещения) чистое размытие по Гауссу не может дать очень реалистичных эффектов, и его также нужно сопоставлять со случайным размытием, деформацией, искажением и другими факторами:
Затем мы добавим масштабирование холста, отскок и т. д., заходите:
void main() {
vec2 myst = uv; // 用于坐标计算
float ratio = iResolution.x / iResolution.y; // 屏幕比例
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); // 左侧拉伸
myst = stretchUv(myst, easingTime2, 3); // 顶部拉伸
myst = stretchUv(myst, easingTime, 5); // 垂直拉伸
// 当前帧进行旋转
vec2 mystCurrent = myst;
mystCurrent.y *= 1./ratio;
mystCurrent = rotateUv(mystCurrent, r, vec2(1., 0.), -1.);
mystCurrent.y *= ratio;
// 以 fps=60 作为间隔,计算出实际帧速率
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);
}
// 下一帧帧进行旋转
vec2 mystNext = myst;
mystNext.y *= 1./ratio;
mystNext = rotateUv(mystNext, r, vec2(1., 0.), -1.);
mystNext.y *= ratio;
// 得到单位坐标方向
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);
}
}
}
Ссылки по теме: