当一个 Android 开发玩抖音玩疯了之后(二)
上一篇文章中,我大概详情了一下短视频的拍摄,主要就是音视频的加减速。这篇文章我将详情下抖音视频特效的实现,废话不多说,进入正题。
1.特效概览
特效列表特效列表
抖音上目前有这九种视频特效,本文将详情前面六种的实现。有人可能会问了,为什么最后三种特效被忽略了。
当然是由于我懒啦。
没想到吧
2.『灵魂出窍』
抖音的实现效果如下:
灵魂出窍
我的实现效果如下:
ezgif.com-rotate.gif
代码实现
通过观察抖音的效果,可以看到,共有两个图层,一个是视频原图,还有一个是从中心放大并且透明度逐步减小的图层,关键代码如下。
2.1 顶点着色器
uniform mat4 uTexMatrix;attribute vec2 aPosition;attribute vec4 aTextureCoord;varying vec2 vTextureCoord;uniform mat4 uMvpMatrix;void main(){ gl_Position = uMvpMatrix * vec4(aPosition,0.1,1.0); vTextureCoord = (uTexMatrix * aTextureCoord).xy;}
2.2 片元着色器
#extension GL_OES_EGL_image_external : requireprecision mediump float;varying vec2 vTextureCoord;uniform samplerExternalOES uTexture;uniform float uAlpha;void main(){ gl_FragColor = vec4(texture2D(uTexture,vTextureCoord).rgb,uAlpha);}
这两部分代码比较简单,没有什么特殊的操作,就是单纯地把纹理渲染到内存中
2.3动画代码
//当前动画进度private float mProgress = 0.0f;//当前地帧数private int mFrames = 0;//动画最大帧数private static final int mMaxFrames = 15;//动画完成后跳过的帧数private static final int mSkipFrames = 8;//放大矩阵private float[] mMvpMatrix = new float[16];//opengl 参数位置private int mMvpMatrixLocation;private int mAlphaLocation;public void onDraw(int textureId,float[] texMatrix){ //由于这里是两个图层,所以开启混合模式 glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA); mProgress = (float) mFrames / mMaxFrames; if (mProgress > 1f) { mProgress = 0f; } mFrames++; if (mFrames > mMaxFrames + mSkipFrames) { mFrames = 0; } Matrix.setIdentityM(mMvpMatrix, 0);//初始化矩阵 //第一帧是没有放大的,所以这里直接赋值一个单位矩阵 glUniformMatrix4fv(mMvpMatrixLocation, 1, false, mMvpMatrix, 0); //底层图层的透明度 float backAlpha = 1f; //放大图层的透明度 float alpha = 0f; if (mProgress > 0f) { alpha = 0.2f - mProgress * 0.2f; backAlpha = 1 - alpha; } glUniform1f(mAlphaLocation, backAlpha); glUniformMatrix4fv(mUniformTexMatrixLocation, 1, false, texMatrix, 0); //初始化顶点着色器数据,包括纹理坐标以及顶点坐标 mRendererInfo.getVertexBuffer().position(0); glVertexAttribPointer(mAttrPositionLocation, 2, GL_FLOAT, false, 0, mRendererInfo.getVertexBuffer()); mRendererInfo.getTextureBuffer().position(0); glVertexAttribPointer(mAttrTexCoordLocation, 2, GL_FLOAT, false, 0, mRendererInfo.getTextureBuffer()); GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); //绘制底部原图 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); if (mProgress > 0f) { //这里绘制放大图层 glUniform1f(mAlphaLocation, alpha); float scale = 1.0f + 1f * mProgress; Matrix.scaleM(mMvpMatrix, 0, scale, scale, scale); glUniformMatrix4fv(mMvpMatrixLocation, 1, false, mMvpMatrix, 0); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); } GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); GLES20.glUseProgram(0); glDisable(GL_BLEND);}
以上代码最终绘制出来的就是 『灵魂出窍』的效果
3.『抖动』
抖音的实现效果如下:
shake
我的实现效果如下:
ezgif-4-d0c993e10f.gif
代码实现
要做这个效果前,我们先分析下抖音的效果。这个特效总共包含两个部分的内容:
- 中心放大
- 颜色偏移
我们把视频暂停截图之后,可以看到如下的图:
WX20180910-191159.png
从图上我们可以看到,键盘里的原文字变成了蓝色,而左上角和右下角分别多了绿色和红色的字,那么这个颜色分离就是将一个像素的RGB值分别分离出去。
2.1 顶点着色器
uniform mat4 uTexMatrix;attribute vec2 aPosition;attribute vec4 aTextureCoord;varying vec2 vTextureCoord;uniform mat4 uMvpMatrix;void main(){ gl_Position = uMvpMatrix * vec4(aPosition,0.1,1.0); vTextureCoord = (uTexMatrix * aTextureCoord).xy;}
2.2 片元着色器
#extension GL_OES_EGL_image_external : requireprecision mediump float;varying vec2 vTextureCoord;uniform samplerExternalOES uTexture;//颜色的偏移距离uniform float uTextureCoordOffset;void main(){ vec4 blue = texture2D(uTexture,vTextureCoord); vec4 green = texture2D(uTexture,vec2(vTextureCoord.x + uTextureCoordOffset,vTextureCoord.y + uTextureCoordOffset)); vec4 red = texture2D(uTexture,vec2(vTextureCoord.x - uTextureCoordOffset,vTextureCoord.y - uTextureCoordOffset)); gl_FragColor = vec4(red.x,green.y,blue.z,blue.w);}
这里分析下片元着色器的代码,要实现像素偏移,首先我们要明白的一点是,片元着色器是针对每个像素生效的,代码中的vTextureCoord包含了当前像素的坐标(x,y),x和y分别都是从0到1。假如要将像素的颜色分离,那么我们只要要将texture2D函数中的坐标进行转换就行了。举个栗子,(0.1,0.1)的点上有个白色像素,当前像素的坐标是(0.0,0.0),我们要让白色像素的绿色分量显示在当前像素的位置上,那么我们可以将当前像素的x、y坐标一律加上0.1,那么实际产生的效果就是那个白色像素向左上角偏移了。红色值偏移也是相似的意思,拿到左上角和右下角的像素的红绿色值之后,跟当前的像素的蓝色色值进行组合,就形成了图片中的效果。
2.3 动画关键代码
private float[] mMvpMatrix = new float[16]; private float mProgress = 0.0f; private int mFrames = 0; private static final int mMaxFrames = 8; private static final int mSkipFrames = 4; @Override protected void onDraw(int textureId, float[] texMatrix) { mProgress = (float) mFrames / mMaxFrames; if (mProgress > 1f) { mProgress = 0f; } mFrames++; if (mFrames > mMaxFrames + mSkipFrames) { mFrames = 0; } float scale = 1.0f + 0.2f * mProgress; Matrix.setIdentityM(mMvpMatrix, 0); //设置放大的百分比 Matrix.scaleM(mMvpMatrix, 0, scale, scale, 1.0f); glUniformMatrix4fv(mMvpMatrixLocation, 1, false, mMvpMatrix, 0); //设置色值偏移的量 float textureCoordOffset = 0.01f * mProgress; glUniform1f(mTextureCoordOffsetLocation, textureCoordOffset); super.onDraw(textureId, texMatrix); }
4.『毛刺』
抖音效果图:
毛刺
我的实现效果图:
毛刺
『毛刺』的效果复原的不是很完整,动画的参数没有调整好。
代码实现
看到这个效果,我们先分析一下,将视频逐帧分析,可以看到以下的截图:
毛刺截图
仔细观察这个图片,我们可以发现,其实毛刺效果就是某一行像素值偏移了一段距离,看着就像是图片被撕裂了,并且这个偏移是随着y轴随机变化的,这样看起来效果更自然,并且观察gif图可以看到,除了撕裂,还有个色值偏移的效果。色值偏移在详情 “抖动” 效果时已经讲过了,那么这里只需处理撕裂效果即可以了。
4.1 顶点着色器
uniform mat4 uTexMatrix;attribute vec2 aPosition;attribute vec4 aTextureCoord;varying vec2 vTextureCoord;uniform mat4 uMvpMatrix;void main(){ gl_Position = uMvpMatrix * vec4(aPosition,0.1,1.0); vTextureCoord = (uTexMatrix * aTextureCoord).xy;}
4.2 片元着色器
#extension GL_OES_EGL_image_external : requireprecision highp float;varying vec2 vTextureCoord;uniform samplerExternalOES uTexture;//这是个二阶向量,x是横向偏移的值,y是阈值uniform vec2 uScanLineJitter;//颜色偏移的值uniform float uColorDrift;//随机函数float nrand(in float x, in float y){ return fract(sin(dot(vec2(x, y), vec2(12.9898, 78.233))) * 43758.5453);}void main(){ float u = vTextureCoord.x; float v = vTextureCoord.y; float jitter = nrand(v,0.0) * 2.0 - 1.0; float drift = uColorDrift; float offsetParam = step(uScanLineJitter.y,abs(jitter)); jitter = jitter * offsetParam * uScanLineJitter.x; vec4 color1 = texture2D(uTexture,fract(vec2( u + jitter,v))); vec4 color2 = texture2D(uTexture,fract(vec2(u + jitter + v*drift ,v))); gl_FragColor = vec4(color1.r,color2.g,color1.b,1.0);}
这里重点讲解下片元着色器的代码,随机函数就是代码中的nrand函数
fract、dot和sin是opengl自带的函数,意思是取某个数的小数部分,即fract(x) = x – floor(x);
dot是向量点乘,sin就是正弦函数
如上代码所示,我们首先取出当前像素的x、y的值,而后使用y去计算随机数
float jitter = nrand(v,0.0) * 2.0 - 1.0;//这里得到一个-1到1的数
而后接下来,我们计算当前这一行的像素要往左偏,还是往右偏
float offsetParam = step(uScanLineJitter.y,abs(jitter));//step是gl自带函数,意思是,假如第一个参数大于第二个参数,那么返回0,否则返回1
所以这句话的意思就是,判断当前的随机数能否大于某个阈值,假如大于这个阈值,那么就偏移,否则就不偏移。通过控制这个阈值,我们可以改变当前视频的混乱度(越混乱,撕裂的像素就越多)
接着是计算某行像素的偏移值
jitter = jitter * offsetParam * uScanLineJitter.x;//offsetParam假如是0,就不便宜了,假如是1,就偏移jitter*uScanLineJitter.x的距离,其中uScanLineJitter.x是最大偏移值//这里计算最终的像素值,纹理坐标是0到1之间的数,假如小于0,那么图像就捅到屏幕右边去,假如超过1,那么就捅到屏幕左边去。vec4 color1 = texture2D(uTexture,fract(vec2( u + jitter,v)));vec4 color2 = texture2D(uTexture,fract(vec2(u + jitter + v*drift ,v)));
4.3 动画代码
动画代码这里就不贴了,大概就是根据当前帧数控制
//这是个二阶向量,x是横向偏移的值,y是阈值uniform vec2 uScanLineJitter;//颜色偏移的值uniform float uColorDrift;
这两个参数的值,uScanLineJitter.x越大,横向撕裂的距离就越大;uScanLineJitter.y越大,屏幕上被撕裂的像素就越多
5.『缩放』
抖音效果图:
缩放
我的实现效果图:
缩放
代码实现
这个效果比较简单,就是放大而后缩小 不停地循环
5.1顶点着色器
uniform mat4 uTexMatrix;attribute vec2 aPosition;attribute vec4 aTextureCoord;varying vec2 vTextureCoord;//缩放矩阵uniform mat4 uMvpMatrix;void main(){ gl_Position = uMvpMatrix * vec4(aPosition,0.1,1.0); vTextureCoord = (uTexMatrix * aTextureCoord).xy;}
5.2片元着色器
#extension GL_OES_EGL_image_external : requireprecision mediump float;varying vec2 vTextureCoord;uniform samplerExternalOES uTexture;void main(){ gl_FragColor = texture2D(uTexture,vTextureCoord);}
5.3动画代码
动画代码比较简单,就是控制缩放矩阵来放大缩小,关键代码如下:
private int mScaleMatrixLocation; //最大缩放是1.3倍 private static final float mScale = 0.3f; private int mFrames; //最大帧数是14帧,通过这个控制动画速度 private int mMaxFrames = 14; private int mMiddleFrames = mMaxFrames / 2; private float[] mScaleMatrix = new float[16]; public void onDraw(int textureId,float texMatrix[]){ //初始化矩阵 Matrix.setIdentityM(mScaleMatrix, 0); float progress; if (mFrames <= mMiddleFrames) { progress = mFrames * 1.0f / mMiddleFrames; } else { progress = 2f - mFrames * 1.0f / mMiddleFrames; } float scale = 1f + mScale * progress; Matrix.scaleM(mScaleMatrix, 0, scale, scale, scale); glUniformMatrix4fv(mScaleMatrixLocation, 1, false, mScaleMatrix, 0); mFrames++; if (mFrames > mMaxFrames) { mFrames = 0; } ... }
6.『闪白』
抖音实现效果图:
闪白
我的实现效果图:
闪白
代码实现
这个效果比较简单,就是个相机过度曝光的感觉,具体实现就是给RGB的每个分量添加一个固定的值。
6.1顶点着色器
uniform mat4 uTexMatrix;attribute vec2 aPosition;attribute vec4 aTextureCoord;varying vec2 vTextureCoord;void main(){ gl_Position = vec4(aPosition,0.1,1.0); vTextureCoord = (uTexMatrix * aTextureCoord).xy;}
6.2片元着色器
#extension GL_OES_EGL_image_external : requireprecision mediump float;varying vec2 vTextureCoord;uniform samplerExternalOES uTexture;//修改这个值,可以控制曝光的程度uniform float uAdditionalColor;void main(){ vec4 color = texture2D(uTexture,vTextureCoord); gl_FragColor = vec4(color.r + uAdditionalColor,color.g + uAdditionalColor,color.b + uAdditionalColor,color.a);}
6.3动画代码
public void onDraw(int textureId,float[] texMatrix){ float progress; if (mFrames <= mHalfFrames) { progress = mFrames * 1.0f / mHalfFrames; } else { progress = 2.0f - mFrames * 1.0f / mHalfFrames; } mFrames++; if (mFrames > mMaxFrames) { mFrames = 0; } glUniform1f(mAdditionColorLocation, progress); ...绘制}
7.『幻觉』
抖音实现效果:
huanjue.gif
我的实现效果:
huanjue1.gif
代码实现
第一次看到这个效果的时候,我是有点懵逼的,由于一点头绪都没有,当时只想把电脑扔了。
throw-away-your-laptop
后来逐帧分析的时候,还是发现了一丝端倪。这个特效大概可以总结为三个部分:
- 滤镜
- 残影
- 残影颜色分离
7.1 滤镜
使用两张图来比照一下,大家大概就知道了
滤镜前
滤镜前
滤镜后
751536631171_.pic.jpg
可以看到,在用了幻觉特效之后,图片有种偏暗蓝的感觉。这种情况下咋整?一般有两种选择,找视觉同学帮你复原,或者者是,反编译apk包搜代码。我选择了后者。在将抖音apk解压之后,搜索资源文件,发现了一张图——lookup_vertigo.png,就是这个东东
lut
这个是啥呢?就是一个颜色查找表,滤镜可以通过代码手动转换颜色或者者把颜色转换信息写在一个lut文件里,而后要使用的时候直接从图片里查找就可。
LUT文件用代码如下:
//这个是LUT文件的纹理uniform sampler2D uTexture2;vec4 lookup(in vec4 textureColor){ mediump float blueColor = textureColor.b * 63.0; mediump vec2 quad1; quad1.y = floor(floor(blueColor) / 8.0); quad1.x = floor(blueColor) - (quad1.y * 8.0); mediump vec2 quad2; quad2.y = floor(ceil(blueColor) / 8.0); quad2.x = ceil(blueColor) - (quad2.y * 8.0); highp vec2 texPos1; texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r); texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g); texPos1.y = 1.0-texPos1.y; highp vec2 texPos2; texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r); texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g); texPos2.y = 1.0-texPos2.y; lowp vec4 newColor1 = texture2D(uTexture2, texPos1); lowp vec4 newColor2 = texture2D(uTexture2, texPos2); lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor)); return newColor;}
将我们的视频帧通过这个lut文件转换之后,就是『幻觉』滤镜的效果了。
在做滤镜的时候碰到了一个问题,就是普通的sampler2D纹理无法和samplerExternalOES纹理共使用,具体情况就是,当在glsl代码中同时存在这两种纹理时,代码是无法正常运行的。那么怎样处理呢?假如只是视频预览,处理的方法比较多,比方用Camera类的previewCallback,拿到每一帧的byte数组(yuv数据)之后,将yuv数据转成rgb,再将rgb转成纹理来显示即可以了。这种方法尽管可行,但是由于需要数据转换,效率比较差。那有没有比较优雅并且高效的处理办法呢?答案是——FBO。
在OpenGL渲染管线中,几何数据和纹理经过屡次转化和屡次测试,最后以二维像素的形式显示在屏幕上。OpenGL管线的最终渲染目的地被称作帧缓存(framebuffer)。帧缓冲是少量二维数组和OpenG所用的存储区的集合:颜色缓存、深度缓存、模板缓存和累计缓存。一般情况下,帧缓存完全由window系统生成和管理,由OpenGL用。这个默认的帧缓存被称作“window系统生成”(window-system-provided)的帧缓存。
在OpenGL扩展中,GL_EXT_framebuffer_object提供了一种创立额外的不能显示的帧缓存对象的接口。为了和默认的“window系统生成”的帧缓存区别,这种帧缓冲成为应使用程序帧缓存(application-createdframebuffer)。通过用帧缓存对象(FBO),OpenGL可以将显示输出到引使用程序帧缓存对象,而不是传统的“window系统生成”帧缓存。而且,它完全受OpenGL控制。
总结来说就是,FBO相当于在内存中创立了一个Canvas,我们可以将这块画布和一个纹理绑定,而后先将内容画到画布上,之后即可以通过纹理对这块画布里的内容为所欲为了。
FBO的用下文会继续说明。
7.2残影
『幻觉』特效最显著的一个效果就是,画面中的物体移动时会有残影,这个如何处理呢?仔细思考一下我们即可以得到答案——保留上一帧的内容,将其透明化,而后和当前帧的内容混合。不断重复这个过程,就会得到残影的效果。那么如何保留上一帧的内容呢?答案还是——FBO。
7.3残影颜色分离
这个可能不好了解,看个截图大家应该就懂了。
残影颜色分离
可以看到,截图中的那支笔的残影是七彩的。
这个如何处理呢?我们在将当前帧和上一帧内容混合时,一定是操作每一个像素点的RGB分量的,那么这个七彩色应该就是从这里入手,一定有一个混合公式
vec4 currentFrame;vec4 lastFrame;gl_FragColor = vec4(a1 * currentFrame.r + a2 * lastFrame.r,b1 * currentFrame.g + b2 * lastFrame.g,c1 * currentFrame.b + c2 * lastFrame.b,1.0);
我们要做的就是把这个公式里的a,b,c值给算出来。那么如何计算呢?这里有个小窍门,我们假定currentFrame的rgb值都是0,lastFrame的rgb都是1。你可能会问,这是什么马叉虫操作呢?我们让上一帧是黑色的,这一帧是白色的即可以啦。废话不多说,看图。
我们找个黑色的背景,白色的物体——黑色鼠标垫和纸巾,效果大概如下图所示:
颜色分离效果图
我们逐帧分析,很快就能算出我们想要的结果。
首先我们看前面三帧
逐帧分析1
可以看到,当纸巾向下移动时,露出来的部分是蓝色的(当前帧是白色,上一帧是黑色),而上面的部分是橙色的(此时上一帧是白色的,当前帧是黑色的),那么从这里我们得出一个结论就是,c1=1,c2 = 0,由于橙色的部分蓝色色值是0。
再看后面几帧
逐帧分析1
可以看到,最顶上的那个残影,最终变得特别的红,那么我们可以知道,a1是一个接近0的数,而a2是一个十分接近1的数,为什么不能是1呢?由于假如是1,那么lastFrame的色值就会一直保留了,并不会随着帧数添加逐步变淡消失。
得出a和c的值以后,b的值我们大概猜测一下,试几个数字之后就能得到我们的结果了。最终得出的公式如下:
gl_FragColor = vec4(0.95 * lastFrame.r + 0.05* currentFrame.r,currentFrame.g * 0.2 + lastFrame.g * 0.8, currentFrame.b,1.0);
这个公式的效果已经十分接近了。
7.4关键代码
private RenderBuffer mRenderBuffer; private RenderBuffer mRenderBuffer2; private RenderBuffer mRenderBuffer3; private int mLutTexture; //当前帧 private int mCurrentFrameProgram; //上一帧 private int mLastFrameProgram; private boolean mFirst = true; @Override public void draw(int textureId, float[] texMatrix, int canvasWidth, int canvasHeight) { if (mRenderBuffer == null) { mRenderBuffer = new RenderBuffer(GL_TEXTURE8, canvasWidth, canvasHeight); mRenderBuffer2 = new RenderBuffer(GL_TEXTURE9, canvasWidth, canvasHeight); mRenderBuffer3 = new RenderBuffer(GL_TEXTURE10, canvasWidth, canvasHeight); mLastFrameProgram = GLUtils.buildProgram(FileUtils.readFromRaw(R.raw.vertex_common), FileUtils.readFromRaw(R.raw.fragment_common)); mCurrentFrameProgram = GLUtils.buildProgram(FileUtils.readFromRaw(R.raw.vertex_common), FileUtils.readFromRaw(R.raw.fragment_current_frame)); mLutTexture = GLUtils.genLutTexture(); android.opengl.GLUtils.texImage2D(GL_TEXTURE_2D, 0, BitmapFactory.decodeResource(AppProfile.getContext().getResources(), R.raw.lookup_vertigo), 0); } mRenderBuffer.bind(); //这里用samplerExternalOES纹理将当前的视频内容绘制到缓存中 super.draw(textureId, texMatrix, canvasWidth, canvasHeight); mRenderBuffer.unbind(); //绘制当前帧 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); drawCurrentFrame(); //将当前帧的内容保存到缓存中 mRenderBuffer3.bind(); drawCurrentFrame(); mRenderBuffer3.unbind(); //只使用两个buffer的话,屏幕中会有黑格子 //把缓存3中的内容画到缓存2中,缓存2中的内容在下一帧会使用到 mRenderBuffer2.bind(); drawToBuffer(); mRenderBuffer2.unbind(); mFrames++; mFirst = false; } private void drawCurrentFrame() { glUseProgram(mCurrentFrameProgram); int textureId = mRenderBuffer.getTextureId(); setup(mCurrentFrameProgram, new int[]{textureId, mFirst ? textureId : mRenderBuffer2.getTextureId(), mLutTexture}); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); } private void drawToBuffer() { glUseProgram(mLastFrameProgram); setup(mLastFrameProgram, new int[]{mRenderBuffer3.getTextureId()}); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); } private void setup(int programId, int[] textureId) { glUseProgram(programId); int aPositionLocation = glGetAttribLocation(programId, "aPosition"); int aTexCoordLocation = glGetAttribLocation(programId, "aTextureCoord"); mRendererInfo.getVertexBuffer().position(0); glEnableVertexAttribArray(aPositionLocation); glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT, false, 0, mRendererInfo.getVertexBuffer()); mRendererInfo.getTextureBuffer().position(0); glEnableVertexAttribArray(aTexCoordLocation); glVertexAttribPointer(aTexCoordLocation, 2, GL_FLOAT, false, 0, mRendererInfo.getTextureBuffer()); for (int i = 0; i < textureId.length; i++) { int textureLocation = glGetUniformLocation(programId, "uTexture" + i); glActiveTexture(GL_TEXTURE0 + i); glBindTexture(GLES20.GL_TEXTURE_2D, textureId[i]); glUniform1i(textureLocation, i); } }
帧缓存代码
public class RenderBuffer { private int mTextureId; private int mActiveTextureUnit; private int mRenderBufferId; private int mFrameBufferId; private int mWidth, mHeight; public RenderBuffer(int activeTextureUnit, int width, int height) { this.mActiveTextureUnit = activeTextureUnit; this.mWidth = width; this.mHeight = height; int[] buffer = new int[1]; GLES20.glActiveTexture(activeTextureUnit); mTextureId = GLUtils.genTexture(); IntBuffer texBuffer = ByteBuffer.allocateDirect(width * height * 4).order(ByteOrder.nativeOrder()).asIntBuffer(); GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, texBuffer); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE); // Generate frame buffer GLES20.glGenFramebuffers(1, buffer, 0); mFrameBufferId = buffer[0]; // Bind frame buffer GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBufferId); // Generate render buffer GLES20.glGenRenderbuffers(1, buffer, 0); mRenderBufferId = buffer[0]; // Bind render buffer GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, mRenderBufferId); GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, width, height); } public void bind() { GLES20.glViewport(0, 0, mWidth, mHeight); checkGlError("glViewport"); GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBufferId); checkGlError("glBindFramebuffer"); GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, mTextureId, 0); checkGlError("glFramebufferTexture2D"); GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, mRenderBufferId); checkGlError("glFramebufferRenderbuffer"); } public void unbind() { GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); } public int getTextureId(){ return mTextureId; }}
着色器代码
precision mediump float;varying vec2 vTextureCoord;uniform sampler2D uTexture0;uniform sampler2D uTexture1;uniform sampler2D uTexture2;vec4 lookup(in vec4 textureColor){ mediump float blueColor = textureColor.b * 63.0; mediump vec2 quad1; quad1.y = floor(floor(blueColor) / 8.0); quad1.x = floor(blueColor) - (quad1.y * 8.0); mediump vec2 quad2; quad2.y = floor(ceil(blueColor) / 8.0); quad2.x = ceil(blueColor) - (quad2.y * 8.0); highp vec2 texPos1; texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r); texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g); texPos1.y = 1.0-texPos1.y; highp vec2 texPos2; texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r); texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g); texPos2.y = 1.0-texPos2.y; lowp vec4 newColor1 = texture2D(uTexture2, texPos1); lowp vec4 newColor2 = texture2D(uTexture2, texPos2); lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor)); return newColor;}void main(){ vec4 lastFrame = texture2D(uTexture1,vTextureCoord); vec4 currentFrame = lookup(texture2D(uTexture0,vTextureCoord)); gl_FragColor = vec4(0.95 * lastFrame.r + 0.05* currentFrame.r,currentFrame.g * 0.2 + lastFrame.g * 0.8, currentFrame.b,1.0);}
总结
抖音的特效大概就是这样了,假如要对视频进行后期解决的话,我们只要要记住每个特效开始的时间和结束的时间,而后在后端对每一帧进行解决,最终保存到一个新的视频文件里就可,这个其实跟录制是差不多的,就是一个离屏渲染的操作。
小伙伴们觉得这篇文章对你们有帮助的话,欢迎点赞噢,觉得文章有不足之处的话,欢迎大佬们指出,谢谢啦!
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 当一个 Android 开发玩抖音玩疯了之后(二)