Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >抖音传送带特效是怎么实现的?

抖音传送带特效是怎么实现的?

作者头像
字节流动
发布于 2021-09-07 07:11:23
发布于 2021-09-07 07:11:23
83100
代码可运行
举报
文章被收录于专栏:字节流动字节流动
运行总次数:0
代码可运行

抖音 APP 真是个好东西,不过也容易上瘾,老实说你的抖音是不是反复卸载又反复安装了,后来我也发现我的几个 leader 都不刷抖音,这令我挺吃惊的。

我刷抖音主要是为了看新闻,听一些大 V 讲历史,研究抖音的一些算法特效,最重要的是抖音提供了一个年轻人的视角去观察世界。

另外,自己感兴趣的内容看多了,反而训练抖音推送更多类似的优质内容,大家可以反向利用抖音的这一特点。

至于我的 leader 老是强调刷抖音不好,对此我并不完全认同。

抖音传送带特效原理

实现抖音传送带特效

抖音传送带特效推出已经很长一段时间了,前面也实现了下,最近把它整理出来了,如果你有仔细观测传送带特效,就会发现它的实现原理其实很简单。

抖音传送带特效原理

通过仔细观察抖音的传送带特效,你可以发现左侧是不停地更新预览画面,右侧看起来就是一小格一小格的竖条状图像区域不断地向右移动,一直移动到右侧边界位置。

预览的时候每次拷贝一小块预览区域的图像送到传送带,这就形成了源源不断地向右传送的效果。

原理图进行了简化处理, 实际上右侧的竖条图像更多,效果会更流畅,每来一帧预览图像,首先拷贝更新左侧预览画面,然后从最右侧的竖条图像区域开始拷贝图像(想一想为什么?)。

例如将区域 2 的像素拷贝到区域 3 ,然后将区域 1 的像素拷贝到区域 2,以此类推,最后将来源区域的像素拷贝到区域 0 。

这样就形成了不断传送的效果,最后将拷贝好的图像更新到纹理,利用 OpenGL 渲染到屏幕上。

抖音传送带特效实现

抖音传送带特效实现

上节原理分析时,将图像区域从左侧到右侧拷贝并不高效,可能会导致一些性能问题,好在 Android 相机出图都是横向的(旋转了 90 或 270 度),这样图像区域上下拷贝效率高了很多,最后渲染的时候再将图像旋转回来。

Android 相机出图是 YUV 格式的,这里为了拷贝处理方便,先使用 OpenCV 将 YUV 图像转换为 RGBA 格式,当然为了追求性能直接使用 YUV 格式的图像问题也不大。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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);

用到的着色器程序就是简单的贴图:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#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);
}

传送带的核心就是图像拷贝操作:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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;//一个 banner 的高(小竖条)
int bannerPixelsBufSize = m_RenderImage.width * bannerHeight * 4;//一个 banner 占用的图像内存

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);
}

//将来源区域的像素拷贝到竖条图像区域 0
memcpy(pBuf, pBuf - bannerPixelsBufSize, bannerPixelsBufSize);

渲染操作:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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);

详细实现代码见项目:https://github.com/githubhaohao/OpenGLCamera2

-- END --

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-08-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 字节流动 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
NDK OpenGLES 3.0 开发(三):YUV 渲染
前面文章一文掌握 YUV 图像的基本处理介绍了 YUV 常用的基本格式,本文以实现 NV21/NV12 的渲染为例。
字节流动
2020/06/03
2K0
FFmpeg 播放器视频渲染优化
前文中,我们已经利用 FFmpeg + OpenGLES + OpenSLES 实现了一个多媒体播放器,本文将在视频渲染方面对播放器进行优化。
字节流动
2020/09/22
3.3K0
NDK OpenGLES 3.0 开发(二):纹理映射
现实生活中,纹理(Texture)最通常的作用是装饰 3D 物体,它就像贴纸一样贴在物体表面,丰富了物体的表面和细节。
字节流动
2020/06/03
1.2K0
OpenGL ES 如何渲染 16bit 图像(P010)?
可以类比,10bit YUV 就是每个 Y、U、V 分量分别占用 10 个 bit ,但是实际处理中,我们是以字节为单位进行存储和处理的,所以最终处理的数据是以 2 个字节来存储 10bit 的有效数据。
字节流动
2024/01/02
9440
OpenGL ES 如何渲染 16bit 图像(P010)?
OpenGL 使用 Shader 实现 RGBA 转 I420(附项目源码)
I420 格式的图像在视频解码中比较常见,像前面文章中提到的,在工程中一般会选择使用 Shader 将 RGBA 转 YUV,这样再使用 glReadPixels 读取图像时可以有效降低传输数据量,提升性能,并且兼容性好。
字节流动
2021/11/22
1.3K1
OpenGL 使用 Shader 实现 RGBA 转 I420(附项目源码)
FFmpeg + OpenGLES 实现视频解码播放和视频滤镜
前面 Android FFmpeg 开发系列文章中,我们已经利用 FFmpeg 的解码功能和 ANativeWindow 的渲染功能,实现了的视频的解码播放。
字节流动
2020/08/20
3.1K0
FFmpeg + OpenGLES 实现视频解码播放和视频滤镜
OpenGL 使用 Shader 实现 RGBA 转 I420(附项目源码)
I420 格式的图像在视频解码中比较常见,像前面文章中提到的,在工程中一般会选择使用 Shader 将 RGBA 转 YUV,这样再使用 glReadPixels 读取图像时可以有效降低传输数据量,提升性能,并且兼容性好。
字节流动
2021/11/26
9950
使用 OpenGL 实现 RGB 到 YUV 的图像格式转换
最近,有位读者大人在后台反馈:在参加一场面试的时候,面试官要求他用 shader 实现图像格式 RGB 转 YUV ,他听了之后一脸懵,然后悻悻地对面试官说,他只用 shader 做过 YUV 转 RGB,不知道 RGB 转 YUV 是个什么思路。
字节流动
2021/05/27
7.9K0
使用 OpenGL 实现 RGB 到 YUV 的图像格式转换
OpenGL: 如何利用 Shader 实现 RGBA 到 NV21 图像格式转换?(全网首次开源)
之前写过一篇 OpenGL 使用 shader 实现 RGBA 转 YUYV 的文章,有几位读者大人在后台建议写一篇 shader 实现 RGBA 转 NV21 的文章,因为在实践中 NV21 格式用的比较多,于是我今天把这篇文章放出来。
字节流动
2021/11/01
2.8K0
OpenGL: 如何利用 Shader 实现 RGBA 到 NV21 图像格式转换?(全网首次开源)
NDK OpenGLES3.0 开发(五):FBO 离屏渲染
FBO(Frame Buffer Object)即帧缓冲区对象,实际上是一个可添加缓冲区的容器,可以为其添加纹理或渲染缓冲区对象(RBO)。
字节流动
2020/06/03
2.2K0
Android 实现抖音传送带特效!
其实在介绍抖音蓝线挑战特效那一章已经将到一个核心知识点Fbo,对,没错,当时做蓝线挑战特效用到的就是Fbo,接下来传送带特效也需要使用Fbo的保留上一帧功能
程序员小顾
2021/12/13
6910
NDK OpenGL ES 3.0 开发(二十二):PBO
OpenGL PBO(Pixel Buffer Object),被称为像素缓冲区对象,主要被用于异步像素传输操作。PBO 仅用于执行像素传输,不连接到纹理,且与 FBO (帧缓冲区对象)无关。
字节流动
2020/06/01
2.9K0
抖音“传送带”特效性能问题终极解决方案
通过仔细观察抖音的传送带特效,你可以发现左侧是不停地更新预览画面,右侧看起来就是一小格一小格的竖条状图像区域不断地向右移动,一直移动到右侧边界位置。
字节流动
2023/12/14
3040
抖音“传送带”特效性能问题终极解决方案
OpenGL ES 多目标渲染(MRT)
OpenGL ES 多目标渲染(MRT),即多重渲染目标,是 OpenGL ES 3.0 新特性,它允许应用程序一次渲染到多个缓冲区。
字节流动
2020/10/12
3.1K0
OpenGL ES 帧缓冲区位块传送
前文 《OpenGL ES 多目标渲染(MRT)》中我们了解了利用 MRT 技术可以一次渲染到多个缓冲区,本文将利用帧缓冲区位块传送实现高性能缓冲区之间的像素拷贝。
字节流动
2020/10/12
1.6K0
OpenGL 帧缓冲区位块传送,不得了(附源码)
如果你觉得本文内容有些超纲,可以先看看 OpenGL ES 干货汇总 系列补补基础。
字节流动
2022/09/26
1.2K0
FFmpeg 视频录制 - 视频添加滤镜和编码
音视频开发中,视频编码是另一个重要的部分,基于 FFmpeg 软件解码前面系列文章已经介绍过了,接下来主要介绍软件编码这一块,包括视频编码、音频编码、为视频添加滤镜等。后期文章安排将介绍 Android MediaCodec 硬件编解码。
字节流动
2021/03/09
2.1K0
FFmpeg 视频录制 - 视频添加滤镜和编码
NDK OpenGLES3.0 开发(八):坐标系统
我们知道 OpenGL 坐标系中每个顶点的 x,y,z 坐标都应该在 -1.0 到 1.0 之间,超出这个坐标范围的顶点都将不可见。
字节流动
2020/06/03
1.6K0
ArkUI框架开发——图片模糊处理的实现
现在市面上有很多APP,都或多或少对图片有模糊上的设计,所以,图片模糊效果到底怎么实现的呢?
小帅聊鸿蒙
2025/04/29
1890
ArkUI框架开发——图片模糊处理的实现
NDK OpenGL ES 3.0 开发(十六):相机预览
相机开发是 OpenGL ES 开发的重要应用,利用 OpenGL 可以很方便地实现相机美颜、滤镜、塑型以及一些动态特效,其性能显著优于对应功能的 CPU 实现。
字节流动
2020/06/02
2.9K0
推荐阅读
相关推荐
NDK OpenGLES 3.0 开发(三):YUV 渲染
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验