Android OpenGL ES 實(shí)現(xiàn)抖音傳送帶特效(原理解析)
抖音 APP 真是個好東西,不過也容易上癮,老實(shí)說你的抖音是不是反復(fù)卸載又反復(fù)安裝了,后來我也發(fā)現(xiàn)我的幾個 leader 都不刷抖音,這令我挺吃驚的。
我刷抖音主要是為了看新聞,聽一些大 V 講歷史,研究抖音的一些算法特效,最重要的是抖音提供了一個年輕人的視角去觀察世界。另外,自己感興趣的內(nèi)容看多了,反而訓(xùn)練抖音推送更多類似的優(yōu)質(zhì)內(nèi)容,大家可以反向利用抖音的這一特點(diǎn)。
至于我的 leader 老是強(qiáng)調(diào)刷抖音不好,對此我并不完全認(rèn)同。


抖音傳送帶特效原理
抖音傳送帶特效推出已經(jīng)很長一段時間了,前面也實(shí)現(xiàn)了下,最近把它整理出來了,如果你有仔細(xì)觀測傳送帶特效,就會發(fā)現(xiàn)它的實(shí)現(xiàn)原理其實(shí)很簡單。

通過仔細(xì)觀察抖音的傳送帶特效,你可以發(fā)現(xiàn)左側(cè)是不停地更新預(yù)覽畫面,右側(cè)看起來就是一小格一小格的豎條狀圖像區(qū)域不斷地向右移動,一直移動到右側(cè)邊界位置。
預(yù)覽的時候每次拷貝一小塊預(yù)覽區(qū)域的圖像送到傳送帶,這就形成了源源不斷地向右傳送的效果。
原理圖進(jìn)行了簡化處理, 實(shí)際上右側(cè)的豎條圖像更多,效果會更流暢,每來一幀預(yù)覽圖像,首先拷貝更新左側(cè)預(yù)覽畫面,然后從最右側(cè)的豎條圖像區(qū)域開始拷貝圖像(想一想為什么?)。
例如將區(qū)域 2 的像素拷貝到區(qū)域 3 ,然后將區(qū)域 1 的像素拷貝到區(qū)域 2,以此類推,最后將來源區(qū)域的像素拷貝到區(qū)域 0 。
這樣就形成了不斷傳送的效果,最后將拷貝好的圖像更新到紋理,利用 OpenGL 渲染到屏幕上。
抖音傳送帶特效實(shí)現(xiàn)

上節(jié)原理分析時,將圖像區(qū)域從左側(cè)到右側(cè)拷貝并不高效,可能會導(dǎo)致一些性能問題,好在 Android 相機(jī)出圖都是橫向的(旋轉(zhuǎn)了 90 或 270 度),這樣圖像區(qū)域上下拷貝效率高了很多,最后渲染的時候再將圖像旋轉(zhuǎn)回來。
Android 相機(jī)出圖是 YUV 格式的,這里為了拷貝處理方便,先使用 OpenCV 將 YUV 圖像轉(zhuǎn)換為 RGBA 格式,當(dāng)然為了追求性能直接使用 YUV 格式的圖像問題也不大。
cv::Mat mati420 = cv::Mat(pImage->height * 3 / 2, pImage->width, CV_8UC1, pImage->ppPlane[0]); cv::Mat matRgba = cv::Mat(m_SrcImage.height, m_SrcImage.width, CV_8UC4, m_SrcImage.ppPlane[0]); cv::cvtColor(mati420, matRgba, CV_YUV2RGBA_I420);
用到的著色器程序就是簡單的貼圖:
#version 300 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_texCoord;
uniform mat4 u_MVPMatrix;
out vec2 v_texCoord;
void main()
{
gl_Position = u_MVPMatrix * a_position;
v_texCoord = a_texCoord;
}
#version 300 es
precision mediump float;
in vec2 v_texCoord;
layout(location = 0) out vec4 outColor;
uniform sampler2D u_texture;
void main()
{
outColor = texture(u_texture, v_texCoord);
}傳送帶的核心就是圖像拷貝操作:
memcpy(m_RenderImage.ppPlane[0], m_SrcImage.ppPlane[0], m_RenderImage.width * m_RenderImage.height * 4 / 2); //左側(cè)預(yù)覽區(qū)域像素拷貝
int bannerHeight = m_RenderImage.height / 2 / m_bannerNum;//一個 banner 的高(小豎條)
int bannerPixelsBufSize = m_RenderImage.width * bannerHeight * 4;//一個 banner 占用的圖像內(nèi)存
uint8 *pBuf = m_RenderImage.ppPlane[0] + m_RenderImage.width * m_RenderImage.height * 4 / 2; //傳送帶分界線
//從最右側(cè)的豎條圖像區(qū)域開始拷貝圖像
for (int i = m_bannerNum - 1; i >= 1; --i) {
memcpy(pBuf + i*bannerPixelsBufSize, pBuf + (i - 1)*bannerPixelsBufSize, bannerPixelsBufSize);
}
//將來源區(qū)域的像素拷貝到豎條圖像區(qū)域 0
memcpy(pBuf, pBuf - bannerPixelsBufSize, bannerPixelsBufSize);渲染操作:
glUseProgram (m_ProgramObj);
glBindVertexArray(m_VaoId);
glUniformMatrix4fv(m_MVPMatLoc, 1, GL_FALSE, &m_MVPMatrix[0][0]);
//圖像拷貝,傳送帶拷貝
memcpy(m_RenderImage.ppPlane[0], m_SrcImage.ppPlane[0], m_RenderImage.width * m_RenderImage.height * 4 / 2);
int bannerHeight = m_RenderImage.height / 2 / m_bannerNum;
int bannerPixelsBufSize = m_RenderImage.width * bannerHeight * 4;
uint8 *pBuf = m_RenderImage.ppPlane[0] + m_RenderImage.width * m_RenderImage.height * 4 / 2; //傳送帶分界線
for (int i = m_bannerNum - 1; i >= 1; --i) {
memcpy(pBuf + i*bannerPixelsBufSize, pBuf + (i - 1)*bannerPixelsBufSize, bannerPixelsBufSize);
}
memcpy(pBuf, pBuf - bannerPixelsBufSize, bannerPixelsBufSize);
//更新紋理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_TextureId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_RenderImage.width, m_RenderImage.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_RenderImage.ppPlane[0]);
glBindTexture(GL_TEXTURE_2D, GL_NONE);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_TextureId);
GLUtils::setInt(m_ProgramObj, "u_texture", 0);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0);
glBindVertexArray(GL_NONE);詳細(xì)實(shí)現(xiàn)代碼見項(xiàng)目:github.com/githubhaoha…
到此這篇關(guān)于Android OpenGL ES 實(shí)現(xiàn)抖音傳送帶特效的文章就介紹到這了,更多相關(guān)Android 抖音傳送帶特效內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android自定義HorizontalScrollView實(shí)現(xiàn)qq側(cè)滑菜單
本文主要介紹了android自定義HorizontalScrollView實(shí)現(xiàn)qq側(cè)滑菜單的相關(guān)知識。具有很好的參考價值。下面跟著小編一起來看下吧2017-04-04
詳解Android自定義控件屬性TypedArray以及attrs
這篇文章主要為大家介紹了android自定義控件屬性TypedArray以及attrs,感興趣的小伙伴們可以參考一下2016-01-01
Android shape和selector 結(jié)合使用實(shí)例代碼
本篇文章主要介紹了Android shape和selector 的使用,這里提供了shape 和selector 的詳細(xì)介紹,并附有代碼實(shí)例,有興趣的朋友可以參考下2016-07-07
教你制作Android中炫酷的ViewPagerIndicator(不僅仿MIUI)
ViewPagerIndicator作為一款分頁指標(biāo)小部件兼容ViewPager,封裝上做得非常不錯,目前已為眾多知名應(yīng)用所使用。今天給你大家分享一個炫酷的效果,有需要的可以參考學(xué)習(xí)。2016-08-08
Android實(shí)現(xiàn)TCP客戶端接收數(shù)據(jù)的方法
這篇文章主要介紹了Android實(shí)現(xiàn)TCP客戶端接收數(shù)據(jù)的方法,較為詳細(xì)的分析了Android基于TCP實(shí)現(xiàn)客戶端接收數(shù)據(jù)的相關(guān)技巧與注意事項(xiàng),需要的朋友可以參考下2016-04-04

