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 着色器
顶点着色器
1attribute vec4 aPos;
2attribute vec2 aCoordinate;
3varying vec2 vCoordinate;
4void main(){
5 vCoordinate = aCoordinate;
6 gl_Position = aPos;
7}
8复制代码
关于顶点着色器,并没有做任何特殊处理
片元着色器
1precision mediump float;
2uniform sampler2D uSampler;
3uniform sampler2D uSampler2;
4varying vec2 vCoordinate;
5uniform float uOffset;
6void main(){
7 if (vCoordinate.x < 0.5) {
8 gl_FragColor = texture2D(uSampler, vCoordinate);
9 } else {
10 gl_FragColor = texture2D(uSampler2, vCoordinate - vec2(uOffset, 0.0));
11 }
12}
13复制代码
对于片元着色器,关键就在于main()函数里面的if判断,前面也有提到,会对纹理坐标进行一个判断
当
x小于0.5时,显示相机预览画面当
x大于0.5时,显示上一帧的数据,且取的是对应坐标往左偏移的数据(uOffset是偏移量,可以理解成小格子的宽度)
那么对于为什么要偏移呢?
这是因为通过上面,我们可以知道,该特效是从左半部分的边缘区域开始运送的,那么如果我们从对应坐标取,那么不就得不到左半部分区域的坐标了吗,所有得偏移一个小格子的宽度,从而得到对应的数据
这样,每帧渲染时,都取0.4 ~ 0.9区域数据显示到0.5 ~ 1.0区域,从而就实现了该传送带特效
在知道了如何实现该特效后,我们还可以实现纵向的传送带特效,只需要将片元着色器里的x改为y即可
1precision mediump float;
2uniform sampler2D uSampler;
3uniform sampler2D uSampler2;
4varying vec2 vCoordinate;
5uniform float uOffset;
6void main(){
7 if (vCoordinate.y < 0.5) {
8 gl_FragColor = texture2D(uSampler, vCoordinate);
9 } else {
10 gl_FragColor = texture2D(uSampler2, vCoordinate - vec2(0.0, uOffset));
11 }
12}
13复制代码
3.2 Java代码实现部分
下面是Java代码实现部分
这里面使用了一个lastRender保留上一帧数据,从而在下一次渲染时能够使用
1public class ConveyorBeltHFilter extends BaseFilter {
2 private final BaseRender lastRender;
3
4 private int uSampler2Location;
5 private int uOffsetLocation;
6
7 private int lastTextureId = -1;
8
9 private float offset = 0.01f;
10
11 public ConveyorBeltHFilter(Context context) {
12 super(
13 context,
14 "render/filter/conveyor_belt_h/vertex.frag",
15 "render/filter/conveyor_belt_h/frag.frag"
16 );
17
18 lastRender = new BaseRender(context);
19
20 lastRender.setBindFbo(true);
21 }
22
23 @Override
24 public void onCreate() {
25 super.onCreate();
26 lastRender.onCreate();
27 }
28
29 @Override
30 public void onChange(int width, int height) {
31 super.onChange(width, height);
32 lastRender.onChange(width, height);
33 }
34
35 @Override
36 public void onDraw(int textureId) {
37 super.onDraw(textureId);
38 lastRender.onDraw(getFboTextureId());
39 lastTextureId = lastRender.getFboTextureId();
40 }
41
42 @Override
43 public void onInitLocation() {
44 super.onInitLocation();
45 uSampler2Location = GLES20.glGetUniformLocation(getProgram(), "uSampler2");
46 uOffsetLocation = GLES20.glGetUniformLocation(getProgram(), "uOffset");
47 }
48
49 @Override
50 public void onActiveTexture(int textureId) {
51 super.onActiveTexture(textureId);
52 GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
53 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, lastTextureId);
54 GLES20.glUniform1i(uSampler2Location, 1);
55 }
56
57 @Override
58 public void onSetOtherData() {
59 super.onSetOtherData();
60 GLES20.glUniform1f(uOffsetLocation, offset);
61 }
62}
63复制代码
以上就是抖音传送带特效的实现全过程,希望大家喜欢!!!
四、GitHub
ConveyorBeltHFilter.java
ConveyorBeltVFilter.java
作者:mirai
链接:https://juejin.cn/post/6998491350018555941
推荐阅读
『Android自定义View实战』实现一个小清新的弹出式圆环菜单
公众号徐公回复黑马,获取 Android 学习视频 公众号徐公回复徐公666,获取简历模板,教你如何优化简历,走近大厂 公众号徐公回复面试,可以获得面试常见算法,剑指 ofer 题解 公众号徐公回复马士兵,可以获得马士兵学习视频一份