Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement this tunnel like animation in WebGL? [closed]

How to implement this tunnel like animation in WebGL?

enter image description here

Source: http://dvdp.tumblr.com/

See also: How to implement this rotating spiral in WebGL?

like image 440
zproxy Avatar asked Mar 27 '11 18:03

zproxy


1 Answers

Well, this was fun. :)

A WebGL demo is available here: http://boblycat.org/~knute/webgl/tunnel/

(EDIT: no longer available, but I created a ShaderToy version: https://www.shadertoy.com/view/XdKfRD)

The main algorithm is in the fragment shader. The basic idea is a for loop iterating over the black rings/circles, from large to small, also offsetting the center to produce a tunnel-like effect.

Given any pixel, we can check if the pixel is close enough to the ring to be a candidate for a black pixel or not. If it is outside the ring, break the loop to avoid seeing smaller rings through the larger ones.

The distance from the previous (outer) circle is used to "squeeze" the pattern together when rings are close, this helps create the illusion of a 3D surface.

The wavy pattern of each ring is of course a sine curve. The angle of the pixel (compared to the circle center) is combined with a uniform time parameter to animate the wavy pattern for each ring.

And finally, there was lots of experimentation with different parameters and transformation functions like pow() to get the result close to the target animation. It's not perfect, but pretty close.

The fragment shader 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;
}
like image 161
Soulman Avatar answered Nov 15 '22 19:11

Soulman