-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathblackhole.html
More file actions
243 lines (203 loc) · 9.65 KB
/
Copy pathblackhole.html
File metadata and controls
243 lines (203 loc) · 9.65 KB
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>3D Interactive Black Hole Simulation</title>
<style>
body { margin: 0; overflow: hidden; background-color: #000; }
canvas { display: block; }
#info {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-family: 'Courier New', Courier, monospace;
pointer-events: none;
z-index: 10;
text-shadow: 0 0 5px #000;
}
</style>
</head>
<body>
<div id="info">
<h2>WebGL 黑洞模拟器</h2>
<p>拖动鼠标旋转视角 | 滚轮缩放</p>
</div>
<!-- 引入 Three.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<!-- 引入 OrbitControls 用于鼠标控制 -->
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
<script>
// --- 1. 初始化场景 ---
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// --- 2. 设置交互控制 ---
const controls = new THREE.OrbitControls(camera, renderer.domElement);
camera.position.set(0, 2, 6); // 初始相机位置
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.autoRotate = true; // 开启自动旋转
controls.autoRotateSpeed = 0.5;
// --- 3. 核心着色器 (GLSL) ---
// 这里包含了黑洞物理视觉模拟的核心算法:引力透镜和吸积盘
const fragmentShader = `
uniform float iTime;
uniform vec2 iResolution;
uniform vec3 iCameraPos;
uniform vec3 iCameraDir;
uniform vec3 iCameraUp;
// 旋转矩阵辅助函数
mat2 rot(float a) {
float s = sin(a), c = cos(a);
return mat2(c, -s, s, c);
}
// 噪声函数,用于生成吸积盘的纹理细节
float hash(vec2 p) { return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * (0.1 + abs(sin(p.y * 13.0 + p.x)))); }
float noise(vec2 x) {
vec2 i = floor(x);
vec2 f = fract(x);
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));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
// 分形布朗运动,创造云雾状效果
float fbm(vec2 p) {
float v = 0.0;
float a = 0.5;
mat2 m = rot(1.5);
for (int i = 0; i < 5; i++) {
v += a * noise(p);
p = m * p * 2.0;
a *= 0.5;
}
return v;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
// 构建相机坐标系
vec3 camPos = iCameraPos;
vec3 forward = normalize(vec3(0.0) - camPos); // 始终看向中心
vec3 right = normalize(cross(vec3(0.0, 1.0, 0.0), forward));
vec3 up = cross(forward, right);
// 射线方向
vec3 ro = camPos;
vec3 rd = normalize(forward + uv.x * right + uv.y * up);
vec3 col = vec3(0.0);
// --- 黑洞参数 ---
float bhMass = 0.5; // 黑洞质量
float bhRadius = bhMass * 2.0; // 史瓦西半径 (事件视界)
float diskRadiusMin = bhRadius * 2.5;
float diskRadiusMax = bhRadius * 7.0;
// --- Raymarching 循环 ---
// 我们不是渲染多边形,而是追踪每条光线
float t = 0.0;
vec3 p = ro;
float brightness = 0.0;
vec3 diskCol = vec3(0.0);
// 累积光线弯曲和吸积盘颜色
float iter = 0.0;
float maxSteps = 100.0;
for(float i=0.0; i < 100.0; i++) {
float dist = length(p);
// 1. 引力弯曲 (简化版广义相对论)
// 越靠近中心,光线越被吸向中心
float force = (bhMass) / (dist * dist);
rd = normalize(rd - p * force * 0.05); // 弯曲光线方向
// 移动光线
float stepSize = max(0.1, dist * 0.05);
p += rd * stepSize;
// 2. 检测是否击中事件视界 (黑洞本体)
if(dist < bhRadius) {
col = vec3(0.0); // 全黑
break; // 光线被吞噬,停止计算
}
// 3. 渲染吸积盘 (Volumetric Rendering)
// 这是一个位于 XZ 平面的盘
float diskH = 0.1; // 盘的厚度
if(abs(p.y) < diskH * 4.0) {
float d = length(p.xz);
if(d > diskRadiusMin && d < diskRadiusMax) {
// 旋转纹理
float angle = atan(p.z, p.x);
float speed = 2.0 / d; // 越靠近中心旋转越快
float noiseVal = fbm(vec2(d * 3.0 - iTime * 2.0, angle * 5.0 - iTime * speed));
// 颜色映射:基于温度(距离)
float temp = (diskRadiusMax - d) / (diskRadiusMax - diskRadiusMin);
vec3 hotColor = vec3(1.0, 0.6, 0.2) * 2.0; // 橙色
vec3 coldColor = vec3(0.8, 0.2, 0.1); // 红色
vec3 c = mix(coldColor, hotColor, temp * temp);
// 密度计算 (距离Y平面越近越密)
float density = exp(-abs(p.y) * 20.0) * max(0.0, noiseVal);
// 多普勒效应模拟 (左侧亮,右侧暗)
float doppler = 1.0 + dot(normalize(p.xz), vec2(-1.0,0.0)) * 0.5 * (2.0/d);
diskCol += c * density * 0.05 * doppler;
}
}
// 远距离逃逸
if(dist > 20.0) {
// 背景星空 (如果光线逃逸到无穷远)
float stars = pow(hash(rd.xy * 100.0), 100.0) * 1.0;
col += vec3(stars);
break;
}
}
col += diskCol;
// 色调映射 (Tone Mapping) 防止过曝
col = col / (1.0 + col);
col = pow(col, vec3(0.4545)); // Gamma校正
fragColor = vec4(col, 1.0);
}
void main() {
mainImage(gl_FragColor, gl_FragCoord.xy);
}
`;
const vertexShader = `
void main() {
gl_Position = vec4( position, 1.0 );
}
`;
// --- 4. 创建显示平面 ---
// 我们在一个覆盖全屏的平面上运行 Shader
const geometry = new THREE.PlaneGeometry(2, 2);
const uniforms = {
iTime: { value: 0 },
iResolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
iCameraPos: { value: new THREE.Vector3() },
iCameraDir: { value: new THREE.Vector3() },
iCameraUp: { value: new THREE.Vector3(0, 1, 0) }
};
const material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: vertexShader,
fragmentShader: fragmentShader
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// --- 5. 动画循环 ---
function animate(time) {
requestAnimationFrame(animate);
controls.update(); // 更新鼠标交互
// 将 Three.js 的相机参数传递给 Shader
uniforms.iTime.value = time * 0.001;
uniforms.iCameraPos.value.copy(camera.position);
// 在 Shader 中我们使用的是全屏四边形,所以需要手动计算相机方向
// 这里我们简单地将相机位置传入,Shader内部会重新计算射线
renderer.render(scene, camera);
}
// 窗口大小调整处理
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
uniforms.iResolution.value.set(window.innerWidth, window.innerHeight);
});
animate();
</script>
</body>
</html>