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

Amazing effects with a few lines of code.

Water ripple effect principle

Recently, a friend who makes video filters asked me to create a dynamic water ripple effect for him. Specifically, click on a certain position on the screen, and then the ripple spreads around from the center of that position. In response to this demand, we tried to modify the z component value in 3D coordinate system (x,y, Z) by using sine or cosine function, but the result was too false, and did not have the sense of reality of water ripples at all.

Then I obediently went to study how ripples of water form in the physical world. You don’t say, I really received a basin of water, sat next to observe along while.

Finally, it is observed that the characteristics of water ripples in the physical world are shown in the figure above. Looking down from the top of the water surface, the effect observed above the concave surface is the reduction effect, while the effect observed above the convex surface is the amplification effect, and the whole effect of water ripples is the cross arrangement of the amplification and reduction effect.

Therefore, we conclude that the water ripple effect is actually a combination of shrinking and amplifying effects that alternate with each other and gradually decrease in amplitude outwards. In this paper, the water ripple model is simplified as a set of amplifying and shrinking effects that gradually shift outward with time.

As shown in the figure above, we take the click position as the center, and the deformed area is the area between the inner circle and the outer circle. The circle (blue dotted line) constructed with the radius of normalized time variable u_Time as the boundary, and the inner part is the area to achieve the reduction effect, and the outer part is the area to achieve the amplification effect, or vice versa.

The width of the deformed area was a fixed value of 2*u_Boundary, and then the deformed area gradually moved outward with the increase of u_Time, finally forming a dynamic water ripple effect.

We set the Distance between the sampling point and the center point as Distance, and then calculate the value of Distance-u_time = DIff to determine whether the sampling point is located in the reduced region (DIff < 0) or the enlarged region (diff > 0). Finally, we only need to build a smooth function. With diff as the input, the function outputs positive values if diff < 0 and negative values if diff > 0.

Why do you do that? Because our fundamental goal is to achieve a certain area of narrow and amplification effect, we use smooth function of the output value as the deviation degree of sampling texture coordinates, when the output when the smoothing function, sampling coordinates to the lateral migration, presents the shrink effect, and smoothing function output is negative, the sample coordinates to round the inside of the migration, presents the amplification effect.

In addition, in order to prevent the deformation effect from jumping, we also need the smoothing function to satisfy that the output value at the boundary is 0 (or close to 0), indicating that this boundary is the critical boundary of whether deformation occurs.

Water ripple effect implemented

Based on the theoretical analysis in the previous section, we need to find an appropriate smoothing function. According to the above characteristics, the first function THAT comes to my mind is -x^3, which meets the conditions of smoothing and output values (positive left and negative right).

In building the smoothing function that we want,fooplot.com/The website provides online function drawing function, you can easily see a function of the generation curve.

Based on the similar -x^3 function, we build the following fragment shader to test whether the effect meets the expectation.

#version 300 es
precision highp float;
in vec2 v_texCoord;
layout(location = 0) out vec4 outColor;
uniform sampler2D s_TextureMap;/ / sampler
uniform vec2 u_TouchXY;// Click position (normalized)
uniform vec2 u_TexSize;// Texture size
uniform float u_Time;// Normalized time
uniform float u_Boundary;0.1 / / boundary
void main(a)
{
    float ratio = u_TexSize.y / u_TexSize.x;
    vec2 texCoord = v_texCoord * vec2(1.0, ratio);// Convert the adopted coordinates according to the texture size
    vec2 touchXY = u_TouchXY * vec2(1.0, ratio);// Convert the center point coordinates according to the texture size
    float distance = distance(texCoord, touchXY);// The distance between the coordinates of the sampling point and the center point
    
    if ((u_Time - u_Boundary) > 0.0
    && (distance <= (u_Time + u_Boundary))
    && (distance >= (u_Time - u_Boundary))) {
        float diff = (distance - u_Time); / / input diff
        float moveDis =  - pow(8 * diff, 3.0);// Smooth function -(8x)^3 Sampling coordinate movement distance
        vec2 unitDirectionVec = normalize(texCoord - touchXY);// Unit direction vector
        texCoord = texCoord + (unitDirectionVec * moveDis);// Sample coordinates offset (to zoom in and out)
    }

    texCoord = texCoord / vec2(1.0, ratio);// Convert back
    outColor = texture(s_TextureMap, texCoord);
}
Copy the code

Logic for drawing:

void ShockWaveSample::Draw(int screenW, int screenH)
{
	LOGCATE("ShockWaveSample::Draw()");
    m_SurfaceWidth = screenW;
    m_SurfaceHeight = screenH;
	if(m_ProgramObj == GL_NONE || m_TextureId == GL_NONE) return;

	m_FrameIndex ++;

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

	glUseProgram (m_ProgramObj);
	glBindVertexArray(m_VaoId);

	GLUtils::setMat4(m_ProgramObj, "u_MVPMatrix", m_MVPMatrix);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, m_TextureId);
	GLUtils::setFloat(m_ProgramObj, "s_TextureMap".0);

	//float time = static_cast<float>(fmod(GetSysCurrentTime(), 2000) / 2000);
    float time = static_cast<float> (fmod(m_FrameIndex, 150) / 120);
	GLUtils::setFloat(m_ProgramObj, "u_Time", time);
	
	// Set the click position
	GLUtils::setVec2(m_ProgramObj, "u_TouchXY", m_touchXY);
 	// Set the texture size
    GLUtils::setVec2(m_ProgramObj, "u_TexSize".vec2(m_RenderImage.width, m_RenderImage.height));
 	// Set the boundary value
	GLUtils::setFloat(m_ProgramObj, "u_Boundary".0.1 f);

	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0);

}
Copy the code

We use y=-(8*x)^3 as the smoothing function, and the resulting effect is shown as follows. Although there is water ripple effect, the deformation boundary jumps seriously. Originally, the smoothing function does not meet the condition that the output value at the boundary is 0.

In order to satisfy the condition that the output value of the smoothing function is 0 at the boundary, we constructed a function y=20x*(x-0.1)*(x+0.1) using fooplot. The function curve is shown in the figure below. Since the boundary value u_Boundary is 0.1, the function meets our requirements.

In the fragment shader above, we replace the smoothing function:

float x = (distance - u_Time); // Enter diff = x
float moveDis = 20.0 * x * (x - 0.1)*(x + 0.1);// Smooth function y=20.0 * x *(x-0.1)*(x + 0.1) sampling coordinate movement distance
Copy the code

After replacing the smoothing function, the result is as shown in the figure below. The result is in line with the expectation, and there is no deformation jump at the boundary.

In addition, we found a weird function on the Internet, y= (1- math.pow (math.abs (20*x), 4.8))*x, which looks interesting as shown in the following image.

Of course, we can also build a smooth function with multiple zeros in the deformation area, to create multiple wave effects, more interesting effects for you to explore.

Implementation code path: Android_OpenGLES_3_0

Technical question solving

Technical exchange or access to source code can add my wechat: byte-flow, access to audio and video development video tutorials