The little elder sister say, my head all by you spirit big, how to do?

The original article was first published on the wechat official account Byteflow

In the old article, we use OpenGL to achieve a slim, long legs effect and thin face big eyes effect, little sister wry smile: MY head are you angry, how to do?

How to do? For a straight male coder with terminal cancer, that’s no big deal.

Big head, small head effect

As we have seen in previous articles, it is easy to stretch and scale a given area of an image using the basic principles of OpenGL texture mapping (texture mapping).

Typical texture mapping shader.

// 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 highp float;
layout(location = 0) out vec4 outColor;
in vec2 v_texCoord;
uniform sampler2D s_TextureMap;
void main(a) {
    outColor = texture(s_TextureMap, v_texCoord);
}
Copy the code

In the previous article, texture mapping took place in regular rectangular areas, such as the thin and long legs effect, whereas in this article, the large and small heads effect actually scales the irregular face area. At this time, the grid cannot be divided according to the regular rectangle, for two reasons :(1) because we only want the deformation to occur in the head area, and the regular rectangular grid will lead to the distortion of the image background; (2) It is difficult to control the deformation degree of the head (irregular) region through regular rectangular mesh.

In order to prevent the background from serious distortion, we designed a radiant grid structure as shown above. To deform the header area, it is necessary to know the key points of the header area. The key points of the header area can be obtained by the AI algorithm. For the convenience of presentation, the key points in the head area are simplified into 9, among which 8 points are located at the edge of the head and 1 point is located at the center of the head.

A straight linex=1,y=1Each key point on the edge of the head and the center point of the head determine a straight line. The straight line will have intersection points with the edge of the rectangle. We use these intersection points and key points on the head to construct this radiating grid.

As shown in the figure above, a straight line is determined for each key point on the edge of the head and the center point of the head. This straight line can be represented by a binary first-order equation, and its intersection with the above rectangular edge can be obtained by solving the binary first-order equation.

The function to calculate the intersection point through the key point is as follows (inputPoint represents the key point on the head edge, centerPoint represents the centerPoint of the head, and DotProduct function represents the DotProduct of two vectors) :

vec2 BigHeadSample::CalculateIntersection(vec2 inputPoint, vec2 centerPoint) { vec2 outputPoint; Vec2 pointA(inputPoint.x, 0); vec2 pointA(inputPoint.x, 0); vec2 pointB(inputPoint.x, 1); float dA = distance(inputPoint, pointA); float dB = distance(inputPoint, pointB); outputPoint = dA > dB ? pointB : pointA; return outputPoint; Vec2 pointA(0, inputPoint.y); vec2 pointA(0, inputPoint.y); vec2 pointA(0, inputPoint.y); vec2 pointB(1, inputPoint.y); float dA = distance(inputPoint, pointA); float dB = distance(inputPoint, pointB); outputPoint = dA > dB ? pointB : pointA; return outputPoint; } // y = a*x + c float a=0, c=0; a = (inputPoint.y - centerPoint.y) / (inputPoint.x - centerPoint.x); c = inputPoint.y - a * inputPoint.x; / / x = 0, x = 1, y = 0, y = 1 / / four lines node x = 0 vec2 point_0 (0, c); float d0 = DotProduct((centerPoint - inputPoint),(centerPoint - point_0)); if(c >= 0 && c <= 1 && d0 > 0) outputPoint = point_0; //x=1 vec2 point_1(1, a + c); float d1 = DotProduct((centerPoint - inputPoint),(centerPoint - point_1)); if((a + c) >= 0 && (a + c) <= 1 && d1 > 0) outputPoint = point_1; //y=0 vec2 point_2(-c / a, 0); float d2 = DotProduct((centerPoint - inputPoint),(centerPoint - point_2)); if((-c / a) >= 0 && (-c / a) <= 1 && d2 > 0) outputPoint = point_2; //y=1 vec2 point_3((1-c) / a, 1); float d3 = DotProduct((centerPoint - inputPoint),(centerPoint - point_3)); if(((1-c) / a) >= 0 && ((1-c) / a) <= 1 && d3 > 0) outputPoint = point_3; return outputPoint; }Copy the code

After the radial mesh is constructed on the texture coordinate system, coordinate system transformation is needed, that is, the texture coordinate system is converted to the rendering coordinate system (screen coordinate system), and the corresponding vertex coordinate of the texture coordinate is obtained.

The mapping between texture coordinates to render coordinates (screen coordinates) ->(2*x- 1.12 -*y)

Copy the code

In addition, the control of head enlargement and shrinkage is actually achieved by controlling the relative position of vertex coordinates corresponding to key points on the head edge. When the vertex coordinates corresponding to key points on the head edge are close to the center point of the head, the head becomes smaller; when it is far away from the center point of the head, the head becomes larger on the contrary.

As shown in the figure above, the vertex coordinates corresponding to the key points on the edge of the head are close to the center point of the head, which can be calculated by adding points and vectors. The geometric meaning of point and vector addition is that the point moves a certain distance in the direction of the vector, which can be obtained by subtracting the coordinates of the center point of the head from the coordinates of the edge key points.

A function that moves key points around the edge.

//input is the edge key point, centerPoint is the centerPoint of the header, and level controls the moving distance
vec2 BigHeadSample::WarpKeyPoint(vec2 input, vec2 centerPoint, float level) {
	vec2 output;
	vec2 direct_vec = centerPoint - input;
	output = input + level * direct_vec * 0.3 f;
	return output;
}
Copy the code

Update the coordinates of the key points after moving and draw the image.

// Set the viewport
glViewport(0.0, screenW, screenH);

m_FrameIndex ++;

// Transform matrix
UpdateMVPMatrix(m_MVPMatrix, m_AngleX, m_AngleY, (float)screenW / screenH);

/ / strength
float ratio = (m_FrameIndex % 100) * 1.0 f / 100;
ratio = (m_FrameIndex / 100) % 2= =1 ? (1 - ratio) : ratio;

// Compute the new grid
CalculateMesh(ratio - 0.5 f);

// Update the vertex array
glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[0]);
glBufferSubData(GL_ARRAY_BUFFER, 0.sizeof(m_Vertices), m_Vertices);

// Draw the image
glUseProgram (m_ProgramObj);
glBindVertexArray(m_VaoId);
glUniformMatrix4fv(m_MVPMatLoc, 1, GL_FALSE, &m_MVPMatrix[0] [0]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_TextureId);
glUniform1i(m_SamplerLoc, 0);
glDrawArrays(GL_TRIANGLES, 0, TRIANGLE_COUNT * 3);
Copy the code

Head sway effect

So how to achieve the effect of head shaking? The answer is to control the location of the key points in the head. In short, all key points of the head are controlled to move uniformly according to the track of a circle. The key points of the head here are the points corresponding to the texture coordinates in the screen coordinate system.

Implements a function that moves key points along the path of a circle (input is the head key and rotaryAngle is the rotation Angle).

vec2 RotaryHeadSample::RotaryKeyPoint(vec2 input, float rotaryAngle) {
	return input + vec2(cos(rotaryAngle), sin(rotaryAngle)) * 0.02 f; // 0.02f represents the radius of the circle
}
Copy the code

Update the coordinates of the key points after moving and draw the image.

// Set the viewport
glViewport(0.0, screenW, screenH);

m_FrameIndex ++;

// Transform matrix
UpdateMVPMatrix(m_MVPMatrix, m_AngleX, m_AngleY, (float)screenW / screenH);

float ratio = (m_FrameIndex % 100) * 1.0 f / 100;

// Compute the new grid
CalculateMesh(static_cast<float>(ratio * 2 * MATH_PI));

// Update the vertex array
glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[0]);
glBufferSubData(GL_ARRAY_BUFFER, 0.sizeof(m_Vertices), m_Vertices);

// Draw the image
glUseProgram (m_ProgramObj);
glBindVertexArray(m_VaoId);
glUniformMatrix4fv(m_MVPMatLoc, 1, GL_FALSE, &m_MVPMatrix[0] [0]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_TextureId);
glUniform1i(m_SamplerLoc, 0);
glDrawArrays(GL_TRIANGLES, 0, TRIANGLE_COUNT * 3);
Copy the code

Implementation code path: Android_OpenGLES_3_0

Contact and exchange