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


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

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

上節(jié)原理分析時(shí),將圖像區(qū)域從左側(cè)到右側(cè)拷貝并不高效,可能會(huì)導(dǎo)致一些性能問(wèn)題,好在 Android 相機(jī)出圖都是橫向的(旋轉(zhuǎn)了 90 或 270 度),這樣圖像區(qū)域上下拷貝效率高了很多,最后渲染的時(shí)候再將圖像旋轉(zhuǎn)回來(lái)。
Android 相機(jī)出圖是 YUV 格式的,這里為了拷貝處理方便,先使用 OpenCV 將 YUV 圖像轉(zhuǎn)換為 RGBA 格式,當(dāng)然為了追求性能直接使用 YUV 格式的圖像問(wèn)題也不大。
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);
用到的著色器程序就是簡(jiǎn)單的貼圖:
#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;//一個(gè) banner 的高(小豎條)
int bannerPixelsBufSize = m_RenderImage.width * bannerHeight * 4;//一個(gè) banner 占用的圖像內(nèi)存
uint8 *pBuf = m_RenderImage.ppPlane[0] + m_RenderImage.width * m_RenderImage.height * 4 / 2; //傳送帶分界線
//從最右側(cè)的豎條圖像區(qū)域開(kāi)始拷貝圖像
for (int i = m_bannerNum - 1; i >= 1; --i) {
memcpy(pBuf + i*bannerPixelsBufSize, pBuf + (i - 1)*bannerPixelsBufSize, bannerPixelsBufSize);
}
//將來(lái)源區(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)代碼見(jiàn)項(xiàng)目:github.com/githubhaoha…
到此這篇關(guān)于Android OpenGL ES 實(shí)現(xiàn)抖音傳送帶特效的文章就介紹到這了,更多相關(guān)Android 抖音傳送帶特效內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android使用DatePickerDialog顯示時(shí)間
本文將結(jié)合實(shí)例代碼,介紹Android使用DatePickerDialog顯示時(shí)間,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07
Android Studio preview 不固定及常見(jiàn)問(wèn)題的解決辦法
preview 可以幫助您預(yù)覽您的布局文件將如何在用戶的設(shè)備上呈現(xiàn)。這篇文章主要介紹了Android Studio preview 不固定及常見(jiàn)問(wèn)題的解決辦法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
Android自定義實(shí)現(xiàn)圖片加文字功能
這篇文章主要介紹了Android自定義實(shí)現(xiàn)圖片加文字功能的相關(guān)資料,需要的朋友可以參考下2017-05-05
Android調(diào)用系統(tǒng)拍照裁剪圖片模糊的解決方法
這篇文章主要為大家詳細(xì)介紹了Android調(diào)用系統(tǒng)拍照裁剪圖片模糊的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01
Android通過(guò)自定義view實(shí)現(xiàn)刮刮樂(lè)效果詳解
這篇文章主要介紹了如何在Android中利用自定義的view實(shí)現(xiàn)刮刮樂(lè)的效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟上小編一起動(dòng)手試一試2022-03-03
Android設(shè)備藍(lán)牙連接掃描槍獲取掃描內(nèi)容
這篇文章主要為大家詳細(xì)介紹了Android設(shè)備藍(lán)牙連接掃描槍獲取掃描內(nèi)容,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
android 仿微信demo——登錄功能實(shí)現(xiàn)(移動(dòng)端)
本篇文章主要介紹了微信小程序-閱讀小程序?qū)嵗╠emo),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望能給你們提供幫助2021-06-06

