Add fog quality material and update runtime/viewport hosts

This commit is contained in:
2026-04-07 11:07:19 +02:00
parent 802d3e026c
commit 40a53801a7
7 changed files with 597 additions and 136 deletions

View File

@@ -18,11 +18,8 @@ import {
PointLight,
Quaternion,
Scene,
ShaderMaterial,
Vector3,
SpotLight,
UniformsLib,
UniformsUtils,
WebGLRenderTarget,
WebGLRenderer
} from "three";
@@ -50,6 +47,7 @@ import {
createWaterContactPatchUniformValue,
createWaterMaterial
} from "../rendering/water-material";
import { createFogQualityMaterial } from "../rendering/fog-material";
import { updatePlanarReflectionCamera } from "../rendering/planar-reflection";
import {
areAdvancedRenderingSettingsEqual,
@@ -117,7 +115,6 @@ export class RuntimeHost {
private readonly waterReflectionCamera = new PerspectiveCamera();
private readonly brushMeshes = new Map<string, Mesh<BufferGeometry, Material[]>>();
private volumeTime = 0;
private readonly volumeAnimatedMaterials: ShaderMaterial[] = [];
private readonly volumeAnimatedUniforms: Array<{ value: number }> = [];
private readonly runtimeWaterContactUniforms: RuntimeWaterContactUniformBinding[] = [];
private readonly localLightObjects = new Map<string, Group>();
@@ -685,7 +682,20 @@ export class RuntimeHost {
if (brush.volume.mode === "fog") {
if (volumeRenderPaths.fog === "quality") {
return this.createFogQualityMaterial(brush.volume.fog);
const fogMaterial = createFogQualityMaterial({
colorHex: brush.volume.fog.colorHex,
density: brush.volume.fog.density,
padding: brush.volume.fog.padding,
time: this.volumeTime,
halfSize: {
x: brush.size.x * 0.5,
y: brush.size.y * 0.5,
z: brush.size.z * 0.5
}
});
this.volumeAnimatedUniforms.push(fogMaterial.animationUniform);
return fogMaterial.material;
}
// Performance fallback: simple transparent material
const densityOpacity = Math.max(0.06, Math.min(0.72, brush.volume.fog.density * 0.8 + 0.08));
@@ -713,72 +723,6 @@ export class RuntimeHost {
});
}
// Soft edge-faded fog shader with slow drift animation — quality mode only.
private createFogQualityMaterial(fog: { colorHex: string; density: number }): ShaderMaterial {
const hex = fog.colorHex.replace("#", "");
const cr = parseInt(hex.substring(0, 2), 16) / 255;
const cg = parseInt(hex.substring(2, 4), 16) / 255;
const cb = parseInt(hex.substring(4, 6), 16) / 255;
const vertexShader = /* glsl */ `
varying vec2 vUv;
#include <fog_pars_vertex>
void main() {
vUv = uv;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * mvPosition;
#include <fog_vertex>
}
`;
const fragmentShader = /* glsl */ `
uniform vec3 volumeFogColor;
uniform float volumeFogDensity;
uniform float time;
varying vec2 vUv;
#include <fog_pars_fragment>
void main() {
// Soft fade: distance from the center of each face in UV space
vec2 dist = abs(vUv - 0.5) * 2.0; // 0 at center, 1 at edge
float edgeFade = 1.0 - smoothstep(0.4, 1.0, max(dist.x, dist.y));
// Slow drifting noise to break up the flat look
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 = volumeFogDensity * edgeFade * variation;
alpha = clamp(alpha, 0.0, 0.88);
// Edge scatter brightening
vec3 color = mix(volumeFogColor, vec3(1.0), (1.0 - edgeFade) * 0.09);
gl_FragColor = vec4(color, alpha);
#include <fog_fragment>
}
`;
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,
transparent: true,
depthWrite: false,
fog: true,
side: 2 // THREE.DoubleSide — fog is visible from inside too
});
this.volumeAnimatedMaterials.push(mat);
return mat;
}
private updateUnderwaterSceneFog() {
const fogState =
this.activeController === this.firstPersonController
@@ -968,7 +912,6 @@ export class RuntimeHost {
}
this.brushMeshes.clear();
this.volumeAnimatedMaterials.length = 0;
this.volumeAnimatedUniforms.length = 0;
for (const binding of this.runtimeWaterContactUniforms) {
binding.reflectionRenderTarget?.dispose();
@@ -1164,9 +1107,6 @@ export class RuntimeHost {
this.audioSystem.updateListenerTransform();
this.volumeTime += dt;
for (const mat of this.volumeAnimatedMaterials) {
(mat.uniforms["time"] as { value: number }).value = this.volumeTime;
}
for (const uniform of this.volumeAnimatedUniforms) {
uniform.value = this.volumeTime;
}