Update terrain shader parameters

- Modified terrain shader parameters in GroundTruthData for better rendering.
- Added new user-configurable settings for water effects in TerrainShaderRegistry.
- Enhanced terrain compute shaders to incorporate water depth and blur adjustments.
- Refactored shader logic to improve water color blending and depth calculations
This commit is contained in:
scamiv
2026-01-20 22:58:27 +01:00
parent 1980834d6c
commit e528988d50
4 changed files with 166 additions and 50 deletions
@@ -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;
@@ -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 };
}
@@ -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<uniform> 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<u32>) {
let x = i32(globalId.x);
@@ -99,18 +90,31 @@ fn main(@builtin(global_invocation_id) globalId: vec3<u32>) {
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<u32>) {
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);
@@ -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<uniform> 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<u32>) {
let x = i32(globalId.x);
@@ -81,8 +78,8 @@ fn main(@builtin(global_invocation_id) globalId: vec3<u32>) {
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<u32>) {
}
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);
}