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

Beating heart

While browsing the blog, I came across this “beating heart” effect and was instantly moved. I was shocked to learn that this effect was implemented in pure code (GLSL implementation).

Tracing back to the original source of this special effect, the original implementation was eventually found on the SahderToy website, in addition to numerous similar amazing effects found on SahderToy, and the implementation code of these special effects is completely open.

ShaderToy is an online community across browsers and a tool for creating and sharing shaders via WebGL for learning and teaching 3D computer graphics in Web browsers.

A quick visit to SahderToy, the Github of GLS that allows you to write and run GLSL scripts online, was like a new world.

We converted the script of “beating heart” special effects on the website into the corresponding GLSL script of OpenGLES to run on the mobile phone, and analyzed the whole script one by one. The complete code is as follows:

#version 300 es
precision highp float;
layout(location = 0) out vec4 outColor;/ / output
uniform float u_time;// Time offset
uniform vec2 u_screenSize;// Screen size
const float PI = 3.141592653;
void main(a)
{
// move to center
vec2 fragCoord = gl_FragCoord.xy;
vec2 p = (2.0*fragCoord-u_screenSize.xy)/min(u_screenSize.y,u_screenSize.x);

// background color
vec3 bcol = vec3(1.0.0.8.0.8) * (1.00.38*length(p));

// animate
float tt = u_time;
float ss = pow(tt,2.) *0.5 + 0.5;
ss = 1.0 + ss*0.5*sin(tt*6.2831*3.0 + p.y*0.5) *exp(-tt*4.0);
p *= vec2(0.5.1.5) + ss*vec2(0.5.0.5);

// shape
p.y -= 0.25;
float a = atan(p.x,p.y) / PI;
float r = length(p);
float h = abs(a);
float d = (13.0*h - 22.0*h*h + 10.0*h*h*h)/(6.05.0*h);

// color
float s = 0.75 + 0.75*p.x;
s *= 1.00.4*r;
s = 0.3 + 0.7*s;
s *= 0.5+0.5*pow( 1.0-clamp(r/d, 0.0.1.0 ), 0.1 );
vec3 hcol = vec3(1.0.0.5*r,0.3)*s;

vec3 col = mix( bcol, hcol, smoothstep( 0.06.0.06, d-r) );

outColor = vec4(col,1.0);
}
Copy the code

Gl_FragCoord: gl_FragCoord: gl_FragCoord The viewports associated with screen space coordinates are given by the viewport setting function glViewport and are accessible via the built-in gl_FragCoord variable in the fragment shader. The x and y of gl_FragCoord represent the screen space coordinates of the segment ((0,0) in the lower left corner). The value range is determined by the glViewport function, with the screen space coordinate origin in the lower left corner.

The following code adjusts the coordinate system by moving the origin from the bottom left corner to the center of the screen coordinate system, so that all the fragment vectors gl_fragcoord.xy start from the center of the screen, and the vector P is the direction vector between the center of the screen and the coordinates of the screen pixels.

// move to center
vec2 fragCoord = gl_FragCoord.xy;
vec2 p = (2.0 * fragCoord - u_screenSize.xy) / min(u_screenSize.y,u_screenSize.x);
Copy the code

Next, calculate the background color, length(p) means calculate the distance between the current slice (pixel) and the center point of the screen. The background color is based on vec3(1.0,0.8,0.8). The farther away from the screen, the darker the color is.

// background color
vec3 bcol = vec3(1.0.0.8.0.8) * (1.00.38*length(p));
Copy the code

At this point, we render the background color to see:

The heart is then drawn, and the boundary of the heart shape is determined by comparing the arctangent function value and the distance between the current slice (pixel) and the center point of the screen. The arctangent function atan(p.x,p.y) in the GLES ranges from [-π, π] to [-1, 1] after dividing by PI.

// shape
p.y -= 0.25;// Shift 0.25 units to the bottom of the screen
float a = atan(p.x,p.y) / PI;
float r = length(p);
float h = abs(a);// take the absolute value
/ / float d = (13.0-22.0 * h * * * h h h + 10.0 * * h * h)/(6.0 5.0 * h); // This function mainly flattens the shape of the heart. Ignore it for now
Copy the code

To understand the process of drawing hearts, we use the figure above. The a value of pixels on each straight line is the same. We use yellow dots to indicate the distance from the center of the screen, and then use d-r values to determine the boundary of hearts.

vec3 col = mix(bcol, hcol, smoothstep( 0.06.0.06, d-r) );
Copy the code

These are the key functions to draw the heart. Hcol is the color of the heart and bcol is the background color.

Smoothstep is a commonly used transition function that returns 0 when the third argument is greater than -0.06 hours, 1 when the third argument is greater than 0.06 hours, and 0 to 1 if the third argument is between -0.06 and 0.06. Its function is to smooth the change of d-R values at the junction of positive and negative.

The Mix function is used to weight the color of the blending heart and the background color. Based on the SmoothStep function feature, the heart color is inside the heart, the background color is outside the heart, and the boundary is a fuzzy transition between the two colors.

Again, the heart flattening function, when we don’t use the heart flattening function, but use h-R to control the shape of the heart, the resulting image is a fat, fat heart, so you can kind of get a sense of what this function does.

/ / float d = (13.0-22.0 * h * * * h h h + 10.0 * * h * h)/(6.0 5.0 * h); // This function mainly flattens the shape of the heart. Ignore it for now
vec3 col = mix(bcol, hcol, smoothstep( 0.06.0.06, h-r) );
Copy the code

The expression VEC3 (1.0,0.5* R,0.3) shows that the color of the heart is red, and the red color gradually decreases from the center of the screen to the surrounding areas, and then a series of gradual changes are produced, finally separating the color of the area inside and outside the heart.

// color
float s = 0.75 + 0.75*p.x;// There is a gradient in the x direction
s *= 1.00.4*r;// Create gradient based on distance
s = 0.3 + 0.7*s;// The dark area on the left is brightened
s *= 0.5+0.5*pow( 1.0-clamp(r/d, 0.0.1.0 ), 0.1 );// Use the variable r/d to separate the color of the area inside and outside the heart
vec3 hcol = vec3(1.0.0.5*r,0.3)*s;
Copy the code

Let’s directly output the color hcol of the heart and see what it looks like:

Finally, the pulsation effect is realized. Its principle is to periodically offset the screen pixels in the x and Y directions, and the offset range is controlled by special functions.

// animate
float tt = u_time;//u_time indicates the time periodically entered
float ss = pow(tt,2.) *0.5 + 0.5;
ss = 1.0 + ss*0.5*sin(tt*6.2831*3.0 + p.y*0.5) *exp(-tt*4.0);// control amplitude function
p *= vec2(0.5.1.5) + ss*vec2(0.5.0.5);
Copy the code

We control the input time cycle to 2000ms through the following code.

float time = static_cast<float> (fmod(GetSysCurrentTime(), 2000) / 2000);
glUniform1f(m_TimeLoc, time);
Copy the code

The simulation curve of amplitude control function is shown in the figure below, which can roughly see the amplitude change of heart beat.

Finally, we need to pay attention to the declaration of accuracy in GLSL script. The code in this paper uses highP precision, but when mediump precision is used, there will be burrs caused by insufficient accuracy, as shown in the following figure:

Implementation code path: NDK_OpenGLES_3_0

reference

www.shadertoy.com/view/XsfGRn blog.csdn.net/candycat199…

Contact and exchange