Well, that was fun. :)
WebGL demo is available here: http://boblycat.org/~knute/webgl/tunnel/
The main algorithm is in the fragment shader. The main idea is a loop cycle, repeating in black rings / circles, from large to small, also a compensating center for creating a tunnel effect.
For any pixel, we can check if the pixel is enough for the ring to be a candidate for a black pixel or not. If it is outside the ring, break the loop so as not to see the smaller rings through the larger ones.
The distance from the previous (outer) circle is used to “squeeze” the picture together, when the rings are close, this helps create the illusion of a three-dimensional surface.
The wavy structure of each ring is, of course, sinusoidal. The pixel angle (compared to the center of the circle) is combined with a single time parameter for animating the wave pattern for each ring.
And finally, there were many experiments with various parameters and conversion functions, such as pow (), to get a result close to the target animation. It is not perfect, but pretty close.
Shader Fragment Code:
#ifdef GL_ES precision highp float; #endif const float PI = 3.14159265358979323846264; const float TWOPI = PI*2.0; const vec4 WHITE = vec4(1.0, 1.0, 1.0, 1.0); const vec4 BLACK = vec4(0.0, 0.0, 0.0, 1.0); const vec2 CENTER = vec2(0.0, 0.0); const int MAX_RINGS = 30; const float RING_DISTANCE = 0.05; const float WAVE_COUNT = 60.0; const float WAVE_DEPTH = 0.04; uniform float uTime; varying vec2 vPosition; void main(void) { float rot = mod(uTime*0.0006, TWOPI); float x = vPosition.x; float y = vPosition.y; bool black = false; float prevRingDist = RING_DISTANCE; for (int i = 0; i < MAX_RINGS; i++) { vec2 center = vec2(0.0, 0.7 - RING_DISTANCE * float(i)*1.2); float radius = 0.5 + RING_DISTANCE / (pow(float(i+5), 1.1)*0.006); float dist = distance(center, vPosition); dist = pow(dist, 0.3); float ringDist = abs(dist-radius); if (ringDist < RING_DISTANCE*prevRingDist*7.0) { float angle = atan(y - center.y, x - center.x); float thickness = 1.1 * abs(dist - radius) / prevRingDist; float depthFactor = WAVE_DEPTH * sin((angle+rot*radius) * WAVE_COUNT); if (dist > radius) { black = (thickness < RING_DISTANCE * 5.0 - depthFactor * 2.0); } else { black = (thickness < RING_DISTANCE * 5.0 + depthFactor); } break; } if (dist > radius) break; prevRingDist = ringDist; } gl_FragColor = black ? BLACK : WHITE; }
Soulman
source share