Files
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

469 lines
18 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>夏夜萤火虫 - Fireflies</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
background: #000;
}
#canvas {
display: block;
}
#stats {
position: absolute;
top: 20px;
left: 20px;
color: #ccff00;
font-family: 'Courier New', monospace;
font-size: 14px;
text-shadow: 0 0 10px #ccff00, 0 0 20px #ccff00;
z-index: 100;
pointer-events: none;
line-height: 1.8;
}
#stats span {
display: block;
}
.glow-text {
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.8; }
50% { opacity: 1; }
}
</style>
</head>
<body>
<div id="stats">
<span class="glow-text">FPS: <span id="fps">0</span></span>
<span class="glow-text">Fireflies: <span id="count">0</span></span>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// 场景设置
const scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x0a0a1a, 0.015);
scene.background = new THREE.Color(0x050510);
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 5, 30);
camera.lookAt(0, 5, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
// 萤火虫配置
const FIREFLY_COUNT = 650;
const STATE_RESTING = 0;
const STATE_FLYING = 1;
// 创建萤火虫纹理
function createFireflyTexture() {
const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
const ctx = canvas.getContext('2d');
// 径向渐变 - 发光效果
const gradient = ctx.createRadialGradient(32, 32, 0, 32, 32, 30);
gradient.addColorStop(0, 'rgba(204, 255, 0, 1)');
gradient.addColorStop(0.3, 'rgba(170, 255, 0, 0.8)');
gradient.addColorStop(0.6, 'rgba(170, 255, 0, 0.3)');
gradient.addColorStop(1, 'rgba(170, 255, 0, 0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 64, 64);
return new THREE.CanvasTexture(canvas);
}
const fireflyTexture = createFireflyTexture();
// 萤火虫材质
const fireflyMaterial = new THREE.SpriteMaterial({
map: fireflyTexture,
color: 0xccff00,
transparent: true,
opacity: 1,
blending: THREE.AdditiveBlending,
depthWrite: false
});
// 萤火虫类
class Firefly {
constructor() {
this.sprite = new THREE.Sprite(fireflyMaterial.clone());
this.reset();
scene.add(this.sprite);
}
reset() {
// 随机位置 - 分布在场景中
this.position = new THREE.Vector3(
(Math.random() - 0.5) * 60,
Math.random() * 15 + 0.5,
(Math.random() - 0.5) * 40 - 5
);
this.sprite.position.copy(this.position);
// 缩放 - 远处的小一点
const depthScale = 1 - (this.position.z + 25) / 50;
this.baseScale = 0.3 + Math.random() * 0.4 * depthScale;
this.sprite.scale.set(this.baseScale, this.baseScale, 1);
// 颜色变化
const colorChoice = Math.random() > 0.5 ? 0xccff00 : 0xaaff00;
this.sprite.material.color.setHex(colorChoice);
// 闪烁参数
this.blinkPhase = Math.random() * Math.PI * 2;
this.blinkSpeed = 0.5 + Math.random() * 1.5;
this.baseOpacity = 0.3 + Math.random() * 0.7;
// 状态
this.state = Math.random() > 0.3 ? STATE_RESTING : STATE_FLYING;
this.stateTimer = Math.random() * 5;
this.stateDuration = 3 + Math.random() * 6;
// 飞行参数
this.velocity = new THREE.Vector3(
(Math.random() - 0.5) * 0.02,
(Math.random() - 0.5) * 0.02,
(Math.random() - 0.5) * 0.01
);
// 噪声偏移
this.noiseOffset = new THREE.Vector3(
Math.random() * 100,
Math.random() * 100,
Math.random() * 100
);
// 停留目标位置
this.restTarget = this.position.clone();
this.findRestTarget();
}
findRestTarget() {
// 寻找附近的树枝/草丛位置作为停留点
const angle = Math.random() * Math.PI * 2;
const radius = 5 + Math.random() * 15;
const height = Math.random() * 8;
this.restTarget.set(
Math.cos(angle) * radius + (Math.random() - 0.5) * 10,
height * 0.5 + 0.3,
Math.sin(angle) * radius * 0.5 - 5 + (Math.random() - 0.5) * 10
);
}
// 简单的噪声函数
noise(x, y, z, time) {
return Math.sin(x + time) * Math.cos(y + time * 0.7) * Math.sin(z + time * 0.3);
}
update(deltaTime, time) {
// 更新状态计时器
this.stateTimer += deltaTime;
// 状态切换
if (this.stateTimer > this.stateDuration) {
this.stateTimer = 0;
this.stateDuration = 2 + Math.random() * 8;
if (this.state === STATE_RESTING) {
this.state = STATE_FLYING;
this.findRestTarget(); // 找新的飞行目标
} else {
this.state = STATE_RESTING;
this.findRestTarget();
}
}
// 根据状态更新位置
if (this.state === STATE_FLYING) {
// 飞行状态 - 使用噪声产生自然飞舞
const noiseX = this.noise(this.noiseOffset.x, 0, 0, time) * 0.03;
const noiseY = this.noise(0, this.noiseOffset.y, 0, time) * 0.02;
const noiseZ = this.noise(0, 0, this.noiseOffset.z, time) * 0.015;
// 向目标飞行的倾向
const toTarget = new THREE.Vector3().subVectors(this.restTarget, this.position);
toTarget.multiplyScalar(0.005);
this.velocity.x += noiseX + toTarget.x * deltaTime;
this.velocity.y += noiseY + toTarget.y * deltaTime;
this.velocity.z += noiseZ + toTarget.z * deltaTime;
// 阻尼
this.velocity.multiplyScalar(0.98);
// 限制速度
this.velocity.clampLength(0, 0.08);
this.position.add(this.velocity);
// 边界限制
this.position.x = THREE.MathUtils.clamp(this.position.x, -30, 30);
this.position.y = THREE.MathUtils.clamp(this.position.y, 0.5, 18);
this.position.z = THREE.MathUtils.clamp(this.position.z, -25, 15);
} else {
// 停留状态 - 几乎不动,轻微漂浮
const floatX = Math.sin(time * 2 + this.blinkPhase) * 0.002;
const floatY = Math.cos(time * 1.5 + this.blinkPhase) * 0.003;
this.position.x += floatX;
this.position.y += floatY;
// 轻柔地趋向停留点
const toRest = new THREE.Vector3().subVectors(this.restTarget, this.position);
toRest.multiplyScalar(0.01);
this.position.add(toRest);
}
this.sprite.position.copy(this.position);
// 闪烁效果 - 使用透明度
const blink = Math.sin(time * this.blinkSpeed + this.blinkPhase);
let opacity = this.baseOpacity * (0.4 + 0.6 * (blink * 0.5 + 0.5));
// 飞行时闪烁更快
if (this.state === STATE_FLYING) {
opacity *= 0.8 + 0.4 * Math.sin(time * 3 + this.blinkPhase);
}
this.sprite.material.opacity = THREE.MathUtils.clamp(opacity, 0.1, 1);
// 根据透明度微调大小(闪烁时稍微变大)
const scalePulse = 1 + opacity * 0.2;
this.sprite.scale.set(
this.baseScale * scalePulse,
this.baseScale * scalePulse,
1
);
// 更新噪声偏移
this.noiseOffset.x += deltaTime * 0.5;
this.noiseOffset.y += deltaTime * 0.3;
this.noiseOffset.z += deltaTime * 0.2;
}
}
// 创建萤火虫群
const fireflies = [];
for (let i = 0; i < FIREFLY_COUNT; i++) {
fireflies.push(new Firefly());
}
// 创建背景树木剪影
function createTreeSilhouettes() {
const treeGroup = new THREE.Group();
// 创建树的形状
function createTree(x, z, scale) {
const treeCanvas = document.createElement('canvas');
treeCanvas.width = 128;
treeCanvas.height = 256;
const ctx = treeCanvas.getContext('2d');
// 渐变深色
const gradient = ctx.createLinearGradient(0, 0, 0, 256);
gradient.addColorStop(0, '#0a1520');
gradient.addColorStop(0.5, '#0d1a25');
gradient.addColorStop(1, '#051015');
ctx.fillStyle = gradient;
// 绘制树冠轮廓(不规则形状)
ctx.beginPath();
ctx.moveTo(64, 20);
ctx.lineTo(100, 80);
ctx.lineTo(110, 140);
ctx.lineTo(90, 180);
ctx.lineTo(100, 220);
ctx.lineTo(70, 240);
ctx.lineTo(30, 220);
ctx.lineTo(40, 180);
ctx.lineTo(20, 140);
ctx.lineTo(30, 80);
ctx.closePath();
ctx.fill();
// 树干
ctx.fillStyle = '#030508';
ctx.fillRect(54, 220, 20, 36);
const texture = new THREE.CanvasTexture(treeCanvas);
const material = new THREE.SpriteMaterial({
map: texture,
transparent: true,
opacity: 0.9,
color: 0x333344
});
const sprite = new THREE.Sprite(material);
sprite.position.set(x, 8 * scale, z);
sprite.scale.set(6 * scale, 12 * scale, 1);
return sprite;
}
// 创建多棵树
const treePositions = [
[-25, -20, 1.5], [-15, -25, 1.2], [-5, -22, 1.8],
[8, -28, 1.3], [20, -24, 1.6], [28, -18, 1.1],
[-30, -10, 1.4], [32, -12, 1.3], [-22, 5, 1.0],
[25, 8, 1.2], [-10, -5, 0.9], [12, -8, 1.1],
[-35, 2, 1.3], [35, -5, 1.2]
];
treePositions.forEach(pos => {
const tree = createTree(pos[0], pos[1], pos[2]);
treeGroup.add(tree);
});
// 添加地面雾层
const groundCanvas = document.createElement('canvas');
groundCanvas.width = 256;
groundCanvas.height = 64;
const gctx = groundCanvas.getContext('2d');
const ggradient = gctx.createLinearGradient(0, 0, 0, 64);
ggradient.addColorStop(0, 'rgba(5, 10, 15, 0)');
ggradient.addColorStop(0.5, 'rgba(8, 15, 22, 0.6)');
ggradient.addColorStop(1, 'rgba(10, 20, 30, 0.95)');
gctx.fillStyle = ggradient;
gctx.fillRect(0, 0, 256, 64);
const groundTexture = new THREE.CanvasTexture(groundCanvas);
const groundMaterial = new THREE.SpriteMaterial({
map: groundTexture,
transparent: true
});
const ground = new THREE.Sprite(groundMaterial);
ground.position.set(0, -2, -10);
ground.scale.set(80, 15, 1);
treeGroup.add(ground);
return treeGroup;
}
const trees = createTreeSilhouettes();
scene.add(trees);
// 添加星空背景
function createStars() {
const starGeometry = new THREE.BufferGeometry();
const starCount = 200;
const positions = new Float32Array(starCount * 3);
const sizes = new Float32Array(starCount);
for (let i = 0; i < starCount; i++) {
positions[i * 3] = (Math.random() - 0.5) * 100;
positions[i * 3 + 1] = 15 + Math.random() * 30;
positions[i * 3 + 2] = -30 - Math.random() * 20;
sizes[i] = Math.random() * 2;
}
starGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
starGeometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
const starMaterial = new THREE.PointsMaterial({
color: 0x88aaff,
size: 0.3,
transparent: true,
opacity: 0.8,
sizeAttenuation: true
});
return new THREE.Points(starGeometry, starMaterial);
}
const stars = createStars();
scene.add(stars);
// 添加月光效果
const moonLight = new THREE.DirectionalLight(0x445577, 0.3);
moonLight.position.set(20, 40, -20);
scene.add(moonLight);
// 环境光
const ambientLight = new THREE.AmbientLight(0x0a1525, 0.4);
scene.add(ambientLight);
// 统计数据
let lastTime = 0;
let frameCount = 0;
let fps = 0;
const fpsElement = document.getElementById('fps');
const countElement = document.getElementById('count');
countElement.textContent = FIREFLY_COUNT;
// 动画循环
function animate(currentTime) {
requestAnimationFrame(animate);
const deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;
// 计算FPS
frameCount++;
if (frameCount % 30 === 0) {
fps = Math.round(1000 / ((currentTime - (animate.lastFpsTime || currentTime)) / 30));
animate.lastFpsTime = currentTime;
fpsElement.textContent = fps;
}
const time = currentTime / 1000;
// 更新所有萤火虫
fireflies.forEach(firefly => {
firefly.update(0.016, time);
});
// 缓慢旋转星空
stars.rotation.y = time * 0.005;
// 轻微相机移动
camera.position.x = Math.sin(time * 0.1) * 2;
camera.position.y = 5 + Math.cos(time * 0.08) * 1;
camera.lookAt(0, 6, 0);
renderer.render(scene, camera);
}
// 窗口大小调整
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// 鼠标交互 - 萤火虫轻微躲避
let mouseX = 0, mouseY = 0;
window.addEventListener('mousemove', (e) => {
mouseX = (e.clientX / window.innerWidth - 0.5) * 2;
mouseY = (e.clientY / window.innerHeight - 0.5) * 2;
});
animate(0);
</script>
</body>
</html>