From c4739493d970e8e9b21f14b0c14d65d67a9d21f2 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Tue, 7 Apr 2026 06:43:38 +0200 Subject: [PATCH] Update shader and add water reflection features in runtime-host.js --- src/runtime-three/runtime-host.js | 99 +++++++++++++++++++++++++++---- 1 file changed, 89 insertions(+), 10 deletions(-) diff --git a/src/runtime-three/runtime-host.js b/src/runtime-three/runtime-host.js index 9999ffc8..569934b1 100644 --- a/src/runtime-three/runtime-host.js +++ b/src/runtime-three/runtime-host.js @@ -506,9 +506,9 @@ export class RuntimeHost { #include } `; - const fragmentShader = ` - uniform vec3 fogColor; - uniform float fogDensity; + const fragmentShader = ` + uniform vec3 volumeFogColor; + uniform float volumeFogDensity; uniform float time; varying vec2 vUv; #include @@ -517,21 +517,25 @@ export class RuntimeHost { float edgeFade = 1.0 - smoothstep(0.4, 1.0, max(dist.x, dist.y)); float drift = sin(vUv.x * 4.5 + time * 0.28) * sin(vUv.y * 3.2 + time * 0.22); float variation = 0.82 + drift * 0.18; - float alpha = fogDensity * edgeFade * variation; + float alpha = volumeFogDensity * edgeFade * variation; alpha = clamp(alpha, 0.0, 0.88); - vec3 color = mix(fogColor, vec3(1.0), (1.0 - edgeFade) * 0.09); + vec3 color = mix(volumeFogColor, vec3(1.0), (1.0 - edgeFade) * 0.09); gl_FragColor = vec4(color, alpha); #include } `; + const uniforms = UniformsUtils.merge([ + UniformsLib.fog, + { + volumeFogColor: { value: [cr, cg, cb] }, + volumeFogDensity: { value: Math.min(0.9, fog.density + 0.12) }, + time: { value: this.volumeTime } + } + ]); const mat = new ShaderMaterial({ vertexShader, fragmentShader, - uniforms: { - fogColor: { value: [cr, cg, cb] }, - fogDensity: { value: Math.min(0.9, fog.density + 0.12) }, - time: { value: this.volumeTime } - }, + uniforms, transparent: true, depthWrite: false, fog: true, @@ -556,6 +560,81 @@ export class RuntimeHost { this.scene.fog = this.underwaterSceneFog; } } + getWaterReflectionMode() { + if (this.currentWorld === null || !this.currentWorld.advancedRendering.enabled || this.currentWorld.advancedRendering.waterPath !== "quality") { + return "none"; + } + return this.currentWorld.advancedRendering.waterReflectionMode; + } + createWaterReflectionRenderTarget() { + const canvasWidth = this.container?.clientWidth ?? this.domElement.width; + const canvasHeight = this.container?.clientHeight ?? this.domElement.height; + const width = Math.max(128, Math.round(Math.max(canvasWidth, 512) * 0.5)); + const height = Math.max(128, Math.round(Math.max(canvasHeight, 512) * 0.5)); + return new WebGLRenderTarget(width, height); + } + resizeWaterReflectionTargets() { + const canvasWidth = this.container?.clientWidth ?? this.domElement.width; + const canvasHeight = this.container?.clientHeight ?? this.domElement.height; + const width = Math.max(128, Math.round(Math.max(canvasWidth, 512) * 0.5)); + const height = Math.max(128, Math.round(Math.max(canvasHeight, 512) * 0.5)); + for (const binding of this.runtimeWaterContactUniforms) { + binding.reflectionRenderTarget?.setSize(width, height); + } + } + updateRuntimeWaterReflections() { + if (this.renderer === null || this.runtimeScene === null) { + return; + } + const reflectionMode = this.getWaterReflectionMode(); + for (const binding of this.runtimeWaterContactUniforms) { + if (reflectionMode === "none" || + binding.reflectionRenderTarget === null || + binding.reflectionTextureUniform === null || + binding.reflectionMatrixUniform === null || + binding.reflectionEnabledUniform === null) { + if (binding.reflectionEnabledUniform !== null) { + binding.reflectionEnabledUniform.value = 0; + } + continue; + } + const canRenderReflection = updatePlanarReflectionCamera(binding.brush, this.camera, this.waterReflectionCamera, binding.reflectionMatrixUniform.value); + if (!canRenderReflection) { + binding.reflectionEnabledUniform.value = 0; + continue; + } + const hiddenWaterMeshes = []; + for (const runtimeBrush of this.runtimeScene.brushes) { + if (runtimeBrush.volume.mode !== "water") { + continue; + } + const mesh = this.brushMeshes.get(runtimeBrush.id); + if (mesh === undefined) { + continue; + } + hiddenWaterMeshes.push({ mesh, visible: mesh.visible }); + mesh.visible = false; + } + const previousModelGroupVisibility = this.modelGroup.visible; + if (reflectionMode === "world") { + this.modelGroup.visible = false; + } + const previousAutoClear = this.renderer.autoClear; + const previousRenderTarget = this.renderer.getRenderTarget(); + this.renderer.autoClear = true; + this.renderer.setRenderTarget(binding.reflectionRenderTarget); + this.renderer.clear(); + this.renderer.render(this.scene, this.waterReflectionCamera); + this.renderer.setRenderTarget(previousRenderTarget); + this.renderer.autoClear = previousAutoClear; + this.modelGroup.visible = previousModelGroupVisibility; + for (const hiddenWaterMesh of hiddenWaterMeshes) { + hiddenWaterMesh.mesh.visible = hiddenWaterMesh.visible; + } + binding.reflectionTextureUniform.value = binding.reflectionRenderTarget.texture; + binding.reflectionEnabledUniform.value = 0.36; + } + } getOrCreateTexture(material) { const signature = createStarterMaterialSignature(material); const cachedTexture = this.materialTextureCache.get(material.id);