加入云和辉光
This commit is contained in:
@@ -2,6 +2,9 @@ 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 { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
|
||||
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
|
||||
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
|
||||
import { TerrainGenerator } from './TerrainGenerator.js';
|
||||
import { VegetationSystem } from './VegetationSystem.js';
|
||||
|
||||
@@ -20,13 +23,24 @@ export class OceanScene {
|
||||
this.pmremGenerator = null;
|
||||
this.renderTarget = null;
|
||||
this.sunLight = null;
|
||||
this.composer = null;
|
||||
this.bloomPass = null;
|
||||
this.cloudGroup = null;
|
||||
this.cloudMaterials = [];
|
||||
this.cloudLayers = [];
|
||||
|
||||
this.params = {
|
||||
elevation: 2,
|
||||
elevation: 15,
|
||||
azimuth: 180,
|
||||
exposure: 0.5,
|
||||
turbidity: 10,
|
||||
rayleigh: 2,
|
||||
bloomStrength: 0.72,
|
||||
bloomRadius: 0.28,
|
||||
bloomThreshold: 0.82,
|
||||
cloudOpacity: 0.78,
|
||||
cloudCoverage: 0.72,
|
||||
cloudSpeed: 0.28,
|
||||
mieCoefficient: 0.005,
|
||||
mieDirectionalG: 0.8
|
||||
};
|
||||
@@ -42,7 +56,9 @@ export class OceanScene {
|
||||
this.initCamera();
|
||||
this.initControls();
|
||||
this.initLighting();
|
||||
this.initPostProcessing();
|
||||
await this.initSky();
|
||||
this.initClouds();
|
||||
await this.initWater();
|
||||
await this.initTerrain();
|
||||
await this.initVegetation();
|
||||
@@ -106,6 +122,19 @@ export class OceanScene {
|
||||
this.sunLight.shadow.camera.bottom = -100;
|
||||
this.scene.add(this.sunLight);
|
||||
}
|
||||
|
||||
initPostProcessing() {
|
||||
this.composer = new EffectComposer(this.renderer);
|
||||
this.composer.addPass(new RenderPass(this.scene, this.camera));
|
||||
|
||||
this.bloomPass = new UnrealBloomPass(
|
||||
new THREE.Vector2(window.innerWidth, window.innerHeight),
|
||||
this.params.bloomStrength,
|
||||
this.params.bloomRadius,
|
||||
this.params.bloomThreshold
|
||||
);
|
||||
this.composer.addPass(this.bloomPass);
|
||||
}
|
||||
|
||||
async initSky() {
|
||||
this.sky = new Sky();
|
||||
@@ -120,6 +149,103 @@ export class OceanScene {
|
||||
|
||||
this.pmremGenerator = new THREE.PMREMGenerator(this.renderer);
|
||||
}
|
||||
|
||||
initClouds() {
|
||||
const cloudTexture = this.createCloudTexture();
|
||||
this.cloudGroup = new THREE.Group();
|
||||
this.cloudGroup.position.y = -120;
|
||||
this.addCloudDomeLayer(cloudTexture, {
|
||||
radius: 4200,
|
||||
opacity: 0.42,
|
||||
repeatX: 5.5,
|
||||
repeatY: 2.4,
|
||||
speedX: 0.00045,
|
||||
speedY: 0.00012
|
||||
});
|
||||
this.addCloudDomeLayer(cloudTexture, {
|
||||
radius: 3900,
|
||||
opacity: 0.28,
|
||||
repeatX: 7.5,
|
||||
repeatY: 3.2,
|
||||
speedX: -0.00032,
|
||||
speedY: 0.00018
|
||||
});
|
||||
|
||||
this.scene.add(this.cloudGroup);
|
||||
this.updateClouds();
|
||||
}
|
||||
|
||||
addCloudDomeLayer(texture, config) {
|
||||
const layerTexture = texture.clone();
|
||||
layerTexture.wrapS = THREE.RepeatWrapping;
|
||||
layerTexture.wrapT = THREE.RepeatWrapping;
|
||||
layerTexture.repeat.set(config.repeatX, config.repeatY);
|
||||
layerTexture.needsUpdate = true;
|
||||
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: layerTexture,
|
||||
alphaMap: layerTexture,
|
||||
color: 0xffffff,
|
||||
transparent: true,
|
||||
opacity: config.opacity,
|
||||
side: THREE.BackSide,
|
||||
depthWrite: false,
|
||||
alphaTest: 0.08,
|
||||
fog: false
|
||||
});
|
||||
|
||||
const geometry = new THREE.SphereGeometry(
|
||||
config.radius,
|
||||
32,
|
||||
20,
|
||||
0,
|
||||
Math.PI * 2,
|
||||
0,
|
||||
Math.PI * 0.48
|
||||
);
|
||||
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
mesh.rotation.y = Math.random() * Math.PI * 2;
|
||||
this.cloudMaterials.push(material);
|
||||
this.cloudLayers.push({
|
||||
mesh,
|
||||
texture: layerTexture,
|
||||
speedX: config.speedX,
|
||||
speedY: config.speedY,
|
||||
baseOpacity: config.opacity
|
||||
});
|
||||
this.cloudGroup.add(mesh);
|
||||
}
|
||||
|
||||
createCloudTexture() {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 256;
|
||||
canvas.height = 256;
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
for (let i = 0; i < 28; i++) {
|
||||
const x = 28 + Math.random() * 200;
|
||||
const y = 52 + Math.random() * 152;
|
||||
const radius = 28 + Math.random() * 44;
|
||||
const gradient = context.createRadialGradient(x, y, 0, x, y, radius);
|
||||
|
||||
gradient.addColorStop(0, 'rgba(255,255,255,0.92)');
|
||||
gradient.addColorStop(0.28, 'rgba(255,255,255,0.78)');
|
||||
gradient.addColorStop(0.62, 'rgba(255,255,255,0.22)');
|
||||
gradient.addColorStop(1, 'rgba(255,255,255,0)');
|
||||
|
||||
context.fillStyle = gradient;
|
||||
context.fillRect(x - radius, y - radius, radius * 2, radius * 2);
|
||||
}
|
||||
|
||||
const texture = new THREE.CanvasTexture(canvas);
|
||||
texture.colorSpace = THREE.SRGBColorSpace;
|
||||
texture.needsUpdate = true;
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
async initWater() {
|
||||
const waterGeometry = new THREE.PlaneGeometry(10000, 10000, 128, 128);
|
||||
@@ -210,6 +336,7 @@ export class OceanScene {
|
||||
this.scene.add(this.sky);
|
||||
|
||||
this.scene.fog.color.setHex(this.getFogColor());
|
||||
this.updateClouds();
|
||||
}
|
||||
|
||||
getFogColor() {
|
||||
@@ -238,6 +365,9 @@ export class OceanScene {
|
||||
this.camera.aspect = window.innerWidth / window.innerHeight;
|
||||
this.camera.updateProjectionMatrix();
|
||||
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
if (this.composer) {
|
||||
this.composer.setSize(window.innerWidth, window.innerHeight);
|
||||
}
|
||||
}
|
||||
|
||||
setSunElevation(value) {
|
||||
@@ -266,6 +396,57 @@ export class OceanScene {
|
||||
this.sky.material.uniforms['rayleigh'].value = value;
|
||||
this.updateSun();
|
||||
}
|
||||
|
||||
setBloomStrength(value) {
|
||||
this.params.bloomStrength = value;
|
||||
if (this.bloomPass) {
|
||||
this.bloomPass.strength = value;
|
||||
}
|
||||
}
|
||||
|
||||
setBloomRadius(value) {
|
||||
this.params.bloomRadius = value;
|
||||
if (this.bloomPass) {
|
||||
this.bloomPass.radius = value;
|
||||
}
|
||||
}
|
||||
|
||||
setBloomThreshold(value) {
|
||||
this.params.bloomThreshold = value;
|
||||
if (this.bloomPass) {
|
||||
this.bloomPass.threshold = value;
|
||||
}
|
||||
}
|
||||
|
||||
setCloudOpacity(value) {
|
||||
this.params.cloudOpacity = value;
|
||||
this.updateClouds();
|
||||
}
|
||||
|
||||
setCloudCoverage(value) {
|
||||
this.params.cloudCoverage = value;
|
||||
this.updateClouds();
|
||||
}
|
||||
|
||||
setCloudSpeed(value) {
|
||||
this.params.cloudSpeed = value;
|
||||
}
|
||||
|
||||
updateClouds() {
|
||||
if (!this.cloudGroup) return;
|
||||
|
||||
const sunMix = THREE.MathUtils.clamp((this.params.elevation + 10) / 100, 0, 1);
|
||||
const warmCloud = new THREE.Color(0xdab188);
|
||||
const dayCloud = new THREE.Color(0xd1dbe6);
|
||||
const cloudColor = warmCloud.lerp(dayCloud, sunMix);
|
||||
|
||||
for (const layer of this.cloudLayers) {
|
||||
const opacity = layer.baseOpacity * this.params.cloudOpacity * (0.2 + this.params.cloudCoverage * 1.2);
|
||||
layer.mesh.material.opacity = opacity;
|
||||
layer.mesh.material.color.copy(cloudColor);
|
||||
layer.mesh.visible = opacity > 0.015;
|
||||
}
|
||||
}
|
||||
|
||||
animate() {
|
||||
requestAnimationFrame(() => this.animate());
|
||||
@@ -275,9 +456,21 @@ export class OceanScene {
|
||||
if (this.water) {
|
||||
this.water.material.uniforms['time'].value += 1.0 / 60.0;
|
||||
}
|
||||
|
||||
if (this.cloudGroup) {
|
||||
this.cloudGroup.rotation.y = time * 0.015 * this.params.cloudSpeed;
|
||||
this.cloudLayers.forEach((layer) => {
|
||||
layer.texture.offset.x = time * layer.speedX * this.params.cloudSpeed;
|
||||
layer.texture.offset.y = time * layer.speedY * this.params.cloudSpeed;
|
||||
});
|
||||
}
|
||||
|
||||
this.controls.update();
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
if (this.composer) {
|
||||
this.composer.render();
|
||||
} else {
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
}
|
||||
|
||||
this.frameCount++;
|
||||
const currentTime = performance.now();
|
||||
|
||||
63
src/main.js
63
src/main.js
@@ -30,47 +30,28 @@ async function main() {
|
||||
}
|
||||
|
||||
function setupControls(oceanScene) {
|
||||
const elevationSlider = document.getElementById('sun-elevation');
|
||||
const azimuthSlider = document.getElementById('sun-azimuth');
|
||||
const exposureSlider = document.getElementById('exposure');
|
||||
const turbiditySlider = document.getElementById('turbidity');
|
||||
const rayleighSlider = document.getElementById('rayleigh');
|
||||
|
||||
const elevationValue = document.getElementById('elevation-value');
|
||||
const azimuthValue = document.getElementById('azimuth-value');
|
||||
const exposureValue = document.getElementById('exposure-value');
|
||||
const turbidityValue = document.getElementById('turbidity-value');
|
||||
const rayleighValue = document.getElementById('rayleigh-value');
|
||||
|
||||
elevationSlider.addEventListener('input', (e) => {
|
||||
const value = parseFloat(e.target.value);
|
||||
oceanScene.setSunElevation(value);
|
||||
elevationValue.textContent = value.toFixed(1) + '°';
|
||||
});
|
||||
|
||||
azimuthSlider.addEventListener('input', (e) => {
|
||||
const value = parseFloat(e.target.value);
|
||||
oceanScene.setSunAzimuth(value);
|
||||
azimuthValue.textContent = value.toFixed(1) + '°';
|
||||
});
|
||||
|
||||
exposureSlider.addEventListener('input', (e) => {
|
||||
const value = parseFloat(e.target.value);
|
||||
oceanScene.setExposure(value);
|
||||
exposureValue.textContent = value.toFixed(2);
|
||||
});
|
||||
|
||||
turbiditySlider.addEventListener('input', (e) => {
|
||||
const value = parseFloat(e.target.value);
|
||||
oceanScene.setTurbidity(value);
|
||||
turbidityValue.textContent = value.toFixed(1);
|
||||
});
|
||||
|
||||
rayleighSlider.addEventListener('input', (e) => {
|
||||
const value = parseFloat(e.target.value);
|
||||
oceanScene.setRayleigh(value);
|
||||
rayleighValue.textContent = value.toFixed(2);
|
||||
});
|
||||
const bindSlider = (id, formatter, setter) => {
|
||||
const slider = document.getElementById(id);
|
||||
const valueLabel = document.getElementById(`${id}-value`);
|
||||
|
||||
slider.addEventListener('input', (e) => {
|
||||
const value = parseFloat(e.target.value);
|
||||
setter(value);
|
||||
valueLabel.textContent = formatter(value);
|
||||
});
|
||||
};
|
||||
|
||||
bindSlider('sun-elevation', (value) => `${value.toFixed(1)}°`, (value) => oceanScene.setSunElevation(value));
|
||||
bindSlider('sun-azimuth', (value) => `${value.toFixed(1)}°`, (value) => oceanScene.setSunAzimuth(value));
|
||||
bindSlider('exposure', (value) => value.toFixed(2), (value) => oceanScene.setExposure(value));
|
||||
bindSlider('turbidity', (value) => value.toFixed(1), (value) => oceanScene.setTurbidity(value));
|
||||
bindSlider('rayleigh', (value) => value.toFixed(2), (value) => oceanScene.setRayleigh(value));
|
||||
bindSlider('bloom-strength', (value) => value.toFixed(2), (value) => oceanScene.setBloomStrength(value));
|
||||
bindSlider('bloom-radius', (value) => value.toFixed(2), (value) => oceanScene.setBloomRadius(value));
|
||||
bindSlider('bloom-threshold', (value) => value.toFixed(2), (value) => oceanScene.setBloomThreshold(value));
|
||||
bindSlider('cloud-opacity', (value) => value.toFixed(2), (value) => oceanScene.setCloudOpacity(value));
|
||||
bindSlider('cloud-coverage', (value) => value.toFixed(2), (value) => oceanScene.setCloudCoverage(value));
|
||||
bindSlider('cloud-speed', (value) => value.toFixed(2), (value) => oceanScene.setCloudSpeed(value));
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
|
||||
Reference in New Issue
Block a user