抖音“蓝线挑战”特效是怎么实现的

共 4404字,需浏览 9分钟

 ·

2021-09-30 22:14

前面文章中我们实现了抖音的“传送带”特效,发现大家普遍对抖音视频特效感兴趣,那么本文将继续这个抖音特效实现系列,后面也将带来更多的关于抖音视频特效分析和实现的文章。



本文带给大家的是抖音的经典视频特效“蓝线挑战”实现,为啥选择“蓝线挑战”?


之前这个特效没太在意,知道看到老罗直播团队用这个特效拍了一个短视频,发现挺有意思的。



老罗直播团队视频展示


本文“蓝线挑战”特效实现

抖音“蓝线挑战”特效原理

之前“蓝线挑战”特效有人实现过它的变种,但是看起来搞的有点复杂了,本文的实现思路就非常简单,抖音“蓝线挑战”和“传送带”实现原理基本相同:每次更新预览帧图像的特定区域进行渲染。


抖音“蓝线挑战”特效实现可以直接基于前文“传送带”特效的 demo 修改几行代码即可,其实就是图像区域的拷贝方式有所变化。



原理如上图所示,蓝线从左侧向右侧移动(每帧移动一定的像素),蓝线左侧区域像素保存不变,蓝线右侧区域像素不断刷新,每次都更新为当前预览帧相应区域的像素

抖音“蓝线挑战”特效实现

上节原理分析时,将当前预览帧相应区域的像素不断地拷贝到蓝线右侧区域,这样需要进行很多次拷贝操作,并不高效,可能会在低端机上导致一些性能问题。


好在 Android 相机出图都是横向的(旋转了 90 或 270 度),这样图像区域只需要一次拷贝即可完成,最后渲染的时候利用 OpenGL 变换矩再将图像旋转回来。


Android 相机出图是 YUV 格式的,这里为了拷贝处理方便,先使用 OpenCV 将 YUV 图像转换为 RGBA 格式,当然为了追求性能直接使用 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;
uniform float u_offset;/
/归一化后,蓝线的位置

void main()
{

    if (v_texCoord.y >= u_offset - 0.004 && v_texCoord.y <= u_offset + 0.004) {
       
 //对靠近蓝线的位置进行采样时,绘制蓝线
        outColor = vec4(0.00.01.01.0);
    }
    else {
        outColor = texture(u_texture, v_texCoord);
    }
}


将当前预览帧相应区域的像素,不断地拷贝到蓝线右侧区域实现代码:


m_frameIndex = m_frameIndex % m_bannerNum;
float offset = m_frameIndex * 1.0f / m_bannerNum; //归一化后,蓝线的位置
int pixelOffset = (m_bannerNum - m_frameIndex) * BF_BANNER_WIDTH; //根据蓝线位置计算蓝线右侧区域要拷贝的像素数量

memcpy(m_RenderImage.ppPlane[0], m_SrcImage.ppPlane[0], m_RenderImage.width * pixelOffset * 4); //拷贝到蓝线右侧区域


将图像从左向右划分很多竖条,竖条宽度为 BF_BANNER_WIDTH 像素,即蓝线每次移动 BF_BANNER_WIDTH 像素;


竖条数量为 m_bannerNum ,m_frameIndex 为绘制次数,m_SrcImage 为当前预览帧,m_RenderImage 为当前要渲染的图像。


渲染操作:


glUseProgram (m_ProgramObj);

glBindVertexArray(m_VaoId);

glUniformMatrix4fv(m_MVPMatLoc, 1, GL_FALSE, &m_MVPMatrix[0][0]);

//将当前预览帧相应区域的像素拷贝到蓝线右侧区域
m_frameIndex = m_frameIndex % m_bannerNum;
float offset = m_frameIndex * 1.0f / m_bannerNum;
int pixelOffset = (m_bannerNum - m_frameIndex) * BF_BANNER_HEIGHT;
LOGCATE("BluelineChallengeExample::Draw[offset, pixelOffset]=[%.4f, %d]", offset, pixelOffset);
memcpy(m_RenderImage.ppPlane[0], m_SrcImage.ppPlane[0], m_RenderImage.width * pixelOffset * 4);


//更新纹理
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);

//更新蓝线位置
GLUtils::setFloat(m_ProgramObj, "u_offset", offset);

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 --


进技术交流群,扫码添加我的微信:Byte-Flow



获取视频教程和源码



推荐:

Android FFmpeg 实现带滤镜的微信小视频录制功能

全网最全的 Android 音视频和 OpenGL ES 干货,都在这了

一文掌握 YUV 图像的基本处理

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

所有你想要的图片转场效果,都在这了

浏览 225
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报