From 7d61cd322dc40fc4102206611da467012853d971 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Tue, 7 Apr 2026 06:15:21 +0200 Subject: [PATCH] Add underwater fog effect and update shader material for water patches --- src/runtime-three/runtime-host.js | 34 ++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/runtime-three/runtime-host.js b/src/runtime-three/runtime-host.js index 6c2cbc12..132e407a 100644 --- a/src/runtime-three/runtime-host.js +++ b/src/runtime-three/runtime-host.js @@ -1,15 +1,16 @@ -import { AmbientLight, AnimationClip, AnimationMixer, DirectionalLight, Euler, Group, LoopOnce, LoopRepeat, Mesh, MeshBasicMaterial, MeshStandardMaterial, PerspectiveCamera, PointLight, Quaternion, Scene, ShaderMaterial, Vector3, SpotLight, WebGLRenderer } from "three"; +import { AmbientLight, AnimationClip, AnimationMixer, DirectionalLight, Euler, FogExp2, Group, LoopOnce, LoopRepeat, Mesh, MeshBasicMaterial, MeshStandardMaterial, PerspectiveCamera, PointLight, Quaternion, Scene, ShaderMaterial, Vector3, SpotLight, WebGLRenderer } from "three"; import { createModelInstanceRenderGroup, disposeModelInstance } from "../assets/model-instance-rendering"; import { buildBoxBrushDerivedMeshData } from "../geometry/box-brush-mesh"; import { createStarterMaterialSignature, createStarterMaterialTexture } from "../materials/starter-material-textures"; import { applyAdvancedRenderingLightShadowFlags, applyAdvancedRenderingRenderableShadowFlags, configureAdvancedRenderingRenderer, createAdvancedRenderingComposer, resolveBoxVolumeRenderPaths } from "../rendering/advanced-rendering"; -import { collectWaterContactPatches, createWaterContactPatchAxisUniformValue, createWaterContactPatchUniformValue, createWaterMaterial } from "../rendering/water-material"; +import { collectWaterContactPatches, createWaterContactPatchAxisUniformValue, createWaterContactPatchShapeUniformValue, createWaterContactPatchUniformValue, createWaterMaterial } from "../rendering/water-material"; import { areAdvancedRenderingSettingsEqual, cloneAdvancedRenderingSettings } from "../document/world-settings"; import { FirstPersonNavigationController } from "./first-person-navigation-controller"; import { RapierCollisionWorld } from "./rapier-collision-world"; import { RuntimeInteractionSystem } from "./runtime-interaction-system"; import { RuntimeAudioSystem } from "./runtime-audio-system"; import { OrbitVisitorNavigationController } from "./orbit-visitor-navigation-controller"; +import { resolveUnderwaterFogState } from "./underwater-fog"; const FALLBACK_FACE_COLOR = 0x747d89; export class RuntimeHost { scene = new Scene(); @@ -27,6 +28,7 @@ export class RuntimeHost { orbitVisitorController = new OrbitVisitorNavigationController(); interactionSystem = new RuntimeInteractionSystem(); audioSystem = new RuntimeAudioSystem(this.scene, this.camera, null); + underwaterSceneFog = new FogExp2("#2c6f8d", 0.03); brushMeshes = new Map(); volumeTime = 0; volumeAnimatedMaterials = []; @@ -209,6 +211,7 @@ export class RuntimeHost { this.advancedRenderingComposer?.dispose(); this.advancedRenderingComposer = null; this.currentAdvancedRenderingSettings = null; + this.scene.fog = null; if (this.renderer !== null) { this.renderer.autoClear = true; } @@ -446,6 +449,7 @@ export class RuntimeHost { brush, uniform: waterMaterial.contactPatchesUniform, axisUniform: waterMaterial.contactPatchAxesUniform, + shapeUniform: waterMaterial.contactPatchShapesUniform ?? { value: [] }, staticContactPatches }); } @@ -484,9 +488,12 @@ export class RuntimeHost { const cb = parseInt(hex.substring(4, 6), 16) / 255; const vertexShader = ` varying vec2 vUv; + #include void main() { vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); + gl_Position = projectionMatrix * mvPosition; + #include } `; const fragmentShader = ` @@ -494,6 +501,7 @@ export class RuntimeHost { uniform float fogDensity; uniform float time; varying vec2 vUv; + #include void main() { vec2 dist = abs(vUv - 0.5) * 2.0; float edgeFade = 1.0 - smoothstep(0.4, 1.0, max(dist.x, dist.y)); @@ -503,6 +511,7 @@ export class RuntimeHost { alpha = clamp(alpha, 0.0, 0.88); vec3 color = mix(fogColor, vec3(1.0), (1.0 - edgeFade) * 0.09); gl_FragColor = vec4(color, alpha); + #include } `; const mat = new ShaderMaterial({ @@ -515,11 +524,28 @@ export class RuntimeHost { }, transparent: true, depthWrite: false, + fog: true, side: 2 }); this.volumeAnimatedMaterials.push(mat); return mat; } + 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; + } + } getOrCreateTexture(material) { const signature = createStarterMaterialSignature(material); const cachedTexture = this.materialTextureCache.get(material.id); @@ -655,6 +681,7 @@ export class RuntimeHost { const mergedPatches = this.mergeRuntimeWaterContactPatches(binding.staticContactPatches, this.collectRuntimePlayerWaterContactPatches(binding.brush)); binding.uniform.value = createWaterContactPatchUniformValue(mergedPatches); binding.axisUniform.value = createWaterContactPatchAxisUniformValue(mergedPatches); + binding.shapeUniform.value = createWaterContactPatchShapeUniformValue(mergedPatches); } } clearModelInstances() { @@ -717,6 +744,7 @@ export class RuntimeHost { if (this.runtimeWaterContactUniforms.length > 0) { this.updateRuntimeWaterContactUniforms(); } + this.updateUnderwaterSceneFog(); if (this.advancedRenderingComposer !== null) { this.advancedRenderingComposer.render(dt); return;