8

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?

Community
  • 1
  • 1
zproxy
  • 3,509
  • 3
  • 39
  • 45

1 Answers1

28

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;
}
Soulman
  • 2,910
  • 24
  • 21
  • While testing wit Firefox on my low-end system: Shader link error: C:\util\firefox-3.7a5pre.en-US.win32\firefox\memory(98,16): warning X3206: implicit truncation of vector type C:\util\firefox-3.7a5pre.en-US.win32\firefox\memory(33,5): error X3511: Unable to unroll loop, loop does not appear to terminate in a timely manner (17 iterations), use the [unroll(n)] attribute to force an exact higher number – zproxy Apr 08 '11 at 05:45
  • With MAX_RINGS=10: Shader link error: C:\util\firefox-3.7a5pre.en-US.win32\firefox\memory(98,16): warning X3206: implicit truncation of vector type C:\util\firefox-3.7a5pre.en-US.win32\firefox\memory(74,4): error X5608: Compiled shader code uses too many arithmetic instruction slots (733). Max. allowed by the target (ps_2_0) is 64. (1,1): error X5609: Compiled shader code uses too many instruction slots (733). Max. allowed by the target (ps_2_0) is 96. – zproxy Apr 08 '11 at 05:50
  • On Galaxy S it works tho :) Can the shader be dumbed down to use less instructions? – zproxy Apr 08 '11 at 05:52
  • 1
    Perhaps the shader can be split into multiple shaders to fit into arithmetic instruction slots of 96? unless >> Multiple shader objects of the same type may not be attached to a single program object. – zproxy Apr 08 '11 at 06:14
  • I get the first error myself on an old Eee netbook, haven't investigated it further yet. But it does work with Firefox 4 on my HTC Desire phone, at about half a frame per second. :) – Soulman Apr 08 '11 at 07:18
  • It would arguably be a lot simpler just to make a torus, apply a wavy texture to it, put the camera inside and offset the U component of the texture coords. It would also run on all hardware and not stress the hardware – gman May 08 '15 at 15:20
  • @gman: Well, you should show us how to do that then! :) – Sk8erPeter May 14 '15 at 00:36
  • http://greggman.github.io/doodles/wave-tunnel.html Probably need to make the texture have multiple period of the wave instead of just one. – gman May 14 '15 at 10:00