Add underwater fog effect and update water material handling

This commit is contained in:
2026-04-07 06:15:02 +02:00
parent 786225b4b7
commit 3e370591d3

View File

@@ -6,6 +6,7 @@ import {
DirectionalLight, DirectionalLight,
Euler, Euler,
Group, Group,
FogExp2,
LoopOnce, LoopOnce,
LoopRepeat, LoopRepeat,
Material, Material,
@@ -38,7 +39,13 @@ import {
resolveBoxVolumeRenderPaths, resolveBoxVolumeRenderPaths,
type ResolvedBoxVolumeRenderPaths type ResolvedBoxVolumeRenderPaths
} from "../rendering/advanced-rendering"; } from "../rendering/advanced-rendering";
import { collectWaterContactPatches, createWaterContactPatchAxisUniformValue, createWaterContactPatchUniformValue, createWaterMaterial } from "../rendering/water-material"; import {
collectWaterContactPatches,
createWaterContactPatchAxisUniformValue,
createWaterContactPatchShapeUniformValue,
createWaterContactPatchUniformValue,
createWaterMaterial
} from "../rendering/water-material";
import { import {
areAdvancedRenderingSettingsEqual, areAdvancedRenderingSettingsEqual,
cloneAdvancedRenderingSettings, cloneAdvancedRenderingSettings,
@@ -51,6 +58,7 @@ import { RapierCollisionWorld } from "./rapier-collision-world";
import { RuntimeInteractionSystem, type RuntimeInteractionDispatcher, type RuntimeInteractionPrompt } from "./runtime-interaction-system"; import { RuntimeInteractionSystem, type RuntimeInteractionDispatcher, type RuntimeInteractionPrompt } from "./runtime-interaction-system";
import { RuntimeAudioSystem } from "./runtime-audio-system"; import { RuntimeAudioSystem } from "./runtime-audio-system";
import { OrbitVisitorNavigationController } from "./orbit-visitor-navigation-controller"; import { OrbitVisitorNavigationController } from "./orbit-visitor-navigation-controller";
import { resolveUnderwaterFogState } from "./underwater-fog";
import type { import type {
RuntimeBoxBrushInstance, RuntimeBoxBrushInstance,
RuntimeLocalLightCollection, RuntimeLocalLightCollection,
@@ -72,6 +80,7 @@ interface RuntimeWaterContactUniformBinding {
brush: RuntimeBoxBrushInstance; brush: RuntimeBoxBrushInstance;
uniform: { value: import("three").Vector4[] }; uniform: { value: import("three").Vector4[] };
axisUniform: { value: import("three").Vector2[] }; axisUniform: { value: import("three").Vector2[] };
shapeUniform: { value: number[] };
staticContactPatches: ReturnType<typeof collectWaterContactPatches>; staticContactPatches: ReturnType<typeof collectWaterContactPatches>;
} }
@@ -93,6 +102,7 @@ export class RuntimeHost {
private readonly orbitVisitorController = new OrbitVisitorNavigationController(); private readonly orbitVisitorController = new OrbitVisitorNavigationController();
private readonly interactionSystem = new RuntimeInteractionSystem(); private readonly interactionSystem = new RuntimeInteractionSystem();
private readonly audioSystem = new RuntimeAudioSystem(this.scene, this.camera, null); private readonly audioSystem = new RuntimeAudioSystem(this.scene, this.camera, null);
private readonly underwaterSceneFog = new FogExp2("#2c6f8d", 0.03);
private readonly brushMeshes = new Map<string, Mesh<BufferGeometry, Material[]>>(); private readonly brushMeshes = new Map<string, Mesh<BufferGeometry, Material[]>>();
private volumeTime = 0; private volumeTime = 0;
private readonly volumeAnimatedMaterials: ShaderMaterial[] = []; private readonly volumeAnimatedMaterials: ShaderMaterial[] = [];
@@ -323,6 +333,7 @@ export class RuntimeHost {
this.advancedRenderingComposer?.dispose(); this.advancedRenderingComposer?.dispose();
this.advancedRenderingComposer = null; this.advancedRenderingComposer = null;
this.currentAdvancedRenderingSettings = null; this.currentAdvancedRenderingSettings = null;
this.scene.fog = null;
if (this.renderer !== null) { if (this.renderer !== null) {
this.renderer.autoClear = true; this.renderer.autoClear = true;
} }
@@ -637,6 +648,7 @@ export class RuntimeHost {
brush, brush,
uniform: waterMaterial.contactPatchesUniform, uniform: waterMaterial.contactPatchesUniform,
axisUniform: waterMaterial.contactPatchAxesUniform, axisUniform: waterMaterial.contactPatchAxesUniform,
shapeUniform: waterMaterial.contactPatchShapesUniform ?? { value: [] },
staticContactPatches staticContactPatches
}); });
} }
@@ -683,9 +695,12 @@ export class RuntimeHost {
const vertexShader = /* glsl */ ` const vertexShader = /* glsl */ `
varying vec2 vUv; varying vec2 vUv;
#include <fog_pars_vertex>
void main() { void main() {
vUv = uv; vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * mvPosition;
#include <fog_vertex>
} }
`; `;
@@ -694,6 +709,7 @@ export class RuntimeHost {
uniform float fogDensity; uniform float fogDensity;
uniform float time; uniform float time;
varying vec2 vUv; varying vec2 vUv;
#include <fog_pars_fragment>
void main() { void main() {
// Soft fade: distance from the center of each face in UV space // Soft fade: distance from the center of each face in UV space
@@ -710,6 +726,7 @@ export class RuntimeHost {
// Edge scatter brightening // Edge scatter brightening
vec3 color = mix(fogColor, vec3(1.0), (1.0 - edgeFade) * 0.09); vec3 color = mix(fogColor, vec3(1.0), (1.0 - edgeFade) * 0.09);
gl_FragColor = vec4(color, alpha); gl_FragColor = vec4(color, alpha);
#include <fog_fragment>
} }
`; `;
@@ -723,12 +740,34 @@ export class RuntimeHost {
}, },
transparent: true, transparent: true,
depthWrite: false, depthWrite: false,
fog: true,
side: 2 // THREE.DoubleSide — fog is visible from inside too side: 2 // THREE.DoubleSide — fog is visible from inside too
}); });
this.volumeAnimatedMaterials.push(mat); this.volumeAnimatedMaterials.push(mat);
return mat; return mat;
} }
private updateUnderwaterSceneFog() {
const fogState =
this.activeController === this.firstPersonController
? resolveUnderwaterFogState(this.runtimeScene, this.currentFirstPersonTelemetry)
: null;
if (fogState === null) {
if (this.scene.fog !== null) {
this.scene.fog = null;
}
return;
}
this.underwaterSceneFog.color.set(fogState.colorHex);
this.underwaterSceneFog.density = fogState.density;
if (this.scene.fog !== this.underwaterSceneFog) {
this.scene.fog = this.underwaterSceneFog;
}
}
private getOrCreateTexture(material: NonNullable<RuntimeBoxBrushInstance["faces"]["posX"]["material"]>) { private getOrCreateTexture(material: NonNullable<RuntimeBoxBrushInstance["faces"]["posX"]["material"]>) {
const signature = createStarterMaterialSignature(material); const signature = createStarterMaterialSignature(material);
const cachedTexture = this.materialTextureCache.get(material.id); const cachedTexture = this.materialTextureCache.get(material.id);
@@ -898,6 +937,7 @@ export class RuntimeHost {
const mergedPatches = this.mergeRuntimeWaterContactPatches(binding.staticContactPatches, this.collectRuntimePlayerWaterContactPatches(binding.brush)); const mergedPatches = this.mergeRuntimeWaterContactPatches(binding.staticContactPatches, this.collectRuntimePlayerWaterContactPatches(binding.brush));
binding.uniform.value = createWaterContactPatchUniformValue(mergedPatches); binding.uniform.value = createWaterContactPatchUniformValue(mergedPatches);
binding.axisUniform.value = createWaterContactPatchAxisUniformValue(mergedPatches); binding.axisUniform.value = createWaterContactPatchAxisUniformValue(mergedPatches);
binding.shapeUniform.value = createWaterContactPatchShapeUniformValue(mergedPatches);
} }
} }
@@ -980,6 +1020,8 @@ export class RuntimeHost {
this.updateRuntimeWaterContactUniforms(); this.updateRuntimeWaterContactUniforms();
} }
this.updateUnderwaterSceneFog();
if (this.advancedRenderingComposer !== null) { if (this.advancedRenderingComposer !== null) {
this.advancedRenderingComposer.render(dt); this.advancedRenderingComposer.render(dt);
return; return;