Three.js 实现超酷炫的虫洞时空漩涡效果!附完整源码

特此感谢作者的贡献!!!

📸 效果预览

虫洞时空漩涡效果

(虫洞时空漩涡效果,螺旋状的能量隧道带你穿越时空)

🚀 在线演示

👉 点击这里立即体验

🤔 前言

相信大家对虫洞都不陌生,在科幻作品中,虫洞是连接两个时空的隧道,让人可以瞬间穿越遥远的距离。

本篇文章将带你深入解析这个虫洞时空漩涡效果的实现原理,了解如何使用 Three.js 着色器创建令人震撼的视觉效果。

💻 核心代码解析

第一步:基础场景搭建

首先,创建 Three.js 基础环境,包括场景、相机、渲染器和轨道控制器。

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

const canvas = document.createElement('canvas');
document.body.appendChild(canvas);

const renderer = new THREE.WebGLRenderer({
    canvas: canvas,
    antialias: true,
    alpha: true,
    preserveDrawingBuffer: true
});

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(90, 1, 0.1, 1000);
camera.position.set(0, 0, 5);

const controls = new OrbitControls(camera, canvas);
controls.enableDamping = true;
controls.maxDistance = 20;
controls.minDistance = 1;
controls.enablePan = false;

第二步:创建虫洞着色器材质(核心)

该实现使用 RawShaderMaterial,核心是基于极坐标和螺旋噪声的隧道效果。

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
const material = new THREE.RawShaderMaterial({
glslVersion: THREE.GLSL3,
uniforms: {
    center: { value: new THREE.Vector2(0.5, 0.5) },
    radius: { value: 0.3 },
    tunnelLength: { value: 8.0 },
    rotationSpeed: { value: 1.5 },
    colorIntensity: { value: 0.7 },
    glowIntensity: { value: 1.0 },
    pulseIntensity: { value: 0.15 },
    color1: { value: new THREE.Color(0x00ffff) },
    color2: { value: new THREE.Color(0xff00ff) },
    color3: { value: new THREE.Color(0xffff00) },
    time: { value: 0.0 },
    resolution: { value: new THREE.Vector2() },
},

vertexShader: `
    precision highp float;
    
    in vec3 position;
    in vec2 uv;
    
    out vec2 V_uv;
    
    void main() {
    V_uv = uv;
    gl_Position = vec4(position, 1.0);
    }
`,

fragmentShader: `
    precision highp float;
    
    #define PI 3.141592653589793
    
    uniform vec2 center;
    uniform float radius;
    uniform float tunnelLength;
    uniform float rotationSpeed;
    uniform float colorIntensity;
    uniform float glowIntensity;
    uniform float pulseIntensity;
    uniform vec3 color1;
    uniform vec3 color2;
    uniform vec3 color3;
    uniform float time;
    uniform vec2 resolution;
    
    in vec2 V_uv;
    
    // 伪随机函数
    float hash(vec2 p) {
    return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
    }
    
    // 二维噪声
    float noise(vec2 p) {
    vec2 i = floor(p);
    vec2 f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    
    float a = hash(i);
    float b = hash(i + vec2(1.0, 0.0));
    float c = hash(i + vec2(0.0, 1.0));
    float d = hash(i + vec2(1.0, 1.0));
    
    return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
    }
    
    // 分形布朗运动
    float fbm(vec2 p) {
    float value = 0.0;
    float amplitude = 0.5;
    float frequency = 1.0;
    
    for (int i = 0; i < 5; i++) {
        value += amplitude * noise(p * frequency);
        amplitude *= 0.5;
        frequency *= 2.0;
    }
    return value;
    }
    
    // 螺旋噪声
    float spiralNoise(vec2 p, float twists, float time) {
    float r = length(p);
    float a = atan(p.y, p.x);
    float angle = a + r * twists + time;
    return sin(angle * 6.0) * 0.5 + 0.5;
    }
    
    void main() {
    vec2 uv = V_uv;
    vec2 p = uv - center;
    float r = length(p);
    float a = atan(p.y, p.x);
    
    float nr = r / radius;
    
    // 虫洞隧道效果
    float tunnel = 1.0 / (nr + 0.1);
    float rot = time * rotationSpeed;
    
    // 多层螺旋
    float spiral1 = spiralNoise(p * 3.0, 8.0, rot);
    float spiral2 = spiralNoise(p * 5.0 + vec2(100.0), -6.0, rot * 0.7);
    float spiral3 = spiralNoise(p * 2.0 + vec2(50.0), 4.0, rot * 1.3);
    
    // 深度感
    float depth = tunnelLength / (nr * tunnelLength + 1.0);
    depth = clamp(depth, 0.0, tunnelLength);
    
    // 能量条纹
    float energyBands = sin(depth * 20.0 - time * 5.0 + a * 5.0) * 0.5 + 0.5;
    energyBands = pow(energyBands, 3.0);
    
    // 分形纹理
    float fractal = fbm(p * 10.0 + vec2(time * 0.2));
    
    // 组合颜色
    vec3 color = color1 * spiral1 * 0.5 + color2 * energyBands * 0.5 + color3 * fractal * 0.4;
    
    // 中心发光
    float glow = exp(-nr * 6.0) * 3.0 * glowIntensity;
    color += color1 * glow * 0.7 + color2 * glow * 0.3;
    
    // 中心白点
    float core = exp(-nr * 40.0) * 4.0 * glowIntensity;
    color += color1 * core * 0.7 + vec3(1.0) * core * 0.3;
    
    // 边缘暗化
    float vignette = 1.0 - smoothstep(0.3, 1.8, nr);
    color *= vignette;
    
    // 能量脉冲
    float pulse = sin(time * 3.0) * pulseIntensity + (1.0 - pulseIntensity * 0.5);
    color *= pulse;
    
    gl_FragColor = vec4(color, 1.0);
    }
`,
});

着色器核心原理

组件 实现方式
隧道效果 使用 1.0 / (nr + 0.1) 创建向内延伸的视觉效果
螺旋纹理 spiralNoise() 函数生成旋转的螺旋图案
分形布朗运动 fbm() 函数生成有机纹理细节
深度感 根据距离中心的远近调整亮度和颜色
能量条纹 使用正弦函数生成动态条纹
中心发光 使用指数函数创建渐变光晕

第三步:创建全屏平面

javascript
1
2
3
const geometry = new THREE.PlaneGeometry(2, 2);
const quad = new THREE.Mesh(geometry, material);
scene.add(quad);

第四步:动画循环与GUI控制

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const clock = new THREE.Clock();

const data = {
radius: 0.3,
tunnelLength: 8.0,
rotationSpeed: 1.5,
colorIntensity: 0.7,
color1: '#00ffff',
color2: '#ff00ff',
color3: '#ffff00',
glowIntensity: 1.0,
pulseIntensity: 0.15,
};

import GUI from 'lil-gui';
const gui = new GUI();
gui.add(data, 'radius', 0.1, 0.5, 0.01).name('半径');
gui.add(data, 'tunnelLength', 1.0, 20.0, 0.1).name('隧道长度');
gui.add(data, 'rotationSpeed', 0.0, 5.0, 0.1).name('旋转速度');
gui.add(data, 'colorIntensity', 0.0, 1.0, 0.01).name('颜色强度');
gui.add(data, 'glowIntensity', 0.0, 2.0, 0.01).name('发光强度');
gui.add(data, 'pulseIntensity', 0.0, 0.5, 0.01).name('脉冲强度');
gui.addColor(data, 'color1').name('颜色1');
gui.addColor(data, 'color2').name('颜色2');
gui.addColor(data, 'color3').name('颜色3');

const tick = (delta, elapsed) => {
controls.update(delta);
material.uniforms.time.value = elapsed;
material.uniforms.radius.value = data.radius;
material.uniforms.tunnelLength.value = data.tunnelLength;
material.uniforms.rotationSpeed.value = data.rotationSpeed;
material.uniforms.color1.value.set(data.color1);
material.uniforms.color2.value.set(data.color2);
material.uniforms.color3.value.set(data.color3);
};

const ani = () => {
const elapsed = clock.getElapsedTime();
const delta = clock.getDelta();
tick(delta, elapsed);
renderer.render(scene, camera);
requestAnimationFrame(ani);
};

ani();

� 关键技术点

技术点 说明 实现位置
RawShaderMaterial 自定义GLSL着色器,支持GLSL 3.0 材质定义
伪随机函数 hash() 生成随机值用于噪声 着色器函数
二维噪声 noise() 生成基础噪声 着色器函数
分形布朗运动 fbm() 生成复杂有机纹理 着色器函数
螺旋噪声 spiralNoise() 生成旋转漩涡纹理 着色器函数
极坐标转换 使用 atan() 转换为极坐标 main() 函数

🔧 常见问题

问题1:隧道效果不明显

原因radiustunnelLength 参数设置不当

解决

  • 增大 radius 值使隧道更宽
  • 增大 tunnelLength 值增强深度感

问题2:颜色不够鲜艳

原因colorIntensity 参数值过低

解决

  • 增大 colorIntensity 参数(范围 0-1)
  • 调整三种颜色选择更鲜艳的配色

问题3:性能较差

原因:分形噪声计算复杂度较高

优化方向

  • 减少 fbm() 循环次数(当前5次)
  • 降低噪声采样频率

📌 总结

本文介绍的虫洞时空漩涡效果实现了:

  • 隧道视觉效果:使用极坐标和倒数函数创建向内延伸的隧道感
  • 螺旋纹理:多层螺旋噪声叠加产生复杂的漩涡图案
  • 分形布朗运动:生成自然有机的纹理细节
  • 动态效果:旋转、脉冲、闪烁等多种动画效果
  • 中心发光:模拟虫洞入口的明亮核心

💡 扩展方向

  1. 添加粒子系统模拟穿越虫洞时的粒子效果
  2. 实现视角移动效果,让用户感觉在穿越隧道
  3. 添加声音效果增强沉浸感
  4. 实现不同阶段的虫洞形态变化

本文档由 ThreeLab 编辑整理,基于开源实现整理,如需转载,请注明出处。