diff --git a/src/components/editor/index.tsx b/src/components/editor/index.tsx index 4e64291..48d0e00 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.0, + color: [0.686, 0.702, 0.725], + }, }); // hook owning node + edge state, and react flow 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 ( - >; + 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,51 @@ 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.fog.intensity}
+
); } diff --git a/src/lib/renderers/terrain-renderer.ts b/src/lib/renderers/terrain-renderer.ts index c1868dc..9de53c2 100644 --- a/src/lib/renderers/terrain-renderer.ts +++ b/src/lib/renderers/terrain-renderer.ts @@ -18,11 +18,6 @@ import { instanceComputeShaderTemplate } from '@/lib/shaders/jit/templates/insta import * as shaders from '@/lib/shaders/shaders'; import type { WebGPUContext } from '@/lib/webgpu-context'; -export type TerrainRendererGlobalParameters = { - size: number; - resolution: number; -}; - export class TerrainRenderer implements IRenderer { private readonly stage: Stage; @@ -1279,6 +1274,19 @@ export class TerrainRenderer implements IRenderer { this.device.queue.submit([encoder.finish()]); } + setCameraUniforms({ + fogColor, + fogIntensity, + }: { + fogColor?: [number, number, number]; + fogIntensity?: number; + }) { + const cameraUniforms = this.stage.camera.uniforms; + + if (fogColor) cameraUniforms.fogColor = fogColor; + if (fogIntensity) cameraUniforms.fogIntensity = fogIntensity; + } + setDisplacePipelineUniform(key: string, value: number | [number, number, number]) { if (!this.displacePipelineConfigured) { console.log('Cannot set uniform'); 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 { diff --git a/src/lib/shaders/instancing.wgsl b/src/lib/shaders/instancing.wgsl index 1b7dcd8..58a66b3 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, }; fn hash11(n: f32) -> f32 { @@ -98,6 +99,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 * vec4f(worldPos, 1.0)).xyz; return out; } @@ -122,6 +124,9 @@ fn fs_main(in: VertexOut) -> @location(0) vec4f let ambientLight = vec3f(0.1, 0.1, 0.1); + let fogStrength = 1.0 - exp(-camera.fogIntensity * length(in.camera_view_pos)); + var color = diffuse.xyz * (directLight + ambientLight); + color = mix(color.xyz, camera.fogColor, fogStrength); return vec4f(color, 1.0); } diff --git a/src/lib/shaders/naive.fs.wgsl b/src/lib/shaders/naive.fs.wgsl index ba59ed5..b48d816 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, } struct BiomeUniforms { @@ -33,16 +34,16 @@ fn hash(p: vec2f) -> f32 { fn noise(p: vec2f) -> f32 { let i = floor(p); let f = fract(p); - + // Cubic interpolation let u = f * f * (3.0 - 2.0 * f); - + // Sample corners let a = hash(i); let b = hash(i + vec2f(1.0, 0.0)); let c = hash(i + vec2f(0.0, 1.0)); let d = hash(i + vec2f(1.0, 1.0)); - + // Interpolate return mix(mix(a, b, u.x), mix(c, d, u.x), u.y); } @@ -52,13 +53,13 @@ fn fbm(p: vec2f) -> f32 { var amplitude = 0.5; var frequency = 1.0; var pos = p; - + for (var i = 0; i < 4; i++) { value += amplitude * noise(pos * frequency); frequency *= 2.0; amplitude *= 0.5; } - + return value; } @@ -69,9 +70,9 @@ fn celShade(value: f32, bands: f32) -> f32 { fn grassTexture(pos: vec3f) -> vec3f { let noise1 = fbm(pos.xz * 8.0); let noise2 = noise(pos.xz * 2.0); - + let combinedNoise = noise1 * 0.6 + noise2 * 0.4; - + let grassBright = vec3f(0.45, 0.85, 0.15); let grassMid = vec3f(0.25, 0.65, 0.10); let grassDark = vec3f(0.15, 0.45, 0.05); @@ -82,27 +83,27 @@ fn grassTexture(pos: vec3f) -> vec3f { var grassColor = grassDark; grassColor = mix(grassColor, grassMid, t1); grassColor = mix(grassColor, grassBright, t2); - + return grassColor; } fn sandTexture(pos: vec3f) -> vec3f { let sandScale = 20.0; let sandNoise = fbm(pos.xz * sandScale); - + let sandBase = vec3f(0.95, 0.88, 0.71); let sandDark = vec3f(0.85, 0.78, 0.61); - + return mix(sandDark, sandBase, sandNoise); } fn snowTexture(pos: vec3f) -> vec3f { let sandScale = 20.0; let sandNoise = fbm(pos.xz * sandScale); - + let sandBase = vec3f(0.94, 0.99, 1.0); let sandDark = vec3f(0.812, 0.953, 0.969); - + return mix(sandDark, sandBase, sandNoise); } @@ -110,20 +111,20 @@ fn mountainTexture(pos: vec3f) -> vec3f { let noise1 = fbm(pos.xz * 3.0); let noise2 = noise(pos.xz * 10.0); let noise3 = noise(pos.xz * 30.0); - + let combinedNoise = noise1 * 0.5 + noise2 * 0.3 + noise3 * 0.2; - + let rockDark = vec3f(0.35, 0.35, 0.38); let rockMid = vec3f(0.50, 0.48, 0.45); let rockLight = vec3f(0.65, 0.62, 0.58); - + let t1 = smoothstep(0.25, 0.45, combinedNoise); let t2 = smoothstep(0.55, 0.75, combinedNoise); - + var rockColor = rockDark; rockColor = mix(rockColor, rockMid, t1); rockColor = mix(rockColor, rockLight, t2); - + return rockColor; } @@ -148,7 +149,7 @@ fn main(in: FragmentInput) -> @location(0) vec4f let grassStart = waterHeight.height + 0.8; let sandEnd = waterHeight.height + 0.4; - + baseColor = grass; if (waterHeight.height > in.pos.y) { baseColor = underwater; @@ -212,6 +213,8 @@ fn main(in: FragmentInput) -> @location(0) vec4f let ambientLight = vec3f(0.1, 0.1, 0.2); - var color = baseColor * (directLight + ambientLight); + let fogStrength = 1.0 - exp(-camera.fogIntensity * length(in.camera_view_pos)); + + var color = mix(baseColor * (directLight + ambientLight), camera.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; } diff --git a/src/lib/shaders/water.fs.wgsl b/src/lib/shaders/water.fs.wgsl index 4341eb9..9374c09 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,25 @@ 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(-camera.fogIntensity * length(in.camera_view_pos)); + + finalColor = mix(finalColor, camera.fogColor, fogStrength); + return vec4f(finalColor, 0.85); -} \ No newline at end of file +}