This commit is contained in:
2026-03-25 17:28:56 +08:00
commit e17b09ff6d
13 changed files with 2466 additions and 0 deletions

305
src/OceanScene.js Normal file
View File

@@ -0,0 +1,305 @@
import * as THREE from 'three';
import { Water } from 'three/addons/objects/Water.js';
import { Sky } from 'three/addons/objects/Sky.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { TerrainGenerator } from './TerrainGenerator.js';
import { VegetationSystem } from './VegetationSystem.js';
export class OceanScene {
constructor(container) {
this.container = container;
this.scene = null;
this.camera = null;
this.renderer = null;
this.controls = null;
this.water = null;
this.sky = null;
this.sun = new THREE.Vector3();
this.terrain = null;
this.vegetation = null;
this.pmremGenerator = null;
this.renderTarget = null;
this.sunLight = null;
this.params = {
elevation: 2,
azimuth: 180,
exposure: 0.5,
turbidity: 10,
rayleigh: 2,
mieCoefficient: 0.005,
mieDirectionalG: 0.8
};
this.clock = new THREE.Clock();
this.frameCount = 0;
this.lastTime = performance.now();
}
async init() {
this.initRenderer();
this.initScene();
this.initCamera();
this.initControls();
this.initLighting();
await this.initSky();
await this.initWater();
await this.initTerrain();
await this.initVegetation();
this.initSunPosition();
this.initEventListeners();
}
initRenderer() {
this.renderer = new THREE.WebGLRenderer({
antialias: true,
powerPreference: 'high-performance'
});
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
this.renderer.toneMappingExposure = this.params.exposure;
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
this.container.appendChild(this.renderer.domElement);
}
initScene() {
this.scene = new THREE.Scene();
this.scene.fog = new THREE.FogExp2(0x8cb8d4, 0.0008);
}
initCamera() {
this.camera = new THREE.PerspectiveCamera(
60,
window.innerWidth / window.innerHeight,
1,
20000
);
this.camera.position.set(100, 50, 200);
}
initControls() {
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.05;
this.controls.maxPolarAngle = Math.PI * 0.48;
this.controls.minDistance = 30;
this.controls.maxDistance = 1000;
this.controls.target.set(0, 10, 0);
this.controls.update();
}
initLighting() {
const ambientLight = new THREE.AmbientLight(0x555555);
this.scene.add(ambientLight);
this.sunLight = new THREE.DirectionalLight(0xffffff, 1.5);
this.sunLight.castShadow = true;
this.sunLight.shadow.mapSize.width = 2048;
this.sunLight.shadow.mapSize.height = 2048;
this.sunLight.shadow.camera.near = 0.5;
this.sunLight.shadow.camera.far = 500;
this.sunLight.shadow.camera.left = -100;
this.sunLight.shadow.camera.right = 100;
this.sunLight.shadow.camera.top = 100;
this.sunLight.shadow.camera.bottom = -100;
this.scene.add(this.sunLight);
}
async initSky() {
this.sky = new Sky();
this.sky.scale.setScalar(10000);
this.scene.add(this.sky);
const skyUniforms = this.sky.material.uniforms;
skyUniforms['turbidity'].value = this.params.turbidity;
skyUniforms['rayleigh'].value = this.params.rayleigh;
skyUniforms['mieCoefficient'].value = this.params.mieCoefficient;
skyUniforms['mieDirectionalG'].value = this.params.mieDirectionalG;
this.pmremGenerator = new THREE.PMREMGenerator(this.renderer);
}
async initWater() {
const waterGeometry = new THREE.PlaneGeometry(10000, 10000, 128, 128);
const waterNormals = await new Promise((resolve) => {
new THREE.TextureLoader().load(
'https://threejs.org/examples/textures/waternormals.jpg',
(texture) => {
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
resolve(texture);
}
);
});
this.water = new Water(waterGeometry, {
textureWidth: 512,
textureHeight: 512,
waterNormals: waterNormals,
sunDirection: new THREE.Vector3(),
sunColor: 0xffffff,
waterColor: 0x001e0f,
distortionScale: 3.7,
fog: true
});
this.water.rotation.x = -Math.PI / 2;
this.water.position.y = 0;
this.scene.add(this.water);
}
async initTerrain() {
const terrainGen = new TerrainGenerator({
size: 1200,
segments: 200,
maxHeight: 30,
waterLevel: 0,
seed: 42
});
this.terrain = terrainGen.generate();
this.scene.add(this.terrain);
this.terrainGenerator = terrainGen;
}
async initVegetation() {
const vegSystem = new VegetationSystem(this.terrainGenerator, {
grassCount: 30000,
treeCount: 300,
terrainSize: 1200,
waterLevel: 1
});
this.vegetation = vegSystem.generate();
vegSystem.addToScene(this.scene);
}
initSunPosition() {
this.updateSun();
}
updateSun() {
const phi = THREE.MathUtils.degToRad(90 - this.params.elevation);
const theta = THREE.MathUtils.degToRad(this.params.azimuth);
this.sun.setFromSphericalCoords(1, phi, theta);
this.sky.material.uniforms['sunPosition'].value.copy(this.sun);
this.water.material.uniforms['sunDirection'].value.copy(this.sun).normalize();
if (this.sunLight) {
const sunDistance = 100;
this.sunLight.position.set(
this.sun.x * sunDistance,
this.sun.y * sunDistance,
this.sun.z * sunDistance
);
}
if (this.renderTarget) {
this.renderTarget.dispose();
}
const sceneEnv = new THREE.Scene();
sceneEnv.add(this.sky);
this.renderTarget = this.pmremGenerator.fromScene(sceneEnv);
this.scene.environment = this.renderTarget.texture;
this.scene.add(this.sky);
this.scene.fog.color.setHex(this.getFogColor());
}
getFogColor() {
const elevation = this.params.elevation;
if (elevation < 0) {
return 0x1a2a3a;
} else if (elevation < 10) {
return 0x4a5a6a;
} else if (elevation < 20) {
return 0x8cb8d4;
} else if (elevation < 45) {
return 0x9ec5db;
} else if (elevation < 70) {
return 0xb8d4e8;
} else {
return 0xd4e8f4;
}
}
initEventListeners() {
window.addEventListener('resize', () => this.onWindowResize());
}
onWindowResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
setSunElevation(value) {
this.params.elevation = value;
this.updateSun();
}
setSunAzimuth(value) {
this.params.azimuth = value;
this.updateSun();
}
setExposure(value) {
this.params.exposure = value;
this.renderer.toneMappingExposure = value;
}
setTurbidity(value) {
this.params.turbidity = value;
this.sky.material.uniforms['turbidity'].value = value;
this.updateSun();
}
setRayleigh(value) {
this.params.rayleigh = value;
this.sky.material.uniforms['rayleigh'].value = value;
this.updateSun();
}
animate() {
requestAnimationFrame(() => this.animate());
const time = this.clock.getElapsedTime();
if (this.water) {
this.water.material.uniforms['time'].value += 1.0 / 60.0;
}
this.controls.update();
this.renderer.render(this.scene, this.camera);
this.frameCount++;
const currentTime = performance.now();
if (currentTime - this.lastTime >= 1000) {
const fps = Math.round(this.frameCount * 1000 / (currentTime - this.lastTime));
const fpsElement = document.getElementById('fps');
if (fpsElement) {
fpsElement.textContent = fps;
}
this.frameCount = 0;
this.lastTime = currentTime;
}
}
hideLoading() {
const loading = document.getElementById('loading');
if (loading) {
loading.style.opacity = '0';
loading.style.transition = 'opacity 0.5s ease';
setTimeout(() => {
loading.style.display = 'none';
}, 500);
}
}
}