如何在WebGL中實(shí)現(xiàn)短視頻卡點(diǎn)動(dòng)效?
卡點(diǎn)動(dòng)效展示
一、效果分解
慢動(dòng)作分解一下上面的視頻效果,可以看到圖片入場(chǎng)沿著從左上角至中心點(diǎn)的曲線位移,加上一個(gè)運(yùn)動(dòng)模糊來模擬快速進(jìn)入然后減速的效果,同時(shí)會(huì)有一個(gè)彈性效果。
二、沿貝塞爾曲線移動(dòng)
通過分解可以看到圖片進(jìn)入顯示區(qū)域的軌跡是一條類似如下圖這樣的曲線:
在數(shù)學(xué)中可以使用三次貝塞爾曲線來表達(dá)這樣的曲線,三次貝塞爾曲線的公式如下:
類似的曲線還有圓弧線,但是貝塞爾曲線更靈活通用,且x坐標(biāo)剛好適配在[0,1]這個(gè)區(qū)間之間。
確定曲線的端點(diǎn),這里的坐標(biāo)系y軸和WebGL坐標(biāo)系y軸方向相反,因此記得對(duì)y做一下?lián)Q算。
得到WebGL坐標(biāo)系中四個(gè)控制點(diǎn):p0 = vec2(0.4,0.2), p1 = vec2(0.5,0.303), p2 = vec2(0.5,0.362), p3 = vec2(0.5,0.5)。
Shader中增加Bezier曲線的公式:
float Bezier(float p0, float p1, float p2, float p3, float t) {
float x0;
float x1;
float x2;
float x3;
x0 = p0 * pow((1. - t), 3.);
x1 = 3. * p1 * t * pow((1. - t), 2.);
x2 = 3. * p2 * pow(t, 2.) * (1. - t);
x3 = p3 * pow(t, 3.);
return x0 + x1 + x2 + x3;
}
vec2 getBezierPoint(vec2 p0, vec2 p1, vec2 p2, vec2 p3, float progress) {
return vec2(
Bezier(p0.x, p1.x, p2.x, p3.x, progress),
Bezier(p0.y, p1.y, p2.y, p3.y, progress)
);
}
根據(jù)當(dāng)前的動(dòng)畫進(jìn)度拿到圖片的當(dāng)前的位置,計(jì)算出位移,并從圖片上取到對(duì)應(yīng)點(diǎn):
uniform float progress;
uniform sampler2D inputImageTexture;
...
vec4 getColor(vec2 position) {
// scalarRatio為圖片縮放比例
position = (position - vec2(0.5, 0.5)) / scalarRatio + vec2(0.5, 0.5);
return texture2D(inputImageTexture, position);
}
void main() {
...
vec2 p0 = vec2(0.45, 0.2);
vec2 p1 = vec2(0.5,0.303);
vec2 p2 = vec2(0.5,0.5);
vec2 p3 = vec2(0.5, 0.5);
vec2 currentPos = getBezierPoint(p0, p1, p2, p3, progress);
vec2 distance = currentPos - vec2(0.5, 0.5);
// 根據(jù)distance做圖片平移操作
vec2 pos = textureCoordinate.xy - distance;
gl_FragColor = getColor(pos);
}
這時(shí)圖片可以動(dòng)起來了,但是效果比較呆板,始終都是朝著一個(gè)方向。
為了模擬沿著曲線滑動(dòng)的效果,我們沿著貝塞爾曲線的切線再給它加上一個(gè)旋轉(zhuǎn)。貝塞爾曲線求切線方向向量的方法如下:
vec2 computeBezierDerivative(vec2 p0, vec2 p1, vec2 p2, vec2 p3, float progress) {
p0 = 3.0 * (p1-p0);
p1 = 3.0 * (p2-p1);
p2 = 3.0 * (p3-p2);
return p0 * (1. - progress) * (1. - progress) + 2. * p1 * (1. - progress) * progress + p2 * progress * progress;
}
在位移的同時(shí)計(jì)算一個(gè)旋轉(zhuǎn)量:
vec2 getRotate(vec2 pos,float angle){
float s=sin(angle);
float c=cos(angle);
vec2 center=vec2(.5,.5);
// 此處省略了圖片適配canvas比例的操作
...
mat2 rotMat=mat2(c,-s,s,c);
pos=pos-center;
pos=rotMat * pos;
pos=pos+center;
return pos;
}
void main() {
...
vec2 p0 = vec2(0.4, 0.2);
vec2 p3 = vec2(0.5, 0.5);
vec2 p1 = vec2(0.5, 0.4);
vec2 p2 = vec2(0.5, 0.45);
vec2 currentPos = getBezierPoint(p0, p1, p2, p3, progress);
vec2 distance = currentPos - vec2(0.5, 0.5);
// 根據(jù)distance做圖片平移操作
vec2 pos = textureCoordinate.xy - distance;
vec2 dir = computeBezierDerivative(p0, p1, p2, p3, progress);
angle = asin(dir.x / dir.y);
pos = getRotate(pos, -angle);
gl_FragColor = getColor(pos);
}
下圖所示為施加后的效果,此時(shí)可以看到圖片沿著貝塞爾曲線移動(dòng)的效果更加自然了。
但是勻速移動(dòng)往往看起來比較呆板沒有動(dòng)感,常常用到緩動(dòng)曲線來使運(yùn)動(dòng)更加有節(jié)奏,查看更多常用的緩動(dòng)函數(shù)可點(diǎn)擊下方傳送門【1】。
【1】http://www.xldzh.cn///#
這里我使用的是彈性曲線,來實(shí)現(xiàn)一種duang~duang~的感覺,處理邏輯如下:
float easeOutElastic(float progress){
float c4 = (2. * 3.1415926) / 3.;
return progress == 0.
? 0.
: progress == 1.
? 1.
: pow(2., -10. * progress) * sin((progress * 10. - 0.75) * c4) + 1.;
}
再用easeOutElastic函數(shù)處理一下progress:
vec2 currentPos = getBezierPoint(p0, p1, p2, p3, easeOutElastic(progress));
三、動(dòng)態(tài)模糊實(shí)現(xiàn)
模糊算法是非常常用的圖像處理算法之一了。常見的有高斯模糊、徑向模糊、旋轉(zhuǎn)模糊、運(yùn)動(dòng)模糊等,模糊可以在視覺上更好地造成快速運(yùn)動(dòng)的感覺。
在放大縮小效果中常常用到徑向模糊,平移的時(shí)候則常用到運(yùn)動(dòng)模糊,旋轉(zhuǎn)模糊顧名思義就常用在旋轉(zhuǎn)的場(chǎng)景中了。
在示例斜切入畫的效果里,圖片主要是沿著曲線在走向下,因此我們給它加一個(gè)豎向的運(yùn)動(dòng)模糊。
// 動(dòng)態(tài)模糊算法
vec4 motionBlur(vec2 velocity, vec2 position) {
int kernelSize = 20;
float offset = 0.;
int MAX_KERNEL_SIZE = 2048;
vec4 color = getColor(position, index);
if (kernelSize == 0)
{
return color;
}
velocity = velocity / filterArea.xy;
offset = -offset / length(velocity) - 0.5;
int k = kernelSize - 1;
for(int i = 0; i < MAX_KERNEL_SIZE - 1; i++) {
if (i == k) {
break;
}
vec2 bias = velocity * (float(i) / float(k) + offset);
color += getColor(position + bias);
}
return color / float(kernelSize);
}
void main() {
...
float vy = 40. * progress;
vec4 color = motionBlur(vec2(0., vy), pos);
}
大功告成,最終效果如下圖所示:
四、結(jié)語
本文主要使用貝塞爾曲線位移+旋轉(zhuǎn)+緩動(dòng)曲線實(shí)現(xiàn)了一個(gè)照片的動(dòng)態(tài)效果,加上動(dòng)感的音樂就可以組合成時(shí)尚的卡點(diǎn)視頻。同樣的思路還可以實(shí)現(xiàn)更多的效果,比如我們經(jīng)常在各種小視頻上看到的“甩來甩去”的效果。
如下圖所示,這里就使用了一個(gè)橫向的貝塞爾曲線,加上沿貝塞爾曲線的法向量旋轉(zhuǎn),前文已經(jīng)給出了求切線方向的方法,求法向量也就很簡(jiǎn)單了。
最后再來給大家安利一波騰訊微剪,騰訊微剪是一個(gè)短視頻剪輯小程序插件,支持實(shí)時(shí)編輯預(yù)覽,支持多視頻圖片的導(dǎo)入導(dǎo)出,內(nèi)置精美的濾鏡、特效、貼紙、字體,自帶炫酷模板,接入簡(jiǎn)單,適合各種音視頻剪輯的場(chǎng)景,歡迎掃碼體驗(yàn)~
文章推薦
hash+跳表,玩轉(zhuǎn)Redis有序集合
文章轉(zhuǎn)載自微信公眾號(hào)云加社區(qū)
聲明:本站所有文章資源內(nèi)容,如無特殊說明或標(biāo)注,均為采集網(wǎng)絡(luò)資源。如若本站內(nèi)容侵犯了原著者的合法權(quán)益,可聯(lián)系本站刪除。