diff --git a/src/client/graphics/webgpu/core/GroundTruthData.ts b/src/client/graphics/webgpu/core/GroundTruthData.ts index 44f125b3f..00daa40dc 100644 --- a/src/client/graphics/webgpu/core/GroundTruthData.ts +++ b/src/client/graphics/webgpu/core/GroundTruthData.ts @@ -101,8 +101,8 @@ export class GroundTruthData { private territoryShaderParams0 = new Float32Array(4); private territoryShaderParams1 = new Float32Array(4); - private terrainShaderParams0 = new Float32Array([0.0, 2.5, 0.6, 0.7]); - private terrainShaderParams1 = new Float32Array([0.0, 0.9, 0.6, 0.05]); + private terrainShaderParams0 = new Float32Array([0.0, 2.5, 1.0, 0.0]); + private terrainShaderParams1 = new Float32Array([0.6, 0.0, 0.0, 0.0]); private paletteMaxSmallId = 0; private ownerIndexWidth = 1; diff --git a/src/client/graphics/webgpu/render/TerrainShaderRegistry.ts b/src/client/graphics/webgpu/render/TerrainShaderRegistry.ts index 7470a0806..9d61cc2cd 100644 --- a/src/client/graphics/webgpu/render/TerrainShaderRegistry.ts +++ b/src/client/graphics/webgpu/render/TerrainShaderRegistry.ts @@ -63,6 +63,15 @@ export const TERRAIN_SHADERS: TerrainShaderDefinition[] = [ max: 5, step: 0.25, }, + { + kind: "range", + key: "settings.webgpu.terrain.improvedLite.waterBlurStrength", + label: "Water Blur Strength", + defaultValue: 1, + min: 0, + max: 1, + step: 0.05, + }, ], }, { @@ -97,6 +106,33 @@ export const TERRAIN_SHADERS: TerrainShaderDefinition[] = [ max: 6, step: 0.25, }, + { + kind: "range", + key: "settings.webgpu.terrain.improvedHeavy.waterDepthStrength", + label: "Water Depth Strength", + defaultValue: 0.35, + min: 0, + max: 1, + step: 0.05, + }, + { + kind: "range", + key: "settings.webgpu.terrain.improvedHeavy.waterDepthCurve", + label: "Water Depth Curve", + defaultValue: 2, + min: 0.5, + max: 4, + step: 0.25, + }, + { + kind: "range", + key: "settings.webgpu.terrain.improvedHeavy.waterDepthBlur", + label: "Water Depth Blur", + defaultValue: 0.6, + min: 0, + max: 1, + step: 0.05, + }, { kind: "range", key: "settings.webgpu.terrain.improvedHeavy.lightingStrength", @@ -153,9 +189,9 @@ export function buildTerrainShaderParams( }, shaderId: TerrainShaderId, ): { shaderPath: string; params0: Float32Array; params1: Float32Array } { - const shorelineMixLand = 0.6; - const shorelineMixWater = 0.7; - const specularStrength = 0.05; + const waterDepthStrengthDefault = 0.4; + const waterDepthCurveDefault = 2; + const waterDepthBlurDefault = 0.6; if (shaderId === "improved-lite") { const noiseStrength = userSettings.getFloat( @@ -166,14 +202,17 @@ export function buildTerrainShaderParams( "settings.webgpu.terrain.improvedLite.blendWidth", 5, ); - + const waterBlurStrength = userSettings.getFloat( + "settings.webgpu.terrain.improvedLite.waterBlurStrength", + 1, + ); const params0 = new Float32Array([ noiseStrength, blendWidth, - shorelineMixLand, - shorelineMixWater, + waterBlurStrength, + 0, ]); - const params1 = new Float32Array([0, 0, 0, specularStrength]); + const params1 = new Float32Array([0, 0, 0, 0]); return { shaderPath: "compute/terrain-compute-improved-lite.wgsl", params0, @@ -194,6 +233,18 @@ export function buildTerrainShaderParams( "settings.webgpu.terrain.improvedHeavy.blendWidth", 4.5, ); + const waterDepthStrength = userSettings.getFloat( + "settings.webgpu.terrain.improvedHeavy.waterDepthStrength", + 0.35, + ); + const waterDepthCurve = userSettings.getFloat( + "settings.webgpu.terrain.improvedHeavy.waterDepthCurve", + 2, + ); + const waterDepthBlur = userSettings.getFloat( + "settings.webgpu.terrain.improvedHeavy.waterDepthBlur", + 0.6, + ); const lightingStrength = userSettings.getFloat( "settings.webgpu.terrain.improvedHeavy.lightingStrength", 0.3, @@ -206,14 +257,14 @@ export function buildTerrainShaderParams( const params0 = new Float32Array([ noiseStrength, blendWidth, - shorelineMixLand, - shorelineMixWater, + waterDepthStrength, + waterDepthCurve, ]); const params1 = new Float32Array([ detailNoiseStrength, lightingStrength, cavityStrength, - specularStrength, + waterDepthBlur, ]); return { shaderPath: "compute/terrain-compute-improved-heavy.wgsl", @@ -225,9 +276,9 @@ export function buildTerrainShaderParams( const params0 = new Float32Array([ 0, 2.5, - shorelineMixLand, - shorelineMixWater, + waterDepthStrengthDefault, + waterDepthCurveDefault, ]); - const params1 = new Float32Array([0, 0, 0, specularStrength]); + const params1 = new Float32Array([waterDepthBlurDefault, 0, 0, 0]); return { shaderPath: "compute/terrain-compute.wgsl", params0, params1 }; } diff --git a/src/client/graphics/webgpu/shaders/compute/terrain-compute-improved-heavy.wgsl b/src/client/graphics/webgpu/shaders/compute/terrain-compute-improved-heavy.wgsl index 09d64cea1..611a2c1a0 100644 --- a/src/client/graphics/webgpu/shaders/compute/terrain-compute-improved-heavy.wgsl +++ b/src/client/graphics/webgpu/shaders/compute/terrain-compute-improved-heavy.wgsl @@ -5,8 +5,8 @@ struct TerrainParams { plainsBaseColor: vec4f, // Plains base RGB (magnitude 0) highlandBaseColor: vec4f, // Highland base RGB (magnitude 10) mountainBaseColor: vec4f, // Mountain base RGB (magnitude 20) - tuning0: vec4f, // x=noiseStrength, y=blendWidth, z=shoreMixLand, w=shoreMixWater - tuning1: vec4f, // x=detailNoise, y=lightingStrength, z=cavityStrength, w=specularStrength + tuning0: vec4f, // x=noiseStrength, y=blendWidth, z=waterDepthStrength, w=waterDepthCurve + tuning1: vec4f, // x=detailNoise, y=lightingStrength, z=cavityStrength, w=waterDepthBlur }; @group(0) @binding(0) var params: TerrainParams; @@ -34,10 +34,9 @@ fn clampCoord(coord: vec2i, dims: vec2u) -> vec2i { return vec2i(clamp(coord.x, 0, maxX), clamp(coord.y, 0, maxY)); } -fn sampleMagnitude(coord: vec2i, dims: vec2u) -> f32 { +fn sampleTerrainData(coord: vec2i, dims: vec2u) -> u32 { let c = clampCoord(coord, dims); - let data = textureLoad(terrainDataTex, c, 0).x; - return f32(data & MAGNITUDE_MASK); + return textureLoad(terrainDataTex, c, 0).x; } fn computeLandColor( @@ -69,14 +68,6 @@ fn computeLandColor( return clamp(land + vec3f(noiseBias), vec3f(0.0), vec3f(1.0)); } -fn computeWaterColor(mag: f32, noise: f32, noiseStrength: f32) -> vec3f { - let depth = clamp(mag / 10.0, 0.0, 1.0); - var water = mix(params.shorelineWaterColor.rgb, params.waterColor.rgb, depth); - let noiseBias = (noise - 0.5) * noiseStrength; - water = clamp(water + vec3f(noiseBias), vec3f(0.0), vec3f(1.0)); - return water; -} - @compute @workgroup_size(8, 8) fn main(@builtin(global_invocation_id) globalId: vec3) { let x = i32(globalId.x); @@ -99,18 +90,31 @@ fn main(@builtin(global_invocation_id) globalId: vec3) { let noiseFine = hash21(vec2u(texCoord) * 3u + vec2u(17u, 29u)); let noiseStrength = max(params.tuning0.x, 0.0); let blendWidth = max(params.tuning0.y, 0.1); - let shoreMixLand = clamp(params.tuning0.z, 0.0, 1.0); - let shoreMixWater = clamp(params.tuning0.w, 0.0, 1.0); + let waterDepthStrength = clamp(params.tuning0.z, 0.0, 1.0); + let waterDepthCurve = max(params.tuning0.w, 0.1); let detailNoiseStrength = max(params.tuning1.x, 0.0); let lightingStrength = clamp(params.tuning1.y, 0.0, 1.0); let cavityStrength = clamp(params.tuning1.z, 0.0, 1.0); - let specularStrength = max(params.tuning1.w, 0.0); + let waterDepthBlur = clamp(params.tuning1.w, 0.0, 1.0); + let shoreMixLand = 0.6; + let shoreMixWater = 0.55; + let specularStrength = 0.05; let hC = mag / 31.0; - let hL = sampleMagnitude(texCoord + vec2i(-1, 0), dims) / 31.0; - let hR = sampleMagnitude(texCoord + vec2i(1, 0), dims) / 31.0; - let hD = sampleMagnitude(texCoord + vec2i(0, -1), dims) / 31.0; - let hU = sampleMagnitude(texCoord + vec2i(0, 1), dims) / 31.0; + let dataL = sampleTerrainData(texCoord + vec2i(-1, 0), dims); + let dataR = sampleTerrainData(texCoord + vec2i(1, 0), dims); + let dataD = sampleTerrainData(texCoord + vec2i(0, -1), dims); + let dataU = sampleTerrainData(texCoord + vec2i(0, 1), dims); + + let magL = f32(dataL & MAGNITUDE_MASK); + let magR = f32(dataR & MAGNITUDE_MASK); + let magD = f32(dataD & MAGNITUDE_MASK); + let magU = f32(dataU & MAGNITUDE_MASK); + + let hL = magL / 31.0; + let hR = magR / 31.0; + let hD = magD / 31.0; + let hU = magU / 31.0; let dx = hR - hL; let dy = hU - hD; @@ -146,7 +150,37 @@ fn main(@builtin(global_invocation_id) globalId: vec3) { color = vec4f(land, 1.0); } else { - var water = computeWaterColor(mag, noise, noiseStrength * 0.6); + var sum = mag; + var count = 1.0; + if ((dataL & (1u << IS_LAND_BIT)) == 0u) { + sum = sum + magL; + count = count + 1.0; + } + if ((dataR & (1u << IS_LAND_BIT)) == 0u) { + sum = sum + magR; + count = count + 1.0; + } + if ((dataD & (1u << IS_LAND_BIT)) == 0u) { + sum = sum + magD; + count = count + 1.0; + } + if ((dataU & (1u << IS_LAND_BIT)) == 0u) { + sum = sum + magU; + count = count + 1.0; + } + + let avgMag = sum / count; + let smoothMag = mix(mag, avgMag, waterDepthBlur); + let depth01 = clamp(smoothMag / 10.0, 0.0, 1.0); + let depth = clamp(pow(depth01, waterDepthCurve), 0.0, 1.0); + let depthColor = mix( + params.shorelineWaterColor.rgb, + params.waterColor.rgb, + depth, + ); + var water = mix(params.waterColor.rgb, depthColor, waterDepthStrength); + let noiseBias = (noise - 0.5) * (noiseStrength * 0.6); + water = clamp(water + vec3f(noiseBias), vec3f(0.0), vec3f(1.0)); if (isShoreline) { water = mix(water, params.shorelineWaterColor.rgb, shoreMixWater); diff --git a/src/client/graphics/webgpu/shaders/compute/terrain-compute-improved-lite.wgsl b/src/client/graphics/webgpu/shaders/compute/terrain-compute-improved-lite.wgsl index 97666c68d..95e4dfa5a 100644 --- a/src/client/graphics/webgpu/shaders/compute/terrain-compute-improved-lite.wgsl +++ b/src/client/graphics/webgpu/shaders/compute/terrain-compute-improved-lite.wgsl @@ -5,8 +5,7 @@ struct TerrainParams { plainsBaseColor: vec4f, // Plains base RGB (magnitude 0) highlandBaseColor: vec4f, // Highland base RGB (magnitude 10) mountainBaseColor: vec4f, // Mountain base RGB (magnitude 20) - tuning0: vec4f, // x=noiseStrength, y=blendWidth, z=shoreMixLand, w=shoreMixWater - tuning1: vec4f, // unused in lite + tuning0: vec4f, // x=noiseStrength, y=blendWidth, z=waterBlurStrength, w=unused }; @group(0) @binding(0) var params: TerrainParams; @@ -28,6 +27,12 @@ fn hash21(p: vec2u) -> f32 { return f32(n & 0x00ffffffu) / 16777215.0; } +fn clampCoord(coord: vec2i, dims: vec2u) -> vec2i { + let maxX = i32(dims.x) - 1; + let maxY = i32(dims.y) - 1; + return vec2i(clamp(coord.x, 0, maxX), clamp(coord.y, 0, maxY)); +} + fn computeLandColor(mag: f32, noise: f32, noiseStrength: f32, blendWidth: f32) -> vec3f { let plainsG = max(params.plainsBaseColor.g - (2.0 * mag) / 255.0, 0.0); let plains = vec3f(params.plainsBaseColor.r, plainsG, params.plainsBaseColor.b); @@ -52,14 +57,6 @@ fn computeLandColor(mag: f32, noise: f32, noiseStrength: f32, blendWidth: f32) - return clamp(land + vec3f(noiseBias), vec3f(0.0), vec3f(1.0)); } -fn computeWaterColor(mag: f32, noise: f32, noiseStrength: f32) -> vec3f { - let depth = clamp(mag / 10.0, 0.0, 1.0); - var water = mix(params.shorelineWaterColor.rgb, params.waterColor.rgb, depth); - let noiseBias = (noise - 0.5) * noiseStrength; - water = clamp(water + vec3f(noiseBias), vec3f(0.0), vec3f(1.0)); - return water; -} - @compute @workgroup_size(8, 8) fn main(@builtin(global_invocation_id) globalId: vec3) { let x = i32(globalId.x); @@ -81,8 +78,8 @@ fn main(@builtin(global_invocation_id) globalId: vec3) { let noise = hash21(vec2u(texCoord)); let noiseStrength = max(params.tuning0.x, 0.0); let blendWidth = max(params.tuning0.y, 0.1); - let shoreMixLand = clamp(params.tuning0.z, 0.0, 1.0); - let shoreMixWater = clamp(params.tuning0.w, 0.0, 1.0); + let waterDepthBlur = clamp(params.tuning0.z, 0.0, 1.0); + let shoreMixLand = 0.6; var color: vec4f; if (isLand) { @@ -92,10 +89,44 @@ fn main(@builtin(global_invocation_id) globalId: vec3) { } color = vec4f(land, 1.0); } else { - var water = computeWaterColor(mag, noise, noiseStrength * 0.6); if (isShoreline) { - water = mix(water, params.shorelineWaterColor.rgb, shoreMixWater); + color = vec4f(params.shorelineWaterColor.rgb, 1.0); + textureStore(terrainTex, texCoord, color); + return; } + + var sum = mag; + var count = 1.0; + let dataL = textureLoad(terrainDataTex, clampCoord(texCoord + vec2i(-1, 0), dims), 0).x; + if ((dataL & (1u << IS_LAND_BIT)) == 0u) { + sum = sum + f32(dataL & MAGNITUDE_MASK); + count = count + 1.0; + } + let dataR = textureLoad(terrainDataTex, clampCoord(texCoord + vec2i(1, 0), dims), 0).x; + if ((dataR & (1u << IS_LAND_BIT)) == 0u) { + sum = sum + f32(dataR & MAGNITUDE_MASK); + count = count + 1.0; + } + let dataD = textureLoad(terrainDataTex, clampCoord(texCoord + vec2i(0, -1), dims), 0).x; + if ((dataD & (1u << IS_LAND_BIT)) == 0u) { + sum = sum + f32(dataD & MAGNITUDE_MASK); + count = count + 1.0; + } + let dataU = textureLoad(terrainDataTex, clampCoord(texCoord + vec2i(0, 1), dims), 0).x; + if ((dataU & (1u << IS_LAND_BIT)) == 0u) { + sum = sum + f32(dataU & MAGNITUDE_MASK); + count = count + 1.0; + } + + let avgMag = sum / count; + let smoothMag = mix(mag, avgMag, waterDepthBlur); + let magClamped = min(smoothMag, 10.0); + let adjustment = (1.0 - magClamped) / 255.0; + let water = vec3f( + max(params.waterColor.r + adjustment, 0.0), + max(params.waterColor.g + adjustment, 0.0), + max(params.waterColor.b + adjustment, 0.0), + ); color = vec4f(water, 1.0); }