一、實現效果
1.1 首先來看抖音的傳送帶特效
從上圖可以看到,抖音的傳送帶特效有如下特點
- 屏幕左半邊部分是正常預覽視頻
- 屏幕右半邊部分像傳送帶一般,將畫面不斷地像右邊
運送
根據此特效的特點,我們可以制作出各種有趣的視頻
1.2 筆者實現傳送帶特效
從上圖來看,筆者實現的效果基本上和抖音實現的一致
那么,對于該特效,我們應該如何去實現呢?
其實在介紹抖音藍線挑戰特效那一章已經將到一個核心知識點Fbo
,對,沒錯,當時做藍線挑戰特效用到的就是Fbo
,接下來傳送帶特效也需要使用Fbo的保留上一幀功能
接下來,我們就來進行特效分析和具體實現
二、特效分析
首先,根據上面的效果圖,我們可以簡單畫出示意圖,如下圖所示(小格子的數量越多,畫面越精細)
我們以橫向進行分析
在OpenGLES
中,紋理坐標水平方向的起始位置在左方(準確的說是在左上角,這里只是分析橫向的效果,故圖上標點0.0
隨意標在左方,便于分析) 根據上面的效果圖,了解到,該特效有兩個特點
- 屏幕左半邊部分是正常預覽視頻
- 屏幕右半邊部分像傳送帶一般,將畫面不斷地像右邊運送
這里,我用了運送一詞,那么,我們得首先知道,它運送的是什么
2.1 運送什么?
通過分析特效圖,我們知道,圖像右半部分是不斷地向右邊移動,而左半部分是正常預覽的,看起來就好像是從左半部分的邊緣處不斷移動到右邊,那么從這里可以得出一個小結論
它運送的是左半部分的邊緣區域,根據上圖,準確的說是中線左邊0區域的畫面
那么,知道了這點,我們就一目了然了
2.2 它是如何運送的?
前面,我們知道了它運送的是0區域的畫面,那么接下來就來分析下,它是如何運送的
- 在預覽時,相機畫面一般都是正常顯示,0區域的畫面當然也是正常一幀幀刷新
- 當0區域顯示第一幀(簡稱f1,后面以f開后,數字為幀序)時,將其移動到1區域
- 當0區域顯示f2時,將1區域的f1移動到2區域,將0區域的f2移動到1區域
- 依次類推,就可以將0區域的畫面源源不斷地運送到右邊
2.3 Fbo
其實,在知道了它是運送什么,且如何運送后,我們還是無法得知如何實現這一特效
此刻,就該Fbo
登場了,前面藍線挑戰特效的篇章已經對其做了詳細描述,現在簡單介紹下
- 可以將Oes紋理轉換成2D紋理
- 可以將紋理數據不顯示在屏幕上,并保留下來
這里,我們要實現該特效,就要使用它的保留幀數據的功能
2.4 特效實現
在上面,我們已經知道了該特效是如何運送數據,那么通過下圖,我們來了解如何使用Fbo
實現
從上面的分析可知,該特效運送的是左半部分的邊緣區域,所有有如何下實現步驟:
- 首先假設每個小格的步長為
0.1
,那么左半部分的邊緣區域就是0.4 ~ 0.5
這個區域 - Fbo可以保存上一幀,那么在渲染時,我們將上一幀的數據保存下來
- 在渲染的時候,會有兩個紋理,一個是相機的正常預覽紋理,另一個是保存的上一幀,此時,我們在著色器里就要進行判斷
- 當紋理坐標x小于
0.5
時,顯示相機的正常預覽畫面 - 當紋理坐標x大于
0.5
時,顯示保存的上一幀畫面,不過這里要注意,并不是對應坐標的上一幀數據,即,不是0.5 ~ 1.0
區域的數據,而是0.4 ~ 0.9
區域的數據,大家可以思考下這是為什么,后面具體實現的時候會有解答
這樣,當相機不斷產生預覽數據時,右半部分將不斷地將左半部分的邊緣區域向右邊運送
三、具體實現
前面我們分析了該特效的整個實現流程,接下來就是具體的實現
首先,先上大家最關心的著色器代碼
3.1 著色器
頂點著色器
attribute vec4 aPos;
attribute vec2 aCoordinate;
varying vec2 vCoordinate;
void main(){
vCoordinate = aCoordinate;
gl_Position = aPos;
}
關于頂點著色器,并沒有做任何特殊處理 片元著色器
precision mediump float;
uniform sampler2D uSampler;
uniform sampler2D uSampler2;
varying vec2 vCoordinate;
uniform float uOffset;
void main(){
if (vCoordinate.x < 0.5) {
gl_FragColor = texture2D(uSampler, vCoordinate);
} else {
gl_FragColor = texture2D(uSampler2, vCoordinate - vec2(uOffset, 0.0));
}
}
對于片元著色器,關鍵就在于main()
函數里面的if判斷,前面也有提到,會對紋理坐標進行一個判斷
- 當x小于
0.5
時,顯示相機預覽畫面 - 當x大于
0.5
時,顯示上一幀的數據,且取的是對應坐標往左偏移的數據(uOffset
是偏移量,可以理解成小格子的寬度)
那么對于為什么要偏移呢?
這是因為通過上面,我們可以知道,該特效是從左半部分的邊緣區域開始運送的,那么如果我們從對應坐標取,那么不就得不到左半部分區域的坐標了嗎,所有得偏移一個小格子的寬度,從而得到對應的數據 這樣,每幀渲染時,都取0.4 ~ 0.9
區域數據顯示到0.5 ~ 1.0
區域,從而就實現了該傳送帶特效
在知道了如何實現該特效后,我們還可以實現縱向的傳送帶特效,只需要將片元著色器里的x改為y即可
precision mediump float;
uniform sampler2D uSampler;
uniform sampler2D uSampler2;
varying vec2 vCoordinate;
uniform float uOffset;
void main(){
if (vCoordinate.y < 0.5) {
gl_FragColor = texture2D(uSampler, vCoordinate);
} else {
gl_FragColor = texture2D(uSampler2, vCoordinate - vec2(0.0, uOffset));
}
}
3.2 Java代碼實現部分
下面是Java代碼實現部分
這里面使用了一個lastRender
保留上一幀數據,從而在下一次渲染時能夠使用
public class ConveyorBeltHFilter extends BaseFilter {
private final BaseRender lastRender;
private int uSampler2Location;
private int uOffsetLocation;
private int lastTextureId = -1;
private float offset = 0.01f;
public ConveyorBeltHFilter(Context context) {
super(
context,
"render/filter/conveyor_belt_h/vertex.frag",
"render/filter/conveyor_belt_h/frag.frag"
);
lastRender = new BaseRender(context);
lastRender.setBindFbo(true);
}
@Override
public void onCreate() {
super.onCreate();
lastRender.onCreate();
}
@Override
public void onChange(int width, int height) {
super.onChange(width, height);
lastRender.onChange(width, height);
}
@Override
public void onDraw(int textureId) {
super.onDraw(textureId);
lastRender.onDraw(getFboTextureId());
lastTextureId = lastRender.getFboTextureId();
}
@Override
public void onInitLocation() {
super.onInitLocation();
uSampler2Location = GLES20.glGetUniformLocation(getProgram(), "uSampler2");
uOffsetLocation = GLES20.glGetUniformLocation(getProgram(), "uOffset");
}
@Override
public void onActiveTexture(int textureId) {
super.onActiveTexture(textureId);
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, lastTextureId);
GLES20.glUniform1i(uSampler2Location, 1);
}
@Override
public void onSetOtherData() {
super.onSetOtherData();
GLES20.glUniform1f(uOffsetLocation, offset);
}
}
以上就是抖音傳送帶特效的實現全過程,希望大家喜歡?。?!
四、GitHub
github地址:https://github.com/JYangkai/MediaDemo/
- ConveyorBeltHFilter.java
- ConveyorBeltVFilter.java