扣丁書屋

Android 實現抖音傳送帶特效!

一、實現效果

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

https://mp.weixin.qq.com/s/fVkzVV5weJ-rfXIaopJNHQ

最多閱讀

簡化Android的UI開發 3年以前  |  515859次閱讀
Android 深色模式適配原理分析 2年以前  |  27474次閱讀
Android 樣式系統 | 主題背景覆蓋 2年以前  |  8727次閱讀
Android Studio 生成so文件 及調用 2年以前  |  6741次閱讀
30分鐘搭建一個android的私有Maven倉庫 4年以前  |  5508次閱讀
Android設計與開發工作流 3年以前  |  5225次閱讀
移動端常見崩潰指標 2年以前  |  5081次閱讀
Android陰影實現的幾種方案 5月以前  |  5053次閱讀
Google Enjarify:可代替dex2jar的dex反編譯 4年以前  |  5018次閱讀
Android內存異常機制(用戶空間)_NE 2年以前  |  4766次閱讀
Android-模塊化-面向接口編程 2年以前  |  4667次閱讀
Android多渠道打包工具:apptools 4年以前  |  4570次閱讀
Google Java編程風格規范(中文版) 4年以前  |  4423次閱讀
Android死鎖初探 2年以前  |  4391次閱讀

手機掃碼閱讀
18禁止午夜福利体验区,人与动人物xxxx毛片人与狍,色男人窝网站聚色窝
<蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>