作者:星隕
來源:
音視訊開發進階轉場介紹
轉場效果是什麼?
轉場效果,簡單來說就是兩段視訊之間的銜接過渡效果。
現在拍攝 vlog 的玩家越來越多,要是視訊沒有一兩個炫酷的轉場效果,都不好意思拿出來炫酷了。

那麼如何在視訊編輯軟體中實作轉場效果呢?
這裡提供使用 OpenGL 實作視訊轉場的一個小示例,我們可以通過自定義 GLSL 來實作不同的轉場效果。
以在 Android 平台上作為示範,但其實不管是 Android 還是 iOS,實作的原理都是一樣的。
首先要有兩段視訊,視訊 A 和視訊 B,先播放視訊 A 後播放視訊 B,中間有一段過程稱為 C ,C 就是視訊 A、B 做轉場動畫的時間段。
如下所示:
播放器按照時間順序,從 A -> C -> B 的播放,這樣就有了轉場的效果。
視訊轉場,首先就得有視訊,直接從視訊 A、B 中解碼出目前幀并通過 OpenGL 顯示到螢幕上就好了,如果你對這個操作不熟悉的話,可以檢視我的公衆号【紙上淺談】曆史文章,都有寫過相關内容。
這裡以圖檔來替代視訊 A、B 中解碼出來的幀。
最終效果如下:
實作講解
模拟視訊渲染播放
模拟 fps 為 30 的視訊,用 RxJava 每間隔 30 ms 就觸發一次 OpenGL 渲染。
Observable
.interval(30, TimeUnit.MILLISECONDS)
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe {
mGLSurfaceView.requestRender()
}
另外,如果在視訊 A 播放階段不斷地改變圖檔,也就是更新紋理内容,就相當于在真實的解碼視訊進行播放了。
當然這些操作隻是為了讓這個小例子更加貼近真正的視訊轉場,重要的還是在于如何實作轉場的 Shader 效果。
首先轉場的時候要有兩個紋理作為輸入,那麼肯定要定義兩個
sampler2D
進行采樣了。
varying vec2 vTextureCoord;//接收從頂點着色器過來的參數
uniform sampler2D sTexture1;
uniform sampler2D sTexture2;
其中
sTexture1
對應于視訊 A 内容,
sTexture2
對應于視訊 B 内容。
vTextureCoord
對應于頂點着色器傳遞過來的紋理坐标,視訊 A 和 視訊 B 都需要用到這個紋理坐标。
這個時候,隻要調用 texture2D 方法就能得到視訊 A 和 視訊 B 的内容了。
// 得到視訊 A 的内容
texture2D(sTexture1,vTextureCoord)
// 得到視訊 B 的内容
texture2D(sTexture2,vTextureCoord)
要注意的是這裡說得到視訊 A/B 的内容,是得到紋理坐标對應的圖像内容。也就是說如果紋理坐标是 [0,1] 範圍内,那麼可以得到視訊 A/B 的全部圖像内容。如果坐标是 [-0.5,0.5] 那麼隻能采樣得到一半内容了。
轉場效果實作
混合函數 mix
由于轉場效果是需要視訊 A 和視訊 B 進行疊加混合的,而 GLSL 内嵌了
mix
函數進行調用。
對于 GLSL 中有哪些内嵌的函數可以直接調用的,可以參考寫過的文章記錄:
OpenGL ES 2.0 着色器語言 GLSL 學習https://glumes.com/post/opengl/opengl-glsl-2-mark
mix
函數的聲明如下:
genType mix(genType x,genType y,float a) // 其中 genType 泛指 GLSL 中的類型定義
它的主要功能是使用因子 a 對 x 與 y 執行線性混合,既傳回 x (1-a) + y a 。
現在,通過 texture2D 能得到視訊幀内容,通過 mix 能進行視訊幀混合疊加,那麼就可以得到最後轉場視訊幀了。
vec4 color = mix(texture2D(sTexture1,vTextureCoord),texture2D(sTexture2,vTextureCoord),a);
渲染進度控制
似乎到這裡就可以大功告成了,實際上才剛剛完成了一半~~~
要知道轉場效果是随着時間來播放的,就上面的例子中,轉場時間内,一開始都是視訊 A 的内容,然後視訊 A 逐漸減少,視訊 B 逐漸增多,到最後全是視訊 B 内容,在我們的 Shader 中也要展現這個時間變化的概念。
在 Shader 中定義
progress
變量,代表轉場的播放進度,進度為 0 ~ 1.0 之間。
uniform float progress;
同時在每一次渲染時更新
progress
變量的值。
GLES20.glUniform1f(muProgressHandle, mProgress);
當
progress
為 0 時代表轉場剛剛開始,當
progress
為 1 時代表轉場已經結束了。
if (mProgress >= 1.0f) {
mProgress = 0.0f;
} else {
mProgress += 0.01;
}
這裡
progress
每次遞增 0.01,完成一次轉場就需要 100 次渲染,每次渲染間隔 30ms,那麼一次轉場動畫就是 3000ms 了,當然這個可以自己調節的。
畫面繪制
再回到
mix
函數的參數
a
,這個參數起到了随時間調節轉場混合程度的作用。當 a = 0 時,全是視訊 A 的内容, 當 a = 1 時,全是視訊 B 的内容。
如上圖所示,在轉場動畫的某一幀,左側是視訊 A 的内容,因為此時 a = 0,右側是視訊 B 的内容,此時 a = 1 。
可以看到在一次渲染繪制内 a 既要能等于 0 ,還要能等于 1 ,這個是怎麼實作的呢?
事實上我們說的一次渲染繪制,通常指 OpenGL draw 方法的一次調用,但是在這一次調用裡,還是有很多步驟要執行的。
OpenGL 渲染管線會先執行頂點着色器,然後光栅化,再接着就是片段着色器,片段着色器會根據紋理坐标采樣紋理貼圖上的像素内容進行着色,是以片段着色器在管線中會多次執行,針對每個像素都要進行着色。
上面圖像的小方塊就好比一個像素,每個像素都要執行一個片段着色器。
首先,肯定所有的像素都要進行着色的。左側方塊采樣視訊 A 的紋理進行着色,右側方塊采樣視訊 B 的紋理進行着色。
回到如下代碼:
mix(texture2D(sTexture1,vTextureCoord),texture2D(sTexture2,vTextureCoord),a);
隻要保證繪制左側時 a = 0,繪制右側時 a = 1 就行了。這裡可以通過移動紋理坐标來控制 a 的值。
vec2 p = vTextureCoord + progress * sign(direction);
float a = step(0.0,p.y) * step(p.y,1.0) * step(0.0,p.x) * step(p.x,1.0);
OpenGL 中定義紋理坐标範圍是 [0 ~ 1] ,可以将範圍右移 0.5 ,進而變成 [0.5 ~ 1.5] ,此時紋理坐标一半位于規定範圍内,一半超出界外了。
這樣就可以通過對目前像素小方格對應的紋理坐标的 x,y 值運用
step
函數進行判斷是否在界内,就可以決定是采樣視訊 A 還是視訊 B 的圖像了。
當每次重新整理 progress 時,就向右移一小段距離,視訊 A 随着右移而變少,視訊 B 變多,這樣就是實作了轉場效果。
聯想和總結
不知道這個簡單的例子有沒有讓你想到些什麼?
對的,沒錯,就是升職加薪,走向巅峰必備的 PPT 技能,這種視訊轉場的實作效果就和我們在編輯 PPT 動畫時添加的一樣。
而且這還是比較簡單的,想要做一些花裡胡哨的轉場特效,缺少靈感就可以參考 PPT 裡面的動畫了。
另外,我們還可以對轉場效果做一些總結分類,比如示例中用的是圖檔,可以了解成視訊 A 的最後一幀顯示與視訊 B 的第一幀顯示做轉場效果,這種轉場效果實際使用的人比較少,大多數是視訊 A 的最後一幀與視訊 B 的前一段時間的視訊做轉場效果。
是以也可以對轉場效果做個分類:
- 視訊 A 最後一幀與視訊 B 第一幀做轉場動畫
- 視訊 A 最後一幀與視訊 B 前一段時間視訊做轉場動畫
- 視訊 A 最後一段時間視訊 與視訊 B 第一幀做轉場動畫
- 視訊 A 最後一段時間視訊 與視訊 B 前一段時間視訊做轉場動畫
這四個分類的實作原理其實都差不多,如果是一段視訊的話,那麼就在視訊播放時更新對應紋理。
以上就在關于使用 OpenGL 在視訊編輯中實作轉場效果的講解,通過這篇文章希望大家可以掌握轉場的基本實作原理。
OpenGL 系列文章「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。