571 lines
22 KiB
HTML
571 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Aurora Borealis - 北极光</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
body {
|
|
overflow: hidden;
|
|
background: #000;
|
|
}
|
|
#canvas-container {
|
|
width: 100vw;
|
|
height: 100vh;
|
|
}
|
|
#fps {
|
|
position: fixed;
|
|
top: 10px;
|
|
left: 10px;
|
|
color: #00ff88;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 14px;
|
|
text-shadow: 0 0 10px rgba(0, 255, 136, 0.8);
|
|
pointer-events: none;
|
|
z-index: 100;
|
|
}
|
|
#info {
|
|
position: fixed;
|
|
bottom: 10px;
|
|
left: 10px;
|
|
color: #9d4edd;
|
|
font-family: 'Arial', sans-serif;
|
|
font-size: 12px;
|
|
opacity: 0.7;
|
|
pointer-events: none;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="fps">FPS: 60</div>
|
|
<div id="info">Aurora Borealis - 点击并拖动旋转视角</div>
|
|
<div id="canvas-container"></div>
|
|
|
|
<!-- Three.js CDN -->
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
|
|
|
<script>
|
|
// ============================================
|
|
// Aurora Shader - 极光着色器
|
|
// ============================================
|
|
|
|
const AuroraVertexShader = `
|
|
varying vec2 vUv;
|
|
varying vec3 vPosition;
|
|
|
|
void main() {
|
|
vUv = uv;
|
|
vPosition = position;
|
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
}
|
|
`;
|
|
|
|
const AuroraFragmentShader = `
|
|
uniform float uTime;
|
|
uniform vec2 uResolution;
|
|
uniform float uIntensity;
|
|
uniform vec3 uColor1;
|
|
uniform vec3 uColor2;
|
|
uniform float uSpeed;
|
|
uniform float uOffset;
|
|
|
|
varying vec2 vUv;
|
|
varying vec3 vPosition;
|
|
|
|
// 改进的 Perlin Noise 函数
|
|
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
|
vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
|
vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
|
|
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
|
|
|
|
float snoise(vec3 v) {
|
|
const vec2 C = vec2(1.0/6.0, 1.0/3.0);
|
|
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
|
|
|
|
vec3 i = floor(v + dot(v, C.yyy));
|
|
vec3 x0 = v - i + dot(i, C.xxx);
|
|
|
|
vec3 g = step(x0.yzx, x0.xyz);
|
|
vec3 l = 1.0 - g;
|
|
vec3 i1 = min(g.xyz, l.zxy);
|
|
vec3 i2 = max(g.xyz, l.zxy);
|
|
|
|
vec3 x1 = x0 - i1 + C.xxx;
|
|
vec3 x2 = x0 - i2 + C.yyy;
|
|
vec3 x3 = x0 - D.yyy;
|
|
|
|
i = mod289(i);
|
|
vec4 p = permute(permute(permute(
|
|
i.z + vec4(0.0, i1.z, i2.z, 1.0))
|
|
+ i.y + vec4(0.0, i1.y, i2.y, 1.0))
|
|
+ i.x + vec4(0.0, i1.x, i2.x, 1.0));
|
|
|
|
float n_ = 0.142857142857;
|
|
vec3 ns = n_ * D.wyz - D.xzx;
|
|
|
|
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
|
|
|
vec4 x_ = floor(j * ns.z);
|
|
vec4 y_ = floor(j - 7.0 * x_);
|
|
|
|
vec4 x = x_ *ns.x + ns.yyyy;
|
|
vec4 y = y_ *ns.x + ns.yyyy;
|
|
vec4 h = 1.0 - abs(x) - abs(y);
|
|
|
|
vec4 b0 = vec4(x.xy, y.xy);
|
|
vec4 b1 = vec4(x.zw, y.zw);
|
|
|
|
vec4 s0 = floor(b0)*2.0 + 1.0;
|
|
vec4 s1 = floor(b1)*2.0 + 1.0;
|
|
vec4 sh = -step(h, vec4(0.0));
|
|
|
|
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy;
|
|
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww;
|
|
|
|
vec3 p0 = vec3(a0.xy, h.x);
|
|
vec3 p1 = vec3(a0.zw, h.y);
|
|
vec3 p2 = vec3(a1.xy, h.z);
|
|
vec3 p3 = vec3(a1.zw, h.w);
|
|
|
|
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
|
|
p0 *= norm.x;
|
|
p1 *= norm.y;
|
|
p2 *= norm.z;
|
|
p3 *= norm.w;
|
|
|
|
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
|
|
m = m * m;
|
|
return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
|
|
}
|
|
|
|
// 多重噪声叠加,增加层次感
|
|
float fbm(vec3 p) {
|
|
float value = 0.0;
|
|
float amplitude = 0.5;
|
|
float frequency = 1.0;
|
|
|
|
for(int i = 0; i < 5; i++) {
|
|
value += amplitude * snoise(p * frequency);
|
|
amplitude *= 0.5;
|
|
frequency *= 2.0;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
void main() {
|
|
vec2 uv = vUv;
|
|
float time = uTime * uSpeed;
|
|
|
|
// 极光带的核心位置(上部天空区域)
|
|
float auroraBase = 0.3 + uOffset * 0.3;
|
|
|
|
// 多层噪声创建流动效果
|
|
vec3 noisePos = vec3(uv.x * 3.0 + time * 0.1, uv.y * 2.0, time * 0.15);
|
|
float noise1 = fbm(noisePos);
|
|
float noise2 = fbm(noisePos * 1.5 + vec3(100.0));
|
|
float noise3 = fbm(noisePos * 0.7 + vec3(50.0, time * 0.1, 0.0));
|
|
|
|
// 极光的垂直分布
|
|
float verticalGradient = 1.0 - smoothstep(auroraBase - 0.2, auroraBase + 0.4, uv.y);
|
|
verticalGradient *= smoothstep(0.0, auroraBase - 0.1, uv.y);
|
|
|
|
// 波浪形状
|
|
float wave = sin(uv.x * 6.28 + noise1 * 2.0 + time) * 0.15;
|
|
wave += sin(uv.x * 12.0 + noise2 * 1.5 + time * 1.3) * 0.08;
|
|
wave += noise3 * 0.1;
|
|
|
|
// 极光带形状
|
|
float auroraCenter = auroraBase + wave;
|
|
float auroraWidth = 0.15 + noise2 * 0.05;
|
|
|
|
float auroraShape = 1.0 - smoothstep(0.0, auroraWidth, abs(uv.y - auroraCenter));
|
|
auroraShape *= verticalGradient;
|
|
|
|
// 添加细长的丝带效果
|
|
float ribbon = sin(uv.x * 20.0 + time * 2.0 + noise1 * 3.0) * 0.5 + 0.5;
|
|
ribbon = pow(ribbon, 4.0) * 0.3;
|
|
auroraShape += ribbon * (1.0 - smoothstep(0.0, 0.3, abs(uv.y - auroraCenter)));
|
|
|
|
// 颜色渐变 - 基于位置和时间
|
|
float colorMix = sin(uv.x * 2.0 + time * 0.5 + noise1) * 0.5 + 0.5;
|
|
colorMix += noise2 * 0.3;
|
|
vec3 color = mix(uColor1, uColor2, colorMix);
|
|
|
|
// 添加亮度变化
|
|
float brightness = 0.6 + noise3 * 0.4;
|
|
brightness *= (1.0 + ribbon * 2.0);
|
|
|
|
// 边缘柔和
|
|
float edgeFade = smoothstep(0.0, 0.1, uv.x) * smoothstep(1.0, 0.9, uv.x);
|
|
|
|
// 最终颜色计算
|
|
float alpha = auroraShape * uIntensity * edgeFade * 0.8;
|
|
vec3 finalColor = color * brightness * auroraShape * 2.0;
|
|
|
|
// 添加顶部光晕
|
|
float topGlow = exp(-uv.y * 3.0) * noise1 * 0.2;
|
|
finalColor += uColor1 * topGlow * uIntensity;
|
|
|
|
gl_FragColor = vec4(finalColor, alpha);
|
|
}
|
|
`;
|
|
|
|
// ============================================
|
|
// Star Field Shader - 星空着色器
|
|
// ============================================
|
|
|
|
const StarVertexShader = `
|
|
attribute float size;
|
|
attribute float opacity;
|
|
varying float vOpacity;
|
|
void main() {
|
|
vOpacity = opacity;
|
|
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
|
gl_PointSize = size * (300.0 / -mvPosition.z);
|
|
gl_Position = projectionMatrix * mvPosition;
|
|
}
|
|
`;
|
|
|
|
const StarFragmentShader = `
|
|
varying float vOpacity;
|
|
void main() {
|
|
float dist = distance(gl_PointCoord, vec2(0.5));
|
|
float alpha = 1.0 - smoothstep(0.0, 0.5, dist);
|
|
gl_FragColor = vec4(1.0, 1.0, 1.0, alpha * vOpacity);
|
|
}
|
|
`;
|
|
|
|
// ============================================
|
|
// Main Application
|
|
// ============================================
|
|
|
|
class AuroraApp {
|
|
constructor() {
|
|
this.container = document.getElementById('canvas-container');
|
|
this.fpsElement = document.getElementById('fps');
|
|
this.scene = null;
|
|
this.camera = null;
|
|
this.renderer = null;
|
|
this.auroras = [];
|
|
this.stars = null;
|
|
this.mountains = null;
|
|
this.clock = new THREE.Clock();
|
|
this.frameCount = 0;
|
|
this.lastTime = 0;
|
|
this.mouse = new THREE.Vector2();
|
|
this.targetRotation = 0;
|
|
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
// 创建场景
|
|
this.scene = new THREE.Scene();
|
|
|
|
// 创建相机
|
|
this.camera = new THREE.PerspectiveCamera(
|
|
60,
|
|
window.innerWidth / window.innerHeight,
|
|
0.1,
|
|
1000
|
|
);
|
|
this.camera.position.set(0, 0.5, 4);
|
|
this.camera.lookAt(0, 0.3, 0);
|
|
|
|
// 创建渲染器
|
|
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
|
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
|
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
this.container.appendChild(this.renderer.domElement);
|
|
|
|
// 添加背景渐变
|
|
this.createBackground();
|
|
|
|
// 添加星空
|
|
this.createStars();
|
|
|
|
// 添加极光层
|
|
this.createAuroras();
|
|
|
|
// 添加山脉
|
|
this.createMountains();
|
|
|
|
// 事件监听
|
|
window.addEventListener('resize', this.onResize.bind(this));
|
|
document.addEventListener('mousemove', this.onMouseMove.bind(this));
|
|
|
|
// 开始动画
|
|
this.animate();
|
|
}
|
|
|
|
createBackground() {
|
|
// 创建天空渐变背景
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = 2;
|
|
canvas.height = 512;
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
const gradient = ctx.createLinearGradient(0, 0, 0, 512);
|
|
gradient.addColorStop(0.0, '#0a1628'); // 顶部深蓝黑
|
|
gradient.addColorStop(0.3, '#0d2137'); // 中上部深绿蓝
|
|
gradient.addColorStop(0.6, '#1a3a3a'); // 中部带绿
|
|
gradient.addColorStop(0.85, '#0d1f2d'); // 中下部
|
|
gradient.addColorStop(1.0, '#000000'); // 底部纯黑
|
|
|
|
ctx.fillStyle = gradient;
|
|
ctx.fillRect(0, 0, 2, 512);
|
|
|
|
const texture = new THREE.CanvasTexture(canvas);
|
|
this.scene.background = texture;
|
|
}
|
|
|
|
createStars() {
|
|
const starCount = 800;
|
|
const positions = new Float32Array(starCount * 3);
|
|
const sizes = new Float32Array(starCount);
|
|
const opacities = new Float32Array(starCount);
|
|
|
|
for (let i = 0; i < starCount; i++) {
|
|
positions[i * 3] = (Math.random() - 0.5) * 50;
|
|
positions[i * 3 + 1] = Math.random() * 25;
|
|
positions[i * 3 + 2] = (Math.random() - 0.5) * 30 - 10;
|
|
|
|
sizes[i] = Math.random() * 2 + 0.5;
|
|
opacities[i] = Math.random() * 0.8 + 0.2;
|
|
}
|
|
|
|
const geometry = new THREE.BufferGeometry();
|
|
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
|
|
geometry.setAttribute('opacity', new THREE.BufferAttribute(opacities, 1));
|
|
|
|
const material = new THREE.ShaderMaterial({
|
|
uniforms: {},
|
|
vertexShader: StarVertexShader,
|
|
fragmentShader: StarFragmentShader,
|
|
transparent: true,
|
|
depthWrite: false,
|
|
blending: THREE.AdditiveBlending
|
|
});
|
|
|
|
this.stars = new THREE.Points(geometry, material);
|
|
this.scene.add(this.stars);
|
|
}
|
|
|
|
createAuroras() {
|
|
// 创建4条极光带,各有不同的参数
|
|
const auroraConfigs = [
|
|
{
|
|
color1: new THREE.Color(0x00ff88), // 绿色
|
|
color2: new THREE.Color(0x00ccaa), // 青绿
|
|
intensity: 0.7,
|
|
speed: 0.3,
|
|
offset: 0.0,
|
|
yPos: 0.8
|
|
},
|
|
{
|
|
color1: new THREE.Color(0x00ffaa), // 浅绿
|
|
color2: new THREE.Color(0x9d4edd), // 紫色
|
|
intensity: 0.6,
|
|
speed: 0.25,
|
|
offset: 0.3,
|
|
yPos: 1.0
|
|
},
|
|
{
|
|
color1: new THREE.Color(0x00ff66), // 翠绿
|
|
color2: new THREE.Color(0x7b2cbf), // 深紫
|
|
intensity: 0.5,
|
|
speed: 0.35,
|
|
offset: 0.6,
|
|
yPos: 1.2
|
|
},
|
|
{
|
|
color1: new THREE.Color(0x88ffaa), // 淡绿
|
|
color2: new THREE.Color(0xc77dff), // 淡紫
|
|
intensity: 0.4,
|
|
speed: 0.2,
|
|
offset: 0.9,
|
|
yPos: 0.6
|
|
}
|
|
];
|
|
|
|
auroraConfigs.forEach((config, index) => {
|
|
const geometry = new THREE.PlaneGeometry(12, 4, 64, 32);
|
|
|
|
const material = new THREE.ShaderMaterial({
|
|
uniforms: {
|
|
uTime: { value: 0 },
|
|
uResolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
|
|
uIntensity: { value: config.intensity },
|
|
uColor1: { value: config.color1 },
|
|
uColor2: { value: config.color2 },
|
|
uSpeed: { value: config.speed },
|
|
uOffset: { value: config.offset }
|
|
},
|
|
vertexShader: AuroraVertexShader,
|
|
fragmentShader: AuroraFragmentShader,
|
|
transparent: true,
|
|
depthWrite: false,
|
|
side: THREE.DoubleSide,
|
|
blending: THREE.AdditiveBlending
|
|
});
|
|
|
|
const aurora = new THREE.Mesh(geometry, material);
|
|
aurora.position.set(0, config.yPos, -2 - index * 0.5);
|
|
aurora.rotation.x = -0.2;
|
|
|
|
this.auroras.push(aurora);
|
|
this.scene.add(aurora);
|
|
});
|
|
}
|
|
|
|
createMountains() {
|
|
// 创建山脉轮廓
|
|
const mountainPoints = [];
|
|
const segments = 100;
|
|
|
|
for (let i = 0; i <= segments; i++) {
|
|
const x = (i / segments) * 20 - 10;
|
|
|
|
// 使用多个正弦波创建自然的山脉轮廓
|
|
let y = 0;
|
|
y += Math.sin(x * 0.3) * 0.8;
|
|
y += Math.sin(x * 0.7 + 1.0) * 0.4;
|
|
y += Math.sin(x * 1.2 + 2.0) * 0.2;
|
|
y += Math.sin(x * 2.5 + 0.5) * 0.1;
|
|
|
|
// 添加一些随机变化
|
|
y += Math.sin(x * 5.0) * 0.05;
|
|
|
|
mountainPoints.push(new THREE.Vector2(x, y));
|
|
}
|
|
|
|
// 创建地面
|
|
const groundShape = new THREE.Shape();
|
|
groundShape.moveTo(-10, -2);
|
|
|
|
// 添加山脉轮廓
|
|
mountainPoints.forEach((point, i) => {
|
|
if (i === 0) {
|
|
groundShape.moveTo(point.x, point.y);
|
|
} else {
|
|
groundShape.lineTo(point.x, point.y);
|
|
}
|
|
});
|
|
|
|
groundShape.lineTo(10, -2);
|
|
groundShape.lineTo(-10, -2);
|
|
|
|
const geometry = new THREE.ShapeGeometry(groundShape);
|
|
const material = new THREE.MeshBasicMaterial({
|
|
color: 0x000000,
|
|
side: THREE.DoubleSide
|
|
});
|
|
|
|
this.mountains = new THREE.Mesh(geometry, material);
|
|
this.mountains.position.z = -1;
|
|
this.scene.add(this.mountains);
|
|
|
|
// 添加第二层远山(较淡)
|
|
const farMountainPoints = [];
|
|
for (let i = 0; i <= segments; i++) {
|
|
const x = (i / segments) * 25 - 12.5;
|
|
let y = Math.sin(x * 0.2) * 1.2 + Math.sin(x * 0.5) * 0.6;
|
|
y += 1.5;
|
|
farMountainPoints.push(new THREE.Vector2(x, y));
|
|
}
|
|
|
|
const farShape = new THREE.Shape();
|
|
farMountainPoints.forEach((point, i) => {
|
|
if (i === 0) farShape.moveTo(point.x, point.y);
|
|
else farShape.lineTo(point.x, point.y);
|
|
});
|
|
farShape.lineTo(15, -2);
|
|
farShape.lineTo(-15, -2);
|
|
|
|
const farGeometry = new THREE.ShapeGeometry(farShape);
|
|
const farMaterial = new THREE.MeshBasicMaterial({
|
|
color: 0x05101a,
|
|
side: THREE.DoubleSide
|
|
});
|
|
|
|
const farMountains = new THREE.Mesh(farGeometry, farMaterial);
|
|
farMountains.position.z = -3;
|
|
this.scene.add(farMountains);
|
|
}
|
|
|
|
onResize() {
|
|
this.camera.aspect = window.innerWidth / window.innerHeight;
|
|
this.camera.updateProjectionMatrix();
|
|
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
|
|
|
// 更新uniforms
|
|
this.auroras.forEach(aurora => {
|
|
aurora.material.uniforms.uResolution.value.set(
|
|
window.innerWidth,
|
|
window.innerHeight
|
|
);
|
|
});
|
|
}
|
|
|
|
onMouseMove(event) {
|
|
this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
|
this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
|
this.targetRotation = this.mouse.x * 0.1;
|
|
}
|
|
|
|
updateFPS() {
|
|
this.frameCount++;
|
|
const currentTime = performance.now();
|
|
|
|
if (currentTime - this.lastTime >= 1000) {
|
|
const fps = this.frameCount;
|
|
this.fpsElement.textContent = `FPS: ${fps}`;
|
|
this.frameCount = 0;
|
|
this.lastTime = currentTime;
|
|
}
|
|
}
|
|
|
|
animate() {
|
|
requestAnimationFrame(this.animate.bind(this));
|
|
|
|
const elapsedTime = this.clock.getElapsedTime();
|
|
|
|
// 更新极光
|
|
this.auroras.forEach((aurora, index) => {
|
|
aurora.material.uniforms.uTime.value = elapsedTime;
|
|
|
|
// 轻微的摆动
|
|
aurora.position.x = Math.sin(elapsedTime * 0.1 + index) * 0.2;
|
|
});
|
|
|
|
// 星星闪烁
|
|
if (this.stars) {
|
|
this.stars.rotation.y = elapsedTime * 0.005;
|
|
}
|
|
|
|
// 相机缓慢移动
|
|
this.camera.position.x += (this.targetRotation - this.camera.position.x) * 0.02;
|
|
this.camera.lookAt(0, 0.5, 0);
|
|
|
|
// 更新FPS
|
|
this.updateFPS();
|
|
|
|
// 渲染
|
|
this.renderer.render(this.scene, this.camera);
|
|
}
|
|
}
|
|
|
|
// 启动应用
|
|
window.addEventListener('load', () => {
|
|
new AuroraApp();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|