如何在WebGL中实现类似隧道的动画?

发布于 2024-10-27 11:53:08 字数 332 浏览 9 评论 0原文

如何在WebGL中实现类似隧道的动画?

在此处输入图像描述

来源:http://dvdp.tumblr.com/

另请参阅:如何在WebGL中实现这种旋转螺旋?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

两人的回忆 2024-11-03 11:53:08

嗯,这很有趣。 :)

此处提供了 WebGL 演示: http://boblycat.org/~knute/webgl/ tunnel/

编辑:不再可用,但我创建了一个 ShaderToy 版本:https://www.shadertoy.com/view/XdKfRD

主要算法在片段着色器中。基本思想是 for 循环从大到小迭代黑色环/圆圈,同时偏移中心以产生类似隧道的效果。

给定任何像素,我们可以检查该像素是否足够接近环以成为黑色像素的候选者。如果它在环之外,请打破环以避免通过较大的环看到较小的环。

当环靠近时,与前一个(外)圆的距离用于将图案“挤压”在一起,这有助于创建 3D 表面的错觉。

每个环的波浪图案当然是正弦曲线。像素的角度(与圆中心相比)与统一的时间参数相结合,为每个环的波浪图案设置动画。

最后,对不同的参数和转换函数(如 pow())进行了大量实验,以获得接近目标动画的结果。它并不完美,但非常接近。

片段着色器代码:

#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;
}

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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文