a
This commit is contained in:
@@ -0,0 +1,570 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user