導(dǎo)語 | “騰訊微剪”是一個(gè)小程序端的實(shí)時(shí)預(yù)覽短視頻編輯插件,支持豐富的視頻效果,近期上架了視頻模板的功能,本文將針對(duì)其中的卡點(diǎn)模板切入動(dòng)效,還原技術(shù)實(shí)現(xiàn)的思路,希望與大家一同交流。文章作者:楊琳,騰訊前端研發(fā)工程師。

卡點(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ū)