In the previous article, we realized the special effects of Douyin conveyor belt, and found that people are generally interested in the special effects of Douyin video. Therefore, this article will continue this series of special effects of Douyin and bring more articles on the analysis and implementation of special effects of Douyin video.
This article brings you the realization of douyin’s classic video effects “Blue Line Challenge”. Why do you choose “Blue Line Challenge”?
I didn’t care much about this special effect before, but I found it interesting when I saw Lao Luo’s team shoot a short video with this special effect.
Tik Tok “Blue Line Challenge” special effects principle
“Blue Line Challenge” has been implemented as a variant before, but it seems to be a bit complicated. In this paper, the implementation idea is very simple. The implementation principle of “Blue Line Challenge” and “Conveyor Belt” is basically the same: each time the preview frame is updated, a specific area of the image is rendered.
Douyin “Blue Line challenge” special effects can be directly based on the above “conveyor belt” special effects demo to modify a few lines of code, in fact, is the image area copy mode has changed.
The principle is shown in the figure above. The blue line moves from left to right (by a certain number of pixels per frame). The pixels to the left of the blue line are kept unchanged, and the pixels to the right of the blue line are constantly updated, each time to the corresponding pixels of the current preview frame.
Douyin “Blue Line Challenge” special effects achieved
In the principle analysis in the previous section, the pixels in the corresponding area of the current preview frame are constantly copied to the area to the right of the blue line. This requires many copying operations, which is not efficient and may cause some performance problems on low-end computers.
The good thing about the Android camera is that the images are rendered horizontally (rotated by 90 or 270 degrees) so that the image area can be copied once and then rotated back using OpenGL moments during the final rendering.
Android camera map is YUV format, here for the convenience of copy processing, first use OpenCV YUV image conversion to RGBA format, of course, in pursuit of performance directly using YUV format image problem is not big.
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);
Copy the code
The shader used is a simple map:
// Vertex shader
#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(a)
{
gl_Position = u_MVPMatrix * a_position;
v_texCoord = a_texCoord;
}
// Fragment shader
#version 300 es
precision mediump float;
in vec2 v_texCoord;
layout(location = 0) out vec4 outColor;
uniform sampler2D u_texture;
uniform float u_offset;// The normalized position of the blue line
void main(a)
{
if (v_texCoord.y >= u_offset - 0.004 && v_texCoord.y <= u_offset + 0.004) {
// Draw the blue line when sampling positions close to the blue line
outColor = vec4(0.0.0.0.1.0.1.0);
}
else{ outColor = texture(u_texture, v_texCoord); }}Copy the code
Continuously copy the pixels of the corresponding area of the current preview frame to the area to the right of the blue line
m_frameIndex = m_frameIndex % m_bannerNum;
float offset = m_frameIndex * 1.0 f / m_bannerNum; // The normalized position of the blue line
int pixelOffset = (m_bannerNum - m_frameIndex) * BF_BANNER_WIDTH; // Count the number of pixels to copy in the area to the right of the blue line
memcpy(m_RenderImage.ppPlane[0], m_SrcImage.ppPlane[0], m_RenderImage.width * pixelOffset * 4); // Copy to the area right of the blue line
Copy the code
Divide the image from left to right into many vertical bars with the width of BF_BANNER_WIDTH pixels, that is, the blue line moves the BF_BANNER_WIDTH pixels each time;
The number of bars is m_bannerNum, m_frameIndex is the number of draws, m_SrcImage is the current preview frame, and m_RenderImage is the current image to render.
Render operations:
glUseProgram (m_ProgramObj);
glBindVertexArray(m_VaoId);
glUniformMatrix4fv(m_MVPMatLoc, 1, GL_FALSE, &m_MVPMatrix[0] [0]);
// Copy the pixels of the current preview frame to the area to the right of the blue line
m_frameIndex = m_frameIndex % m_bannerNum;
float offset = m_frameIndex * 1.0 f / 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);
// Update the texture
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);
// Update the blue line position
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);
Copy the code
Detailed implementation code see the project: github.com/githubhaoha…