好的,这很有趣。 :)
WebGL演示可以在此处找到:http://boblycat.org/~knute/webgl/tunnel/
(编辑:不再可用,但我创建了一个ShaderToy版本:https://www.shadertoy.com/view/XdKfRD)
主要算法在片段着色器中。基本思想是从大到小迭代遍历黑色环/圆,并偏移中心以产生隧道效果。
对于任何像素,我们可以检查该像素是否接近环以成为黑色像素的候选项。如果它在环外部,则跳出循环以避免看到较大的环穿过较小的环。
从前一个(外部)圆的距离用于在环接近时“挤压”模式在一起,这有助于创建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;
}