From d6ebff1e4aa5b094273cc1250a93a440e445013c Mon Sep 17 00:00:00 2001 From: Thomas Shaw Date: Sun, 7 Dec 2025 22:27:22 -0500 Subject: [PATCH 1/8] Add basic exponential distance fog --- src/lib/shaders/naive.fs.wgsl | 6 +++++- src/lib/shaders/naive.vs.wgsl | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/shaders/naive.fs.wgsl b/src/lib/shaders/naive.fs.wgsl index cdc4fc9..077e7b5 100644 --- a/src/lib/shaders/naive.fs.wgsl +++ b/src/lib/shaders/naive.fs.wgsl @@ -8,6 +8,7 @@ struct FragmentInput @location(1) nor: vec3f, @location(2) uv: vec2f, @location(3) shadow_pos: vec3f, + @location(4) camera_view_pos: vec3f, } @group(0) @binding(1) var directionalLightUniforms: DirectionalLightUniforms; @@ -30,8 +31,11 @@ fn main(in: FragmentInput) -> @location(0) vec4f let ambientLight = vec3f(0.1, 0.1, 0.2); + let fogStrength = 1.0 - exp(-0.4 * length(in.camera_view_pos)); + let fogColor = vec3f(0.686, 0.702, 0.725); + let baseColor = vec3f(0.8, 0.8, 0.8); - var color = baseColor * (directLight + ambientLight); + var color = mix(baseColor * (directLight + ambientLight), fogColor, fogStrength); return vec4f(color, 1.0); } diff --git a/src/lib/shaders/naive.vs.wgsl b/src/lib/shaders/naive.vs.wgsl index b06927f..daef886 100644 --- a/src/lib/shaders/naive.vs.wgsl +++ b/src/lib/shaders/naive.vs.wgsl @@ -14,6 +14,7 @@ struct VertexOutput @location(1) nor: vec3f, @location(2) uv: vec2f, @location(3) shadow_pos: vec3f, + @location(4) camera_view_pos: vec3f, } @group(0) @binding(0) var camera : CameraUniforms; @@ -37,6 +38,7 @@ fn main(in: VertexInput) -> VertexOutput posFromLight.xy * vec2(0.5, -0.5) + vec2(0.5), posFromLight.z ); + out.camera_view_pos = (camera.viewMat * modelPos).xyz; return out; } From 7c4b75b1639bf28eab8ba2c7e17f52f8beeda875 Mon Sep 17 00:00:00 2001 From: Thomas Shaw Date: Sun, 7 Dec 2025 22:36:44 -0500 Subject: [PATCH 2/8] Add fog for water and instanced stuff --- src/lib/shaders/instancing.wgsl | 11 +++++++++-- src/lib/shaders/naive.fs.wgsl | 2 +- src/lib/shaders/water.fs.wgsl | 32 +++++++++++++++++++------------- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/lib/shaders/instancing.wgsl b/src/lib/shaders/instancing.wgsl index 9723a01..6f01736 100644 --- a/src/lib/shaders/instancing.wgsl +++ b/src/lib/shaders/instancing.wgsl @@ -31,6 +31,7 @@ struct VertexOut { @location(2) uv : vec2f, @location(3) @interpolate(flat) tex_id: u32, @location(4) @interpolate(flat) used: u32, + @location(5) camera_view_pos: vec3f, }; @vertex @@ -84,6 +85,7 @@ fn vs_main(in : VertexIn) -> VertexOut { out.uv = vert_uv; out.tex_id = texture_id; out.used = used; + out.camera_view_pos = (camera.viewMat * world_pos).xyz; return out; } @@ -106,10 +108,15 @@ fn fs_main(in: VertexOut) -> @location(0) vec4f discard; } - return color; + let fogStrength = 1.0 - exp(-0.08 * length(in.camera_view_pos)); + let fogColor = vec3f(0.686, 0.702, 0.725); + + let finalColor = mix(color.xyz, fogColor, fogStrength); + + return vec4f(finalColor, color.a); // var color = vec3f(0.0, 0.0, 0.0); - + // if (diffuse > 0.75) { // color = vec3f(0.58, 1.0, 0.235); // } else if (diffuse > 0.5) { diff --git a/src/lib/shaders/naive.fs.wgsl b/src/lib/shaders/naive.fs.wgsl index 077e7b5..a412e90 100644 --- a/src/lib/shaders/naive.fs.wgsl +++ b/src/lib/shaders/naive.fs.wgsl @@ -31,7 +31,7 @@ fn main(in: FragmentInput) -> @location(0) vec4f let ambientLight = vec3f(0.1, 0.1, 0.2); - let fogStrength = 1.0 - exp(-0.4 * length(in.camera_view_pos)); + let fogStrength = 1.0 - exp(-0.08 * length(in.camera_view_pos)); let fogColor = vec3f(0.686, 0.702, 0.725); let baseColor = vec3f(0.8, 0.8, 0.8); diff --git a/src/lib/shaders/water.fs.wgsl b/src/lib/shaders/water.fs.wgsl index 4341eb9..4fb218c 100644 --- a/src/lib/shaders/water.fs.wgsl +++ b/src/lib/shaders/water.fs.wgsl @@ -6,13 +6,14 @@ struct FragmentInput @location(1) nor: vec3f, @location(2) uv: vec2f, @location(3) shadow_pos: vec3f, + @location(4) camera_view_pos: vec3f, } fn random3D(seed: vec3f) -> vec3f { let dot_product = dot(seed, vec3f(12.9898, 78.233, 45.164)); let sin_value = sin(dot_product) * 43758.5453; let fract_value = fract(sin_value); - + return vec3f(fract_value, fract_value, fract_value); } @@ -24,21 +25,21 @@ fn worley_noise(pos: vec3f, time: f32) -> f32 { for (var z: i32 = -1; z <= 1; z = z + 1) { for (var y: i32 = -1; y <= 1; y = y + 1) { for (var x: i32 = -1; x <= 1; x = x + 1) { - var neighbor = vec3f(f32(x), f32(y), f32(z)); + var neighbor = vec3f(f32(x), f32(y), f32(z)); var point = random3D(posInt + neighbor); let timeSpeed = 0.8; let offsetX = sin(time * timeSpeed + point.x * 6.28) * 0.5; let offsetY = cos(time * timeSpeed * 0.7 + point.y * 6.28) * 0.5; let offsetZ = sin(time * timeSpeed * 0.5 + point.z * 6.28) * 0.5; - + point = point + vec3f(offsetX, offsetY, offsetZ) * 0.3; - + var diff = neighbor + point - posFract; - var dist = length(diff); + var dist = length(diff); minDist = min(minDist, dist); } - } + } } return minDist; @@ -65,10 +66,10 @@ fn main(in: FragmentInput) -> @location(0) vec4f in.pos.z * scale, in.pos.y * 2.0 ); - + let noise1 = worley_noise(scaledPos, camera.time); let noise2 = worley_noise(scaledPos * 2.0, camera.time * 1.5); - + let combinedNoise = noise1 * 0.7 + noise2 * 0.3; let deepOceanColor = vec3f(0.0, 0.3, 0.6); @@ -79,21 +80,26 @@ fn main(in: FragmentInput) -> @location(0) vec4f let t1 = smoothstep(0.15, 0.3, combinedNoise); let t2 = smoothstep(0.4, 0.55, combinedNoise); let t3 = smoothstep(0.65, 0.8, combinedNoise); - + var waterC = deepOceanColor; waterC = mix(waterC, midOceanColor, t1); waterC = mix(waterC, shallowColor, t2); waterC = mix(waterC, foamColor, t3); - + let lightDir = normalize(vec3f(-1.0, 1.0, -1.0)); let diffuse = max(dot(in.nor, lightDir), 0.2); var finalColor = waterC * diffuse; finalColor = mix(finalColor, waterC, combinedNoise * combinedNoise * 0.5); - + if isShadowed { finalColor *= 0.5; } - + + let fogStrength = 1.0 - exp(-0.08 * length(in.camera_view_pos)); + let fogColor = vec3f(0.686, 0.702, 0.725); + + finalColor = mix(finalColor, fogColor, fogStrength); + return vec4f(finalColor, 0.85); -} \ No newline at end of file +} From 85fc830992c3a48abe05545eab0bdf7c71e349e4 Mon Sep 17 00:00:00 2001 From: Thomas Shaw Date: Sun, 7 Dec 2025 22:43:33 -0500 Subject: [PATCH 3/8] Add fogIntensity and fogColor to cam uniforms --- src/lib/scene/camera.ts | 12 +++++++++++- src/lib/shaders/common.wgsl | 5 ++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/lib/scene/camera.ts b/src/lib/scene/camera.ts index 5b7d2c8..9241b12 100644 --- a/src/lib/scene/camera.ts +++ b/src/lib/scene/camera.ts @@ -8,7 +8,7 @@ function toRadians(degrees: number) { } class CameraUniforms { - readonly buffer = new ArrayBuffer(304); + readonly buffer = new ArrayBuffer(320); private readonly floatView = new Float32Array(this.buffer, 0, 16); private readonly invProjMatView = new Float32Array(this.buffer, 64, 16); private readonly viewMatView = new Float32Array(this.buffer, 128, 16); @@ -19,6 +19,8 @@ class CameraUniforms { private readonly nearPlaneView = new Float32Array(this.buffer, 280, 1); private readonly farPlaneView = new Float32Array(this.buffer, 284, 1); private readonly timeView = new Float32Array(this.buffer, 288, 1); + private readonly fogIntensityView = new Float32Array(this.buffer, 292, 1); + private readonly fogColorView = new Float32Array(this.buffer, 304, 3); set viewProjMat(mat: Float32Array) { this.floatView.set(mat.subarray(0, 16), 0); @@ -59,6 +61,14 @@ class CameraUniforms { set time(time: number) { this.timeView[0] = time; } + + set fogIntensity(intensity: number) { + this.fogIntensityView[0] = intensity; + } + + set fogColor(color: [number, number, number]) { + this.fogColorView.set(color); + } } export class Camera { diff --git a/src/lib/shaders/common.wgsl b/src/lib/shaders/common.wgsl index 923c1ea..35aab31 100644 --- a/src/lib/shaders/common.wgsl +++ b/src/lib/shaders/common.wgsl @@ -11,9 +11,8 @@ struct CameraUniforms { nearPlane: f32, farPlane: f32, time: f32, - _padding1: f32, - _padding2: f32, - _padding3: f32, + fogIntensity: f32, + fogColor: vec3f, } struct DirectionalLightUniforms { From 98766d988563588cef0c5ff0be75508f81175043 Mon Sep 17 00:00:00 2001 From: Thomas Shaw Date: Sun, 7 Dec 2025 23:02:29 -0500 Subject: [PATCH 4/8] Add fog params to globalParams, add color to sliders --- src/components/editor/index.tsx | 7 +++- src/components/editor/terrain-sliders.tsx | 51 ++++++++++++++++++----- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/components/editor/index.tsx b/src/components/editor/index.tsx index 09a7865..4191dd1 100644 --- a/src/components/editor/index.tsx +++ b/src/components/editor/index.tsx @@ -5,6 +5,7 @@ import NodeGraph from './node-graph'; import NodeGraphCanvas from './node-graph-canvas'; import TerrainCanvas from './terrain-canvas'; import TerrainSliders from './terrain-sliders'; +import type { GlobalParams } from './terrain-sliders'; import Toolbar from './toolbar'; import type { ToolbarProps } from './toolbar'; @@ -36,9 +37,13 @@ export default function Editor() { }); // states for size and resolution... - const [globalParams, setGlobalParams] = useState({ + const [globalParams, setGlobalParams] = useState({ size: 20, resolution: 100, + fog: { + intensity: 0.08, + color: [0.686, 0.702, 0.725], + }, }); // hook owning node + edge state, and react flow diff --git a/src/components/editor/terrain-sliders.tsx b/src/components/editor/terrain-sliders.tsx index de9193a..4440cc5 100644 --- a/src/components/editor/terrain-sliders.tsx +++ b/src/components/editor/terrain-sliders.tsx @@ -1,17 +1,21 @@ +export type GlobalParams = { + size: number; + resolution: number; + fog: { intensity: number; color: [number, number, number] }; +}; + export type TerrainSliderProps = { - globalParams: { - size: number; - resolution: number; - }; - setGlobalParams: React.Dispatch< - React.SetStateAction<{ - size: number; - resolution: number; - }> - >; + globalParams: GlobalParams; + setGlobalParams: React.Dispatch>; }; export default function TerrainSliders({ globalParams, setGlobalParams }: TerrainSliderProps) { + const fogColorChannelHexes = globalParams.fog.color.map((v) => + Math.round(v * 255) + .toString(16) + .padStart(2, '0'), + ); + return (
@@ -53,6 +57,33 @@ export default function TerrainSliders({ globalParams, setGlobalParams }: Terrai />
{globalParams.resolution}
+ +
+ +
); } From 3aea22f8d0c4aef1c8214ec6645d9e19d71cbbd4 Mon Sep 17 00:00:00 2001 From: Thomas Shaw Date: Sun, 7 Dec 2025 23:05:09 -0500 Subject: [PATCH 5/8] Add slider for fog intensity --- src/components/editor/terrain-sliders.tsx | 66 ++++++++++++++--------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/src/components/editor/terrain-sliders.tsx b/src/components/editor/terrain-sliders.tsx index 4440cc5..5b6a625 100644 --- a/src/components/editor/terrain-sliders.tsx +++ b/src/components/editor/terrain-sliders.tsx @@ -58,31 +58,49 @@ export default function TerrainSliders({ globalParams, setGlobalParams }: Terrai
{globalParams.resolution}
+ +
- + + { + const v = parseFloat(e.target.value); + setGlobalParams((prev) => ({ + ...prev, + fog: { ...prev.fog, intensity: v }, + })); + }} + className="w-full" + /> +
{globalParams.size.toFixed(0)}
); From 273360ed42010ef8f35069c346f4dd89be72f400 Mon Sep 17 00:00:00 2001 From: Thomas Shaw Date: Sun, 7 Dec 2025 23:09:46 -0500 Subject: [PATCH 6/8] Update fog uniforms on slider update --- src/components/editor/terrain-canvas.tsx | 16 +++++++++++----- src/lib/renderers/terrain-renderer.ts | 18 +++++++++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/components/editor/terrain-canvas.tsx b/src/components/editor/terrain-canvas.tsx index c8ecfab..03558c7 100644 --- a/src/components/editor/terrain-canvas.tsx +++ b/src/components/editor/terrain-canvas.tsx @@ -1,14 +1,13 @@ import { useEffect } from 'react'; +import type { GlobalParams } from './terrain-sliders'; + import WebGPUCanvas, { type WebGPUCanvasProps } from '@/components/common/webgpu-canvas'; -import { - TerrainRenderer, - type TerrainRendererGlobalParameters, -} from '@/lib/renderers/terrain-renderer'; +import { TerrainRenderer } from '@/lib/renderers/terrain-renderer'; export type TerrainCanvasProps = { rendererRef: React.RefObject; - globalParams: TerrainRendererGlobalParameters; + globalParams: GlobalParams; }; const createRenderer: WebGPUCanvasProps['createRenderer'] = async (webGPU, stage) => { @@ -23,6 +22,13 @@ export default function TerrainCanvas({ rendererRef, globalParams }: TerrainCanv rendererRef.current?.setMeshUniforms(globalParams.size, globalParams.resolution); }, [globalParams.resolution, globalParams.size, rendererRef]); + useEffect(() => { + rendererRef.current?.setCameraUniforms({ + fogColor: globalParams.fog.color, + fogIntensity: globalParams.fog.intensity, + }); + }, [globalParams.fog.color, globalParams.fog.intensity, rendererRef]); + return ( Date: Sun, 7 Dec 2025 23:13:53 -0500 Subject: [PATCH 7/8] Consume camera uniforms in shaders --- src/lib/shaders/instancing.wgsl | 5 ++--- src/lib/shaders/naive.fs.wgsl | 6 +++--- src/lib/shaders/water.fs.wgsl | 5 ++--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/lib/shaders/instancing.wgsl b/src/lib/shaders/instancing.wgsl index 6f01736..c01c46b 100644 --- a/src/lib/shaders/instancing.wgsl +++ b/src/lib/shaders/instancing.wgsl @@ -108,10 +108,9 @@ fn fs_main(in: VertexOut) -> @location(0) vec4f discard; } - let fogStrength = 1.0 - exp(-0.08 * length(in.camera_view_pos)); - let fogColor = vec3f(0.686, 0.702, 0.725); + let fogStrength = 1.0 - exp(-camera.fogIntensity * length(in.camera_view_pos)); - let finalColor = mix(color.xyz, fogColor, fogStrength); + let finalColor = mix(color.xyz, camera.fogColor, fogStrength); return vec4f(finalColor, color.a); diff --git a/src/lib/shaders/naive.fs.wgsl b/src/lib/shaders/naive.fs.wgsl index a412e90..ed76a50 100644 --- a/src/lib/shaders/naive.fs.wgsl +++ b/src/lib/shaders/naive.fs.wgsl @@ -11,6 +11,7 @@ struct FragmentInput @location(4) camera_view_pos: vec3f, } +@group(0) @binding(0) var camera : CameraUniforms; @group(0) @binding(1) var directionalLightUniforms: DirectionalLightUniforms; @group(0) @binding(2) var shadow_map: texture_depth_2d; @group(0) @binding(3) var shadow_sampler: sampler; @@ -31,11 +32,10 @@ fn main(in: FragmentInput) -> @location(0) vec4f let ambientLight = vec3f(0.1, 0.1, 0.2); - let fogStrength = 1.0 - exp(-0.08 * length(in.camera_view_pos)); - let fogColor = vec3f(0.686, 0.702, 0.725); + let fogStrength = 1.0 - exp(-camera.fogIntensity * length(in.camera_view_pos)); let baseColor = vec3f(0.8, 0.8, 0.8); - var color = mix(baseColor * (directLight + ambientLight), fogColor, fogStrength); + var color = mix(baseColor * (directLight + ambientLight), camera.fogColor, fogStrength); return vec4f(color, 1.0); } diff --git a/src/lib/shaders/water.fs.wgsl b/src/lib/shaders/water.fs.wgsl index 4fb218c..9374c09 100644 --- a/src/lib/shaders/water.fs.wgsl +++ b/src/lib/shaders/water.fs.wgsl @@ -96,10 +96,9 @@ fn main(in: FragmentInput) -> @location(0) vec4f finalColor *= 0.5; } - let fogStrength = 1.0 - exp(-0.08 * length(in.camera_view_pos)); - let fogColor = vec3f(0.686, 0.702, 0.725); + let fogStrength = 1.0 - exp(-camera.fogIntensity * length(in.camera_view_pos)); - finalColor = mix(finalColor, fogColor, fogStrength); + finalColor = mix(finalColor, camera.fogColor, fogStrength); return vec4f(finalColor, 0.85); } From 9949e155b26d43a85687a4c61b51835f7e301b0e Mon Sep 17 00:00:00 2001 From: Thomas Shaw Date: Sun, 7 Dec 2025 23:14:07 -0500 Subject: [PATCH 8/8] Fix slider preview value, change max --- src/components/editor/terrain-sliders.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/editor/terrain-sliders.tsx b/src/components/editor/terrain-sliders.tsx index 5b6a625..46bc3a6 100644 --- a/src/components/editor/terrain-sliders.tsx +++ b/src/components/editor/terrain-sliders.tsx @@ -88,7 +88,7 @@ export default function TerrainSliders({ globalParams, setGlobalParams }: Terrai { @@ -100,7 +100,7 @@ export default function TerrainSliders({ globalParams, setGlobalParams }: Terrai }} className="w-full" /> -
{globalParams.size.toFixed(0)}
+
{globalParams.fog.intensity}
);