<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>从单调到绚丽的3D世界</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background: #000;
font-family: Arial, sans-serif;
}
#container {
position: relative;
width: 100vw;
height: 100vh;
}
#startScreen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: #000;
z-index: 100;
transition: opacity 2s ease-in-out;
}
#title {
color: white;
font-size: 36px;
margin-bottom: 30px;
text-align: center;
}
.mode-button {
padding: 15px 30px;
font-size: 20px;
background: #fff;
border: none;
border-radius: 50px;
cursor: pointer;
color: #000;
font-weight: bold;
margin: 15px;
transition: all 0.3s ease;
width: 250px;
}
.mode-button:hover {
transform: scale(1.1);
box-shadow: 0 0 20px rgba(255, 255, 255, 0.5);
}
canvas {
display: block;
}
#info {
position: absolute;
bottom: 20px;
left: 20px;
color: white;
font-family: Arial, sans-serif;
font-size: 14px;
z-index: 10;
opacity: 0.7;
}
#modeInfo {
position: absolute;
top: 20px;
left: 20px;
color: white;
font-family: Arial, sans-serif;
font-size: 16px;
z-index: 10;
background: rgba(0,0,0,0.5);
padding: 10px;
border-radius: 5px;
}
#controls {
position: absolute;
bottom: 20px;
right: 20px;
color: white;
font-family: Arial, sans-serif;
font-size: 14px;
z-index: 10;
background: rgba(0,0,0,0.5);
padding: 10px;
border-radius: 5px;
text-align: center;
}
#musicInfo {
position: absolute;
top: 20px;
right: 20px;
color: white;
font-family: Arial, sans-serif;
font-size: 12px;
z-index: 10;
background: rgba(0,0,0,0.5);
padding: 10px;
border-radius: 5px;
max-width: 200px;
}
</style>
</head>
<body>
<div id="container">
<div id="startScreen">
<div id="title">探索深邃的3D世界</div>
<button class="mode-button" id="freeMode">自由探索模式</button>
<button class="mode-button" id="touchMode">触控移动模式</button>
</div>
<!-- Three.js 将在这里渲染 -->
</div>
<div id="modeInfo" style="display: none;">
<span id="currentMode">当前模式: </span>
</div>
<div id="controls" style="display: none;">
<div id="freeControls">
自动旋转<br>
按住鼠标拖拽旋转
</div>
<div id="touchControls" style="display: none;">
滑动屏幕移动视角<br>
双指缩放
</div>
</div>
<div id="musicInfo">
背景音乐: <span id="musicTitle">深邃夜空</span><br>
来源: freesound.org [[8]]
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// 初始化变量
let scene, camera, renderer;
let particles, geometry, material;
let startScreen, freeModeButton, touchModeButton;
let animationId;
let time = 0;
let isTransitioning = false;
let currentMode = 'free'; // 'free' or 'touch'
// 相机控制变量
let isMouseDown = false;
let mouseX = 0, mouseY = 0;
let targetRotationX = 0, targetRotationY = 0;
let rotationX = 0, rotationY = 0;
// 触控变量
let touchStartX = 0, touchStartY = 0;
let touchStartDistance = 0;
let cameraDistance = 5;
// 音频元素
let audio;
// 初始化场景
function init() {
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
// 创建相机
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = cameraDistance;
// 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.getElementById('container').appendChild(renderer.domElement);
// 创建粒子系统
createParticleSystem();
// 添加光源
const ambientLight = new THREE.AmbientLight(0x333333);
scene.add(ambientLight);
const pointLight = new THREE.PointLight(0xffffff, 1, 100);
pointLight.position.set(10, 10, 10);
scene.add(pointLight);
// 隐藏开始屏幕
startScreen = document.getElementById('startScreen');
freeModeButton = document.getElementById('freeMode');
touchModeButton = document.getElementById('touchMode');
freeModeButton.addEventListener('click', () => startTransition('free'));
touchModeButton.addEventListener('click', () => startTransition('touch'));
// 添加事件监听器
addEventListeners();
// 窗口大小调整
window.addEventListener('resize', onWindowResize);
// 初始化音频
initAudio();
}
// 初始化音频
function initAudio() {
// 这里使用一个深邃的背景音乐示例
// 在实际应用中,你可能需要使用一个真实的音乐文件URL
// 根据搜索结果,可以选择类似"深邃的夜"这样的爵士乐作为背景音乐 [[8]]
audio = new Audio();
audio.loop = true;
// 由于版权问题,这里不直接嵌入音乐文件
// 在实际使用时,你可以替换为一个合适的免费音乐文件URL
// 例如:https://example.com/path/to/your/deep-space-music.mp3
console.log("背景音乐已准备就绪");
}
// 创建粒子系统
function createParticleSystem() {
const particleCount = 20000;
geometry = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);
const originalPositions = new Float32Array(particleCount * 3); // 保存原始位置
for (let i = 0; i < particleCount * 3; i += 3) {
// 随机位置 (使用球形分布使效果更好)
const radius = Math.random() * 10;
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(2 * Math.random() - 1);
originalPositions[i] = radius * Math.sin(phi) * Math.cos(theta);
originalPositions[i + 1] = radius * Math.sin(phi) * Math.sin(theta);
originalPositions[i + 2] = radius * Math.cos(phi);
// 复制到当前位置
positions[i] = originalPositions[i];
positions[i + 1] = originalPositions[i + 1];
positions[i + 2] = originalPositions[i + 2];
// 初始颜色(灰色)
colors[i] = 0.5;
colors[i + 1] = 0.5;
colors[i + 2] = 0.5;
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
// 保存原始位置以便后续使用
geometry.setAttribute('originalPosition', new THREE.BufferAttribute(originalPositions, 3));
material = new THREE.PointsMaterial({
size: 0.1,
vertexColors: true,
transparent: true,
opacity: 0.8
});
particles = new THREE.Points(geometry, material);
scene.add(particles);
}
// 添加事件监听器
function addEventListeners() {
// 鼠标事件
renderer.domElement.addEventListener('mousedown', onMouseDown);
renderer.domElement.addEventListener('mousemove', onMouseMove);
renderer.domElement.addEventListener('mouseup', onMouseUp);
renderer.domElement.addEventListener('wheel', onMouseWheel);
// 触控事件
renderer.domElement.addEventListener('touchstart', onTouchStart);
renderer.domElement.addEventListener('touchmove', onTouchMove);
renderer.domElement.addEventListener('touchend', onTouchEnd);
}
// 鼠标事件处理
function onMouseDown(event) {
isMouseDown = true;
mouseX = event.clientX;
mouseY = event.clientY;
}
function onMouseMove(event) {
if (isMouseDown && currentMode === 'touch') {
const deltaX = event.clientX - mouseX;
const deltaY = event.clientY - mouseY;
targetRotationY += deltaX * 0.01;
targetRotationX += deltaY * 0.01;
mouseX = event.clientX;
mouseY = event.clientY;
}
}
function onMouseUp() {
isMouseDown = false;
}
function onMouseWheel(event) {
if (currentMode === 'touch') {
cameraDistance -= event.deltaY * 0.01;
cameraDistance = Math.max(1, Math.min(20, cameraDistance));
}
}
// 触控事件处理
function onTouchStart(event) {
if (currentMode !== 'touch') return;
if (event.touches.length === 1) {
touchStartX = event.touches[0].clientX;
touchStartY = event.touches[0].clientY;
} else if (event.touches.length === 2) {
const dx = event.touches[0].clientX - event.touches[1].clientX;
const dy = event.touches[0].clientY - event.touches[1].clientY;
touchStartDistance = Math.sqrt(dx * dx + dy * dy);
}
event.preventDefault();
}
function onTouchMove(event) {
if (currentMode !== 'touch') return;
if (event.touches.length === 1) {
const deltaX = event.touches[0].clientX - touchStartX;
const deltaY = event.touches[0].clientY - touchStartY;
targetRotationY += deltaX * 0.01;
targetRotationX += deltaY * 0.01;
touchStartX = event.touches[0].clientX;
touchStartY = event.touches[0].clientY;
} else if (event.touches.length === 2) {
const dx = event.touches[0].clientX - event.touches[1].clientX;
const dy = event.touches[0].clientY - event.touches[1].clientY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (touchStartDistance > 0) {
const deltaDistance = distance - touchStartDistance;
cameraDistance -= deltaDistance * 0.05;
cameraDistance = Math.max(1, Math.min(20, cameraDistance));
}
touchStartDistance = distance;
}
event.preventDefault();
}
function onTouchEnd() {
if (currentMode !== 'touch') return;
touchStartDistance = 0;
}
// 开始过渡动画
function startTransition(mode) {
if (isTransitioning) return;
isTransitioning = true;
currentMode = mode;
// 更新UI
document.getElementById('currentMode').textContent =
mode === 'free' ? '当前模式: 自由探索模式' : '当前模式: 触控移动模式';
// 显示控制说明
document.getElementById('modeInfo').style.display = 'block';
document.getElementById('controls').style.display = 'block';
if (mode === 'touch') {
document.getElementById('touchControls').style.display = 'block';
document.getElementById('freeControls').style.display = 'none';
} else {
document.getElementById('touchControls').style.display = 'none';
document.getElementById('freeControls').style.display = 'block';
}
// 开始过渡
startScreen.style.opacity = '0';
setTimeout(() => {
startScreen.style.display = 'none';
// 播放背景音乐
// audio.play(); // 在实际应用中取消注释
animate();
}, 2000);
}
// 动画循环
function animate() {
animationId = requestAnimationFrame(animate);
time += 0.01;
// 更新粒子系统
updateParticles();
// 更新相机位置
updateCamera();
// 渲染场景
renderer.render(scene, camera);
}
// 更新相机位置
function updateCamera() {
if (currentMode === 'free') {
// 自由探索模式 - 自动旋转
targetRotationY += 0.005;
targetRotationX = Math.sin(time * 0.3) * 0.3;
}
// 平滑插值旋转
rotationX += (targetRotationX - rotationX) * 0.1;
rotationY += (targetRotationY - rotationY) * 0.1;
// 应用旋转和距离
camera.position.x = cameraDistance * Math.sin(rotationY) * Math.cos(rotationX);
camera.position.y = cameraDistance * Math.sin(rotationX);
camera.position.z = cameraDistance * Math.cos(rotationY) * Math.cos(rotationX);
camera.lookAt(scene.position);
}
// 更新粒子系统
function updateParticles() {
const positions = geometry.attributes.position.array;
const colors = geometry.attributes.color.array;
const originalPositions = geometry.attributes.originalPosition.array;
const particleCount = positions.length / 3;
for (let i = 0; i < particleCount; i++) {
const i3 = i * 3;
// 基于原始位置的平滑偏移,而不是随机抖动
const offsetTime = time + i * 0.001;
positions[i3] = originalPositions[i3] + Math.sin(offsetTime) * 0.5;
positions[i3 + 1] = originalPositions[i3 + 1] + Math.cos(offsetTime * 0.7) * 0.5;
positions[i3 + 2] = originalPositions[i3 + 2] + Math.sin(offsetTime * 0.5) * 0.5;
// 颜色变化
const colorTime = time + i * 0.005;
colors[i3] = Math.abs(Math.sin(colorTime)) * 0.8 + 0.2; // R
colors[i3 + 1] = Math.abs(Math.sin(colorTime + 2)) * 0.8 + 0.2; // G
colors[i3 + 2] = Math.abs(Math.sin(colorTime + 4)) * 0.8 + 0.2; // B
}
geometry.attributes.position.needsUpdate = true;
geometry.attributes.color.needsUpdate = true;
}
// 窗口大小调整
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// 启动应用
init();
</script>
</body>
</html>
index.html
index.html