Browse Source

Expand ground variations with distinct features and enhanced shaders

Co-authored-by: djeada <[email protected]>
copilot-swe-agent[bot] 2 weeks ago
parent
commit
fb3b4a1d5a

+ 40 - 6
assets/maps/GROUND_TYPES.md

@@ -1,16 +1,50 @@
 # Ground Type System
 
-The ground type system allows designers to quickly select from predefined ground variations when creating maps. Each ground type provides a different visual appearance for the terrain.
+The ground type system allows designers to quickly select from predefined ground variations when creating maps. Each ground type provides a different visual appearance for the terrain with distinct shader effects.
 
 ## Available Ground Types
 
 | Ground Type ID  | Description                        | Visual Characteristics                                 |
 |-----------------|------------------------------------|---------------------------------------------------------|
-| `forest_mud`    | Deep Green + Mud Ground (Default)  | Lush green grass with dark brown muddy soil            |
-| `grass_dry`     | Dry Mediterranean Grass            | Yellowish-green grass with light brown dusty soil      |
-| `soil_rocky`    | Light-Brown Rocky Soil             | Sparse grass with prominent light brown rocks          |
-| `alpine_mix`    | Alpine Rock + Snow Mix             | Cool-toned grass with light gray/white rocky terrain   |
-| `soil_fertile`  | Dark Fertile Farmland Soil         | Rich green grass with very dark brown fertile soil     |
+| `forest_mud`    | Deep Green + Mud Ground (Default)  | Lush green grass with dark brown muddy soil, moderate moisture |
+| `grass_dry`     | Dry Mediterranean Grass            | Sparse yellowed grass with cracked dusty soil, low moisture |
+| `soil_rocky`    | Light-Brown Rocky Soil             | Sparse grass with prominent exposed rocks, rough terrain |
+| `alpine_mix`    | Alpine Rock + Snow Mix             | Hardy grass with snow patches and lichen-covered rocks |
+| `soil_fertile`  | Dark Fertile Farmland Soil         | Thick healthy grass with rich dark soil, high moisture |
+
+## Distinct Features Per Ground Type
+
+Each ground type configures multiple distinct visual features:
+
+### forest_mud (Default)
+- **Vegetation**: Dense, tall grass (0.60-1.40 height)
+- **Moisture**: High (0.70) - wet, muddy appearance
+- **Saturation**: Normal (1.05) - vibrant greens
+- **Snow**: None
+
+### grass_dry
+- **Vegetation**: Sparse, short grass (0.35-0.80 height)
+- **Moisture**: Very low (0.15) - parched appearance
+- **Cracking**: High (0.65) - visible ground cracks
+- **Saturation**: Reduced (0.75) - faded, dusty colors
+
+### soil_rocky
+- **Vegetation**: Minimal grass clumps (0.30-0.70 height)
+- **Rock Exposure**: High (0.75) - prominent rocky outcrops
+- **Roughness**: Very high (0.85) - textured rocky surface
+- **Irregularity**: High amplitude - uneven terrain
+
+### alpine_mix
+- **Vegetation**: Hardy alpine grass (0.20-0.50 height)
+- **Snow Coverage**: Moderate (0.55) - patchy snow accumulation
+- **Snow Color**: Bright white-blue tint
+- **Ambient**: Bright (1.25) - high altitude light
+
+### soil_fertile
+- **Vegetation**: Thick healthy grass (0.55-1.25 height)
+- **Moisture**: High (0.80) - rich, dark soil
+- **Saturation**: High (1.15) - vivid greens
+- **Roughness**: Low (0.42) - smooth farmland
 
 ## Usage
 

+ 45 - 3
assets/shaders/ground_plane.frag

@@ -18,6 +18,15 @@ uniform float u_soilBlendSharpness;
 uniform float u_ambientBoost;
 uniform vec3 u_lightDir;
 
+// Ground-type-specific uniforms
+uniform float u_snowCoverage;      // 0-1: snow accumulation
+uniform float u_moistureLevel;     // 0-1: wetness/dryness
+uniform float u_crackIntensity;    // 0-1: ground cracking
+uniform float u_rockExposure;      // 0-1: rock visibility
+uniform float u_grassSaturation;   // 0-1.5: grass color intensity
+uniform float u_soilRoughness;     // 0-1: soil texture roughness
+uniform vec3 u_snowColor;          // Snow tint color
+
 float hash21(vec2 p) {
   return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
 }
@@ -62,6 +71,35 @@ void main() {
   mudPatch = smoothstep(0.65, 0.75, mudPatch);
   soilMix = max(soilMix, mudPatch * 0.85);
   vec3 baseCol = mix(grassCol, u_soilColor, soilMix);
+
+  // Ground cracking effect (for dry Mediterranean terrain)
+  if (u_crackIntensity > 0.01) {
+    float crackNoise1 = noise21(wuv * 8.0);
+    float crackNoise2 = noise21(wuv * 16.0 + vec2(42.0, 17.0));
+    float crackPattern = smoothstep(0.45, 0.50, crackNoise1) * 
+                         smoothstep(0.40, 0.55, crackNoise2);
+    float crackDarkening = 1.0 - crackPattern * u_crackIntensity * 0.35;
+    baseCol *= crackDarkening;
+  }
+
+  // Snow coverage effect (for alpine terrain)
+  if (u_snowCoverage > 0.01) {
+    float snowNoise = fbm(wuv * 0.5 + vec2(123.0, 456.0));
+    float snowAccumulation = smoothstep(0.3, 0.7, snowNoise);
+    float heightSnowBonus = smoothstep(-0.5, 1.5, v_worldPos.y) * 0.3;
+    float snowMask = clamp(snowAccumulation * (u_snowCoverage + heightSnowBonus), 0.0, 1.0);
+    vec3 snowTinted = u_snowColor * (1.0 + detail * 0.1);
+    baseCol = mix(baseCol, snowTinted, snowMask * 0.85);
+  }
+
+  // Apply grass saturation modifier
+  vec3 grayLevel = vec3(dot(baseCol, vec3(0.299, 0.587, 0.114)));
+  baseCol = mix(grayLevel, baseCol, u_grassSaturation);
+
+  // Moisture effect
+  float wetDarkening = 1.0 - u_moistureLevel * 0.15;
+  baseCol *= wetDarkening;
+
   vec3 dx = dFdx(v_worldPos), dy = dFdy(v_worldPos);
   float mScale = u_detailNoiseScale * 8.0 / ts;
   float h0 = noise21(wuv * mScale);
@@ -72,7 +110,8 @@ void main() {
   vec3 b = normalize(cross(n, t));
   float microAmp = 0.12;
   vec3 nMicro = normalize(n - (t * g.x + b * g.y) * microAmp);
-  float jitter = (hash21(wuv * 0.27 + vec2(17.0, 9.0)) - 0.5) * 0.06;
+  float jitterAmp = 0.06 * (0.5 + u_soilRoughness * 0.5);
+  float jitter = (hash21(wuv * 0.27 + vec2(17.0, 9.0)) - 0.5) * jitterAmp;
   float brightnessVar = (moistureVar - 0.5) * 0.08;
   vec3 col = baseCol * (1.0 + jitter + brightnessVar);
   col *= u_tint;
@@ -80,8 +119,11 @@ void main() {
   float ndl = max(dot(nMicro, L), 0.0);
   float ambient = 0.40;
   float fres = pow(1.0 - max(dot(nMicro, vec3(0, 1, 0)), 0.0), 2.0);
-  float roughnessVar = mix(0.65, 0.95, 1.0 - moistureVar);
-  float specContrib = fres * 0.08 * roughnessVar;
+  // Surface roughness affects specular - wet surfaces are shinier
+  float surfaceRoughness = mix(0.65, 0.95, u_soilRoughness);
+  surfaceRoughness = mix(surfaceRoughness, 0.45, u_moistureLevel * 0.5);
+  float specContrib = fres * 0.08 * (1.0 - surfaceRoughness);
+  specContrib += u_moistureLevel * 0.06 * fres;
   float shade = ambient + ndl * 0.65 + specContrib;
   vec3 lit = col * shade * (u_ambientBoost + heightTint);
 

+ 57 - 4
assets/shaders/terrain_chunk.frag

@@ -16,6 +16,15 @@ uniform float u_soilBlendHeight, u_soilBlendSharpness;
 uniform float u_heightNoiseStrength, u_heightNoiseFrequency;
 uniform float u_ambientBoost, u_rockDetailStrength;
 
+// Ground-type-specific uniforms
+uniform float u_snowCoverage;      // 0-1: snow accumulation (alpine_mix)
+uniform float u_moistureLevel;     // 0-1: wetness/dryness
+uniform float u_crackIntensity;    // 0-1: ground cracking (grass_dry)
+uniform float u_rockExposure;      // 0-1: how much rock shows through
+uniform float u_grassSaturation;   // 0-1.5: grass color intensity
+uniform float u_soilRoughness;     // 0-1: soil texture roughness
+uniform vec3 u_snowColor;          // Snow tint color
+
 // lets soil “climb” up steep toes (world units)
 uniform float u_soilFootHeight; // try 0.6–1.2
 
@@ -258,10 +267,50 @@ void main() {
       clamp(rockMask + rimFactor * 0.10 - plateauFactor * 0.06 - isGully * 0.08,
             0.0, 1.0);
 
+  // Apply rock exposure modifier from ground type
+  rockMask = clamp(rockMask + (u_rockExposure - 0.3) * 0.4, 0.0, 1.0);
+
   vec3 terrainColor = mix(soilBlend, rockColor, rockMask);
 
-  // albedo jitter
-  float jitter = (hash21(world_coord * 0.27 + vec2(17.0, 9.0)) - 0.5) * 0.06;
+  // Ground cracking effect (for dry Mediterranean terrain)
+  if (u_crackIntensity > 0.01) {
+    float crackNoise1 = noise21(world_coord * 8.0);
+    float crackNoise2 = noise21(world_coord * 16.0 + vec2(42.0, 17.0));
+    float crackPattern = smoothstep(0.45, 0.50, crackNoise1) * 
+                         smoothstep(0.40, 0.55, crackNoise2);
+    crackPattern *= (1.0 - slope * 0.8); // Less cracking on slopes
+    float crackDarkening = 1.0 - crackPattern * u_crackIntensity * 0.35;
+    terrainColor *= crackDarkening;
+  }
+
+  // Snow coverage effect (for alpine terrain)
+  if (u_snowCoverage > 0.01) {
+    float snowNoise = fbm(world_coord * 0.5 + vec2(123.0, 456.0));
+    float snowAccumulation = smoothstep(0.3, 0.7, snowNoise);
+    // Snow accumulates more on flat areas and less on steep slopes
+    float slopeSnowReduction = 1.0 - smoothstep(0.15, 0.45, slope);
+    // Higher areas get more snow
+    float heightSnowBonus = smoothstep(-0.5, 1.5, v_worldPos.y) * 0.3;
+    float snowMask = clamp(snowAccumulation * slopeSnowReduction * 
+                          (u_snowCoverage + heightSnowBonus), 0.0, 1.0);
+    // Blend in snow color with brightness boost
+    vec3 snowTinted = u_snowColor * (1.0 + detailNoise * 0.1);
+    terrainColor = mix(terrainColor, snowTinted, snowMask * 0.85);
+  }
+
+  // Apply grass saturation modifier
+  vec3 grayLevel = vec3(dot(terrainColor, vec3(0.299, 0.587, 0.114)));
+  terrainColor = mix(grayLevel, terrainColor, u_grassSaturation);
+
+  // Moisture effect on surface appearance
+  float moistureEffect = u_moistureLevel;
+  // Wet surfaces are darker and more saturated
+  float wetDarkening = 1.0 - moistureEffect * 0.15 * (1.0 - rockMask);
+  terrainColor *= wetDarkening;
+
+  // albedo jitter - modulated by soil roughness
+  float jitterAmp = 0.06 * (0.5 + u_soilRoughness * 0.5);
+  float jitter = (hash21(world_coord * 0.27 + vec2(17.0, 9.0)) - 0.5) * jitterAmp;
   float brightnessVar = (moistureVar - 0.5) * 0.08 * (1.0 - rockMask);
   terrainColor *= (1.0 + jitter + brightnessVar) * u_tint;
 
@@ -271,8 +320,12 @@ void main() {
   float ambient = 0.35;
   float fresnel =
       pow(1.0 - max(dot(microNormal, vec3(0.0, 1.0, 0.0)), 0.0), 2.0);
-  float roughnessVar = mix(0.65, 0.95, 1.0 - moistureVar);
-  float specContrib = fresnel * 0.12 * roughnessVar * (1.0 - rockMask);
+  // Surface roughness affects specular - wet surfaces are shinier
+  float surfaceRoughness = mix(0.65, 0.95, u_soilRoughness);
+  surfaceRoughness = mix(surfaceRoughness, 0.45, u_moistureLevel * 0.5);
+  float specContrib = fresnel * 0.12 * (1.0 - surfaceRoughness) * (1.0 - rockMask);
+  // Add subtle moisture-based specular for wet surfaces
+  specContrib += u_moistureLevel * 0.08 * fresnel * (1.0 - rockMask);
   float shade = ambient + ndl * 0.75 + specContrib;
 
   float plateauBrightness = 1.0 + plateauFactor * 0.05;

+ 198 - 30
game/map/terrain.h

@@ -158,6 +158,15 @@ struct BiomeSettings {
   bool groundIrregularityEnabled = true;
   float irregularityScale = 0.15F;
   float irregularityAmplitude = 0.08F;
+
+  // Ground-type-specific shader parameters
+  float snowCoverage = 0.0F;      // 0-1: snow accumulation (alpine_mix)
+  float moistureLevel = 0.5F;     // 0-1: wetness/dryness (affects soil/grass)
+  float crackIntensity = 0.0F;    // 0-1: ground cracking (grass_dry)
+  float rockExposure = 0.3F;      // 0-1: how much rock shows through (soil_rocky)
+  float grassSaturation = 1.0F;   // 0-1.5: grass color intensity
+  float soilRoughness = 0.5F;     // 0-1: soil texture roughness
+  QVector3D snowColor{0.92F, 0.94F, 0.98F}; // Snow tint color (alpine_mix)
 };
 
 inline void applyGroundTypeDefaults(BiomeSettings &settings,
@@ -166,6 +175,7 @@ inline void applyGroundTypeDefaults(BiomeSettings &settings,
   switch (groundType) {
   case GroundType::ForestMud:
     // Default: Deep green + mud ground (current style)
+    // Lush vegetation with wet, muddy soil in shaded forest areas
     settings.grassPrimary = QVector3D(0.30F, 0.60F, 0.28F);
     settings.grassSecondary = QVector3D(0.44F, 0.70F, 0.32F);
     settings.grassDry = QVector3D(0.72F, 0.66F, 0.48F);
@@ -174,50 +184,208 @@ inline void applyGroundTypeDefaults(BiomeSettings &settings,
     settings.rockHigh = QVector3D(0.68F, 0.69F, 0.73F);
     settings.terrainAmbientBoost = 1.08F;
     settings.terrainRockDetailStrength = 0.35F;
+    // Grass/vegetation properties - tall lush grass
+    settings.patchDensity = 4.5F;
+    settings.patchJitter = 0.95F;
+    settings.backgroundBladeDensity = 0.70F;
+    settings.bladeHeightMin = 0.60F;
+    settings.bladeHeightMax = 1.40F;
+    settings.bladeWidthMin = 0.028F;
+    settings.bladeWidthMax = 0.058F;
+    settings.sway_strength = 0.28F;
+    settings.sway_speed = 1.3F;
+    // Terrain noise - moderate variation with mud patches
+    settings.terrainMacroNoiseScale = 0.035F;
+    settings.terrainDetailNoiseScale = 0.14F;
+    settings.terrainSoilHeight = 0.65F;
+    settings.terrainSoilSharpness = 3.5F;
+    settings.terrainRockThreshold = 0.48F;
+    settings.terrainRockSharpness = 3.2F;
+    // Ground irregularity - moderate for forest floor
+    settings.groundIrregularityEnabled = true;
+    settings.irregularityScale = 0.15F;
+    settings.irregularityAmplitude = 0.09F;
+    settings.plant_density = 0.60F;
+    // Ground-type-specific shader parameters - forest_mud
+    settings.snowCoverage = 0.0F;
+    settings.moistureLevel = 0.70F;
+    settings.crackIntensity = 0.0F;
+    settings.rockExposure = 0.25F;
+    settings.grassSaturation = 1.05F;
+    settings.soilRoughness = 0.55F;
+    settings.snowColor = QVector3D(0.92F, 0.94F, 0.98F);
     break;
+
   case GroundType::GrassDry:
     // Dry Mediterranean Grass
-    settings.grassPrimary = QVector3D(0.55F, 0.52F, 0.30F);
-    settings.grassSecondary = QVector3D(0.62F, 0.58F, 0.35F);
-    settings.grassDry = QVector3D(0.75F, 0.68F, 0.42F);
-    settings.soilColor = QVector3D(0.45F, 0.38F, 0.28F);
-    settings.rockLow = QVector3D(0.58F, 0.55F, 0.50F);
-    settings.rockHigh = QVector3D(0.72F, 0.70F, 0.65F);
-    settings.terrainAmbientBoost = 1.15F;
-    settings.terrainRockDetailStrength = 0.30F;
+    // Sparse, yellowed grass with dusty exposed soil and cracked earth
+    settings.grassPrimary = QVector3D(0.58F, 0.54F, 0.32F);
+    settings.grassSecondary = QVector3D(0.65F, 0.60F, 0.38F);
+    settings.grassDry = QVector3D(0.78F, 0.72F, 0.45F);
+    settings.soilColor = QVector3D(0.52F, 0.44F, 0.32F);
+    settings.rockLow = QVector3D(0.62F, 0.58F, 0.52F);
+    settings.rockHigh = QVector3D(0.78F, 0.75F, 0.70F);
+    settings.terrainAmbientBoost = 1.18F;
+    settings.terrainRockDetailStrength = 0.28F;
+    // Grass/vegetation properties - short sparse dry grass
+    settings.patchDensity = 2.8F;
+    settings.patchJitter = 0.75F;
+    settings.backgroundBladeDensity = 0.35F;
+    settings.bladeHeightMin = 0.35F;
+    settings.bladeHeightMax = 0.80F;
+    settings.bladeWidthMin = 0.018F;
+    settings.bladeWidthMax = 0.038F;
+    settings.sway_strength = 0.15F;
+    settings.sway_speed = 1.8F;
+    // Terrain noise - less variation, more cracked appearance
+    settings.terrainMacroNoiseScale = 0.028F;
+    settings.terrainDetailNoiseScale = 0.22F;
+    settings.terrainSoilHeight = 0.50F;
+    settings.terrainSoilSharpness = 4.5F;
+    settings.terrainRockThreshold = 0.38F;
+    settings.terrainRockSharpness = 2.8F;
+    // Ground irregularity - low for dry packed earth
+    settings.groundIrregularityEnabled = true;
+    settings.irregularityScale = 0.10F;
+    settings.irregularityAmplitude = 0.04F;
+    settings.plant_density = 0.25F;
+    // Ground-type-specific shader parameters - grass_dry
+    settings.snowCoverage = 0.0F;
+    settings.moistureLevel = 0.15F;
+    settings.crackIntensity = 0.65F;
+    settings.rockExposure = 0.35F;
+    settings.grassSaturation = 0.75F;
+    settings.soilRoughness = 0.72F;
+    settings.snowColor = QVector3D(0.92F, 0.94F, 0.98F);
     break;
+
   case GroundType::SoilRocky:
     // Light-Brown Rocky Soil
-    settings.grassPrimary = QVector3D(0.42F, 0.48F, 0.30F);
-    settings.grassSecondary = QVector3D(0.50F, 0.54F, 0.35F);
-    settings.grassDry = QVector3D(0.60F, 0.55F, 0.40F);
-    settings.soilColor = QVector3D(0.50F, 0.42F, 0.32F);
-    settings.rockLow = QVector3D(0.55F, 0.52F, 0.48F);
-    settings.rockHigh = QVector3D(0.70F, 0.68F, 0.65F);
+    // Exposed rocks with thin soil cover, mountain foothills aesthetic
+    settings.grassPrimary = QVector3D(0.40F, 0.45F, 0.28F);
+    settings.grassSecondary = QVector3D(0.48F, 0.52F, 0.32F);
+    settings.grassDry = QVector3D(0.58F, 0.52F, 0.38F);
+    settings.soilColor = QVector3D(0.55F, 0.48F, 0.38F);
+    settings.rockLow = QVector3D(0.52F, 0.50F, 0.46F);
+    settings.rockHigh = QVector3D(0.72F, 0.70F, 0.66F);
     settings.terrainAmbientBoost = 1.05F;
-    settings.terrainRockDetailStrength = 0.55F;
+    settings.terrainRockDetailStrength = 0.65F;
+    // Grass/vegetation properties - sparse clumps between rocks
+    settings.patchDensity = 2.2F;
+    settings.patchJitter = 0.60F;
+    settings.backgroundBladeDensity = 0.28F;
+    settings.bladeHeightMin = 0.30F;
+    settings.bladeHeightMax = 0.70F;
+    settings.bladeWidthMin = 0.020F;
+    settings.bladeWidthMax = 0.040F;
+    settings.sway_strength = 0.18F;
+    settings.sway_speed = 1.5F;
+    // Terrain noise - high detail for rocky texture
+    settings.terrainMacroNoiseScale = 0.055F;
+    settings.terrainDetailNoiseScale = 0.28F;
+    settings.terrainSoilHeight = 0.40F;
+    settings.terrainSoilSharpness = 5.0F;
+    settings.terrainRockThreshold = 0.28F;
+    settings.terrainRockSharpness = 4.0F;
+    // Ground irregularity - high for rocky terrain
+    settings.groundIrregularityEnabled = true;
+    settings.irregularityScale = 0.22F;
+    settings.irregularityAmplitude = 0.14F;
+    settings.plant_density = 0.18F;
+    // Ground-type-specific shader parameters - soil_rocky
+    settings.snowCoverage = 0.0F;
+    settings.moistureLevel = 0.35F;
+    settings.crackIntensity = 0.25F;
+    settings.rockExposure = 0.75F;
+    settings.grassSaturation = 0.85F;
+    settings.soilRoughness = 0.85F;
+    settings.snowColor = QVector3D(0.92F, 0.94F, 0.98F);
     break;
+
   case GroundType::AlpineMix:
     // Alpine Rock + Snow Mix
-    settings.grassPrimary = QVector3D(0.35F, 0.42F, 0.32F);
-    settings.grassSecondary = QVector3D(0.40F, 0.48F, 0.38F);
-    settings.grassDry = QVector3D(0.55F, 0.52F, 0.45F);
-    settings.soilColor = QVector3D(0.38F, 0.35F, 0.32F);
-    settings.rockLow = QVector3D(0.60F, 0.62F, 0.65F);
-    settings.rockHigh = QVector3D(0.85F, 0.88F, 0.92F);
-    settings.terrainAmbientBoost = 1.20F;
-    settings.terrainRockDetailStrength = 0.45F;
+    // High altitude with snow patches, lichen-covered rocks, hardy grass
+    settings.grassPrimary = QVector3D(0.32F, 0.40F, 0.30F);
+    settings.grassSecondary = QVector3D(0.38F, 0.46F, 0.36F);
+    settings.grassDry = QVector3D(0.50F, 0.48F, 0.42F);
+    settings.soilColor = QVector3D(0.42F, 0.40F, 0.38F);
+    settings.rockLow = QVector3D(0.58F, 0.60F, 0.64F);
+    settings.rockHigh = QVector3D(0.88F, 0.90F, 0.94F);
+    settings.terrainAmbientBoost = 1.25F;
+    settings.terrainRockDetailStrength = 0.52F;
+    // Grass/vegetation properties - short hardy alpine grass
+    settings.patchDensity = 1.8F;
+    settings.patchJitter = 0.50F;
+    settings.backgroundBladeDensity = 0.22F;
+    settings.bladeHeightMin = 0.20F;
+    settings.bladeHeightMax = 0.50F;
+    settings.bladeWidthMin = 0.015F;
+    settings.bladeWidthMax = 0.032F;
+    settings.sway_strength = 0.22F;
+    settings.sway_speed = 2.0F;
+    // Terrain noise - moderate with snow accumulation effect
+    settings.terrainMacroNoiseScale = 0.042F;
+    settings.terrainDetailNoiseScale = 0.18F;
+    settings.terrainSoilHeight = 0.55F;
+    settings.terrainSoilSharpness = 3.0F;
+    settings.terrainRockThreshold = 0.32F;
+    settings.terrainRockSharpness = 2.5F;
+    // Ground irregularity - moderate for alpine terrain
+    settings.groundIrregularityEnabled = true;
+    settings.irregularityScale = 0.18F;
+    settings.irregularityAmplitude = 0.12F;
+    settings.plant_density = 0.12F;
+    // Ground-type-specific shader parameters - alpine_mix
+    settings.snowCoverage = 0.55F;
+    settings.moistureLevel = 0.45F;
+    settings.crackIntensity = 0.10F;
+    settings.rockExposure = 0.60F;
+    settings.grassSaturation = 0.80F;
+    settings.soilRoughness = 0.62F;
+    settings.snowColor = QVector3D(0.94F, 0.96F, 1.0F);
     break;
+
   case GroundType::SoilFertile:
     // Dark Fertile Farmland Soil
-    settings.grassPrimary = QVector3D(0.28F, 0.52F, 0.25F);
-    settings.grassSecondary = QVector3D(0.38F, 0.62F, 0.32F);
-    settings.grassDry = QVector3D(0.55F, 0.50F, 0.35F);
-    settings.soilColor = QVector3D(0.22F, 0.18F, 0.14F);
-    settings.rockLow = QVector3D(0.42F, 0.40F, 0.38F);
-    settings.rockHigh = QVector3D(0.55F, 0.54F, 0.52F);
+    // Rich dark earth with lush green grass, ideal for agriculture
+    settings.grassPrimary = QVector3D(0.25F, 0.55F, 0.22F);
+    settings.grassSecondary = QVector3D(0.35F, 0.65F, 0.30F);
+    settings.grassDry = QVector3D(0.52F, 0.48F, 0.32F);
+    settings.soilColor = QVector3D(0.20F, 0.16F, 0.12F);
+    settings.rockLow = QVector3D(0.38F, 0.36F, 0.34F);
+    settings.rockHigh = QVector3D(0.52F, 0.50F, 0.48F);
     settings.terrainAmbientBoost = 1.02F;
-    settings.terrainRockDetailStrength = 0.25F;
+    settings.terrainRockDetailStrength = 0.22F;
+    // Grass/vegetation properties - thick healthy grass
+    settings.patchDensity = 5.2F;
+    settings.patchJitter = 0.90F;
+    settings.backgroundBladeDensity = 0.80F;
+    settings.bladeHeightMin = 0.55F;
+    settings.bladeHeightMax = 1.25F;
+    settings.bladeWidthMin = 0.030F;
+    settings.bladeWidthMax = 0.062F;
+    settings.sway_strength = 0.32F;
+    settings.sway_speed = 1.2F;
+    // Terrain noise - smooth fertile soil
+    settings.terrainMacroNoiseScale = 0.025F;
+    settings.terrainDetailNoiseScale = 0.10F;
+    settings.terrainSoilHeight = 0.75F;
+    settings.terrainSoilSharpness = 2.8F;
+    settings.terrainRockThreshold = 0.58F;
+    settings.terrainRockSharpness = 2.2F;
+    // Ground irregularity - low for flat farmland
+    settings.groundIrregularityEnabled = true;
+    settings.irregularityScale = 0.08F;
+    settings.irregularityAmplitude = 0.05F;
+    settings.plant_density = 0.45F;
+    // Ground-type-specific shader parameters - soil_fertile
+    settings.snowCoverage = 0.0F;
+    settings.moistureLevel = 0.80F;
+    settings.crackIntensity = 0.0F;
+    settings.rockExposure = 0.12F;
+    settings.grassSaturation = 1.15F;
+    settings.soilRoughness = 0.42F;
+    settings.snowColor = QVector3D(0.92F, 0.94F, 0.98F);
     break;
   }
 }

+ 86 - 0
render/gl/backend.cpp

@@ -829,6 +829,49 @@ void Backend::execute(const DrawQueue &queue, const Camera &cam) {
           active_shader->setUniform(
               m_terrainPipeline->m_groundUniforms.light_dir, light_dir);
         }
+        // Ground-type-specific uniforms
+        if (m_terrainPipeline->m_groundUniforms.snowCoverage !=
+            Shader::InvalidUniform) {
+          active_shader->setUniform(
+              m_terrainPipeline->m_groundUniforms.snowCoverage,
+              terrain.params.snowCoverage);
+        }
+        if (m_terrainPipeline->m_groundUniforms.moistureLevel !=
+            Shader::InvalidUniform) {
+          active_shader->setUniform(
+              m_terrainPipeline->m_groundUniforms.moistureLevel,
+              terrain.params.moistureLevel);
+        }
+        if (m_terrainPipeline->m_groundUniforms.crackIntensity !=
+            Shader::InvalidUniform) {
+          active_shader->setUniform(
+              m_terrainPipeline->m_groundUniforms.crackIntensity,
+              terrain.params.crackIntensity);
+        }
+        if (m_terrainPipeline->m_groundUniforms.rockExposure !=
+            Shader::InvalidUniform) {
+          active_shader->setUniform(
+              m_terrainPipeline->m_groundUniforms.rockExposure,
+              terrain.params.rockExposure);
+        }
+        if (m_terrainPipeline->m_groundUniforms.grassSaturation !=
+            Shader::InvalidUniform) {
+          active_shader->setUniform(
+              m_terrainPipeline->m_groundUniforms.grassSaturation,
+              terrain.params.grassSaturation);
+        }
+        if (m_terrainPipeline->m_groundUniforms.soilRoughness !=
+            Shader::InvalidUniform) {
+          active_shader->setUniform(
+              m_terrainPipeline->m_groundUniforms.soilRoughness,
+              terrain.params.soilRoughness);
+        }
+        if (m_terrainPipeline->m_groundUniforms.snowColor !=
+            Shader::InvalidUniform) {
+          active_shader->setUniform(
+              m_terrainPipeline->m_groundUniforms.snowColor,
+              terrain.params.snowColor);
+        }
       } else {
 
         if (m_terrainPipeline->m_terrainUniforms.mvp !=
@@ -963,6 +1006,49 @@ void Backend::execute(const DrawQueue &queue, const Camera &cam) {
           active_shader->setUniform(
               m_terrainPipeline->m_terrainUniforms.light_dir, light_dir);
         }
+        // Ground-type-specific uniforms
+        if (m_terrainPipeline->m_terrainUniforms.snowCoverage !=
+            Shader::InvalidUniform) {
+          active_shader->setUniform(
+              m_terrainPipeline->m_terrainUniforms.snowCoverage,
+              terrain.params.snowCoverage);
+        }
+        if (m_terrainPipeline->m_terrainUniforms.moistureLevel !=
+            Shader::InvalidUniform) {
+          active_shader->setUniform(
+              m_terrainPipeline->m_terrainUniforms.moistureLevel,
+              terrain.params.moistureLevel);
+        }
+        if (m_terrainPipeline->m_terrainUniforms.crackIntensity !=
+            Shader::InvalidUniform) {
+          active_shader->setUniform(
+              m_terrainPipeline->m_terrainUniforms.crackIntensity,
+              terrain.params.crackIntensity);
+        }
+        if (m_terrainPipeline->m_terrainUniforms.rockExposure !=
+            Shader::InvalidUniform) {
+          active_shader->setUniform(
+              m_terrainPipeline->m_terrainUniforms.rockExposure,
+              terrain.params.rockExposure);
+        }
+        if (m_terrainPipeline->m_terrainUniforms.grassSaturation !=
+            Shader::InvalidUniform) {
+          active_shader->setUniform(
+              m_terrainPipeline->m_terrainUniforms.grassSaturation,
+              terrain.params.grassSaturation);
+        }
+        if (m_terrainPipeline->m_terrainUniforms.soilRoughness !=
+            Shader::InvalidUniform) {
+          active_shader->setUniform(
+              m_terrainPipeline->m_terrainUniforms.soilRoughness,
+              terrain.params.soilRoughness);
+        }
+        if (m_terrainPipeline->m_terrainUniforms.snowColor !=
+            Shader::InvalidUniform) {
+          active_shader->setUniform(
+              m_terrainPipeline->m_terrainUniforms.snowColor,
+              terrain.params.snowColor);
+        }
       }
 
       DepthMaskScope const depth_mask(terrain.depthWrite);

+ 28 - 0
render/gl/backend/terrain_pipeline.cpp

@@ -107,6 +107,20 @@ void TerrainPipeline::cacheGroundUniforms() {
   m_groundUniforms.ambientBoost =
       m_groundShader->uniformHandle("u_ambientBoost");
   m_groundUniforms.light_dir = m_groundShader->uniformHandle("u_lightDir");
+  // Ground-type-specific uniforms
+  m_groundUniforms.snowCoverage =
+      m_groundShader->uniformHandle("u_snowCoverage");
+  m_groundUniforms.moistureLevel =
+      m_groundShader->uniformHandle("u_moistureLevel");
+  m_groundUniforms.crackIntensity =
+      m_groundShader->uniformHandle("u_crackIntensity");
+  m_groundUniforms.rockExposure =
+      m_groundShader->uniformHandle("u_rockExposure");
+  m_groundUniforms.grassSaturation =
+      m_groundShader->uniformHandle("u_grassSaturation");
+  m_groundUniforms.soilRoughness =
+      m_groundShader->uniformHandle("u_soilRoughness");
+  m_groundUniforms.snowColor = m_groundShader->uniformHandle("u_snowColor");
 }
 
 void TerrainPipeline::cacheTerrainUniforms() {
@@ -149,6 +163,20 @@ void TerrainPipeline::cacheTerrainUniforms() {
   m_terrainUniforms.rockDetailStrength =
       m_terrainShader->uniformHandle("u_rockDetailStrength");
   m_terrainUniforms.light_dir = m_terrainShader->uniformHandle("u_lightDir");
+  // Ground-type-specific uniforms
+  m_terrainUniforms.snowCoverage =
+      m_terrainShader->uniformHandle("u_snowCoverage");
+  m_terrainUniforms.moistureLevel =
+      m_terrainShader->uniformHandle("u_moistureLevel");
+  m_terrainUniforms.crackIntensity =
+      m_terrainShader->uniformHandle("u_crackIntensity");
+  m_terrainUniforms.rockExposure =
+      m_terrainShader->uniformHandle("u_rockExposure");
+  m_terrainUniforms.grassSaturation =
+      m_terrainShader->uniformHandle("u_grassSaturation");
+  m_terrainUniforms.soilRoughness =
+      m_terrainShader->uniformHandle("u_soilRoughness");
+  m_terrainUniforms.snowColor = m_terrainShader->uniformHandle("u_snowColor");
 }
 
 void TerrainPipeline::initializeGrassGeometry() {

+ 16 - 0
render/gl/backend/terrain_pipeline.h

@@ -50,6 +50,14 @@ public:
     GL::Shader::UniformHandle heightNoiseFrequency{GL::Shader::InvalidUniform};
     GL::Shader::UniformHandle ambientBoost{GL::Shader::InvalidUniform};
     GL::Shader::UniformHandle light_dir{GL::Shader::InvalidUniform};
+    // Ground-type-specific uniforms
+    GL::Shader::UniformHandle snowCoverage{GL::Shader::InvalidUniform};
+    GL::Shader::UniformHandle moistureLevel{GL::Shader::InvalidUniform};
+    GL::Shader::UniformHandle crackIntensity{GL::Shader::InvalidUniform};
+    GL::Shader::UniformHandle rockExposure{GL::Shader::InvalidUniform};
+    GL::Shader::UniformHandle grassSaturation{GL::Shader::InvalidUniform};
+    GL::Shader::UniformHandle soilRoughness{GL::Shader::InvalidUniform};
+    GL::Shader::UniformHandle snowColor{GL::Shader::InvalidUniform};
   };
 
   struct TerrainUniforms {
@@ -75,6 +83,14 @@ public:
     GL::Shader::UniformHandle ambientBoost{GL::Shader::InvalidUniform};
     GL::Shader::UniformHandle rockDetailStrength{GL::Shader::InvalidUniform};
     GL::Shader::UniformHandle light_dir{GL::Shader::InvalidUniform};
+    // Ground-type-specific uniforms
+    GL::Shader::UniformHandle snowCoverage{GL::Shader::InvalidUniform};
+    GL::Shader::UniformHandle moistureLevel{GL::Shader::InvalidUniform};
+    GL::Shader::UniformHandle crackIntensity{GL::Shader::InvalidUniform};
+    GL::Shader::UniformHandle rockExposure{GL::Shader::InvalidUniform};
+    GL::Shader::UniformHandle grassSaturation{GL::Shader::InvalidUniform};
+    GL::Shader::UniformHandle soilRoughness{GL::Shader::InvalidUniform};
+    GL::Shader::UniformHandle snowColor{GL::Shader::InvalidUniform};
   };
 
   GL::Shader *m_grassShader = nullptr;

+ 25 - 5
render/ground/ground_renderer.cpp

@@ -85,11 +85,14 @@ auto GroundRenderer::buildParams() const -> TerrainChunkParams {
   params.detail_noiseScale =
       std::max(0.045F, m_biomeSettings.terrainDetailNoiseScale * 0.75F);
 
-  params.slopeRockThreshold = 0.72F;
-  params.slopeRockSharpness = 4.5F;
+  params.slopeRockThreshold =
+      std::clamp(m_biomeSettings.terrainRockThreshold + 0.30F, 0.40F, 0.90F);
+  params.slopeRockSharpness =
+      std::clamp(m_biomeSettings.terrainRockSharpness + 1.5F, 2.0F, 6.0F);
 
-  params.soilBlendHeight = -0.65F;
-  params.soilBlendSharpness = 2.6F;
+  params.soilBlendHeight = m_biomeSettings.terrainSoilHeight - 1.25F;
+  params.soilBlendSharpness =
+      std::clamp(m_biomeSettings.terrainSoilSharpness * 0.75F, 1.5F, 5.0F);
 
   params.noiseOffset = m_noiseOffset;
   params.noiseAngle = m_noiseAngle;
@@ -125,6 +128,17 @@ auto GroundRenderer::buildParams() const -> TerrainChunkParams {
 
   params.isGroundPlane = true;
 
+  // Ground-type-specific shader parameters
+  params.snowCoverage = std::clamp(m_biomeSettings.snowCoverage, 0.0F, 1.0F);
+  params.moistureLevel = std::clamp(m_biomeSettings.moistureLevel, 0.0F, 1.0F);
+  params.crackIntensity =
+      std::clamp(m_biomeSettings.crackIntensity, 0.0F, 1.0F);
+  params.rockExposure = std::clamp(m_biomeSettings.rockExposure, 0.0F, 1.0F);
+  params.grassSaturation =
+      std::clamp(m_biomeSettings.grassSaturation, 0.0F, 1.5F);
+  params.soilRoughness = std::clamp(m_biomeSettings.soilRoughness, 0.0F, 1.0F);
+  params.snowColor = saturate(m_biomeSettings.snowColor);
+
   m_cachedParams = params;
   m_cachedParamsValid = true;
   return params;
@@ -197,7 +211,13 @@ auto GroundRenderer::biomeEquals(const Game::Map::BiomeSettings &a,
          a.heightNoiseFrequency == b.heightNoiseFrequency &&
          a.groundIrregularityEnabled == b.groundIrregularityEnabled &&
          a.irregularityScale == b.irregularityScale &&
-         a.irregularityAmplitude == b.irregularityAmplitude && a.seed == b.seed;
+         a.irregularityAmplitude == b.irregularityAmplitude &&
+         a.seed == b.seed && a.snowCoverage == b.snowCoverage &&
+         a.moistureLevel == b.moistureLevel &&
+         a.crackIntensity == b.crackIntensity &&
+         a.rockExposure == b.rockExposure &&
+         a.grassSaturation == b.grassSaturation &&
+         a.soilRoughness == b.soilRoughness && a.snowColor == b.snowColor;
 }
 
 } // namespace Render::GL

+ 22 - 0
render/ground/terrain_gpu.h

@@ -43,6 +43,16 @@ struct TerrainChunkParams {
   static constexpr float kDefaultMicroBumpFreq = 2.2F;
   static constexpr float kDefaultMicroNormalWeight = 0.65F;
   static constexpr float kDefaultAlbedoJitter = 0.05F;
+  // Ground-type-specific defaults
+  static constexpr float kDefaultSnowCoverage = 0.0F;
+  static constexpr float kDefaultMoistureLevel = 0.5F;
+  static constexpr float kDefaultCrackIntensity = 0.0F;
+  static constexpr float kDefaultRockExposure = 0.3F;
+  static constexpr float kDefaultGrassSaturation = 1.0F;
+  static constexpr float kDefaultSoilRoughness = 0.5F;
+  static constexpr float kDefaultSnowColorR = 0.92F;
+  static constexpr float kDefaultSnowColorG = 0.94F;
+  static constexpr float kDefaultSnowColorB = 0.98F;
 
   static auto defaultGrassPrimary() -> QVector3D {
     return {kDefaultGrassPrimaryR, kDefaultGrassPrimaryG,
@@ -71,6 +81,9 @@ struct TerrainChunkParams {
   static auto defaultLightDirection() -> QVector3D {
     return {kDefaultLightDirX, kDefaultLightDirY, kDefaultLightDirZ};
   }
+  static auto defaultSnowColor() -> QVector3D {
+    return {kDefaultSnowColorR, kDefaultSnowColorG, kDefaultSnowColorB};
+  }
 
   QVector3D grassPrimary = defaultGrassPrimary();
   float tile_size = kDefaultTileSize;
@@ -103,6 +116,15 @@ struct TerrainChunkParams {
   float albedoJitter = kDefaultAlbedoJitter;
 
   bool isGroundPlane = false;
+
+  // Ground-type-specific shader parameters
+  float snowCoverage = kDefaultSnowCoverage;
+  float moistureLevel = kDefaultMoistureLevel;
+  float crackIntensity = kDefaultCrackIntensity;
+  float rockExposure = kDefaultRockExposure;
+  float grassSaturation = kDefaultGrassSaturation;
+  float soilRoughness = kDefaultSoilRoughness;
+  QVector3D snowColor = defaultSnowColor();
 };
 
 } // namespace Render::GL

+ 32 - 16
tests/core/ground_type_test.cpp

@@ -84,10 +84,14 @@ TEST_F(GroundTypeTest, ApplyGroundTypeDefaultsGrassDry) {
   applyGroundTypeDefaults(settings, GroundType::GrassDry);
 
   EXPECT_EQ(settings.groundType, GroundType::GrassDry);
-  EXPECT_FLOAT_EQ(settings.grassPrimary.x(), 0.55F);
-  EXPECT_FLOAT_EQ(settings.grassPrimary.y(), 0.52F);
-  EXPECT_FLOAT_EQ(settings.grassPrimary.z(), 0.30F);
-  EXPECT_FLOAT_EQ(settings.terrainAmbientBoost, 1.15F);
+  EXPECT_FLOAT_EQ(settings.grassPrimary.x(), 0.58F);
+  EXPECT_FLOAT_EQ(settings.grassPrimary.y(), 0.54F);
+  EXPECT_FLOAT_EQ(settings.grassPrimary.z(), 0.32F);
+  EXPECT_FLOAT_EQ(settings.terrainAmbientBoost, 1.18F);
+  // Check ground-type-specific parameters
+  EXPECT_FLOAT_EQ(settings.crackIntensity, 0.65F);
+  EXPECT_FLOAT_EQ(settings.moistureLevel, 0.15F);
+  EXPECT_FLOAT_EQ(settings.grassSaturation, 0.75F);
 }
 
 TEST_F(GroundTypeTest, ApplyGroundTypeDefaultsSoilRocky) {
@@ -95,10 +99,13 @@ TEST_F(GroundTypeTest, ApplyGroundTypeDefaultsSoilRocky) {
   applyGroundTypeDefaults(settings, GroundType::SoilRocky);
 
   EXPECT_EQ(settings.groundType, GroundType::SoilRocky);
-  EXPECT_FLOAT_EQ(settings.soilColor.x(), 0.50F);
-  EXPECT_FLOAT_EQ(settings.soilColor.y(), 0.42F);
-  EXPECT_FLOAT_EQ(settings.soilColor.z(), 0.32F);
-  EXPECT_FLOAT_EQ(settings.terrainRockDetailStrength, 0.55F);
+  EXPECT_FLOAT_EQ(settings.soilColor.x(), 0.55F);
+  EXPECT_FLOAT_EQ(settings.soilColor.y(), 0.48F);
+  EXPECT_FLOAT_EQ(settings.soilColor.z(), 0.38F);
+  EXPECT_FLOAT_EQ(settings.terrainRockDetailStrength, 0.65F);
+  // Check ground-type-specific parameters
+  EXPECT_FLOAT_EQ(settings.rockExposure, 0.75F);
+  EXPECT_FLOAT_EQ(settings.soilRoughness, 0.85F);
 }
 
 TEST_F(GroundTypeTest, ApplyGroundTypeDefaultsAlpineMix) {
@@ -106,10 +113,15 @@ TEST_F(GroundTypeTest, ApplyGroundTypeDefaultsAlpineMix) {
   applyGroundTypeDefaults(settings, GroundType::AlpineMix);
 
   EXPECT_EQ(settings.groundType, GroundType::AlpineMix);
-  EXPECT_FLOAT_EQ(settings.rockHigh.x(), 0.85F);
-  EXPECT_FLOAT_EQ(settings.rockHigh.y(), 0.88F);
-  EXPECT_FLOAT_EQ(settings.rockHigh.z(), 0.92F);
-  EXPECT_FLOAT_EQ(settings.terrainAmbientBoost, 1.20F);
+  EXPECT_FLOAT_EQ(settings.rockHigh.x(), 0.88F);
+  EXPECT_FLOAT_EQ(settings.rockHigh.y(), 0.90F);
+  EXPECT_FLOAT_EQ(settings.rockHigh.z(), 0.94F);
+  EXPECT_FLOAT_EQ(settings.terrainAmbientBoost, 1.25F);
+  // Check ground-type-specific parameters
+  EXPECT_FLOAT_EQ(settings.snowCoverage, 0.55F);
+  EXPECT_FLOAT_EQ(settings.snowColor.x(), 0.94F);
+  EXPECT_FLOAT_EQ(settings.snowColor.y(), 0.96F);
+  EXPECT_FLOAT_EQ(settings.snowColor.z(), 1.0F);
 }
 
 TEST_F(GroundTypeTest, ApplyGroundTypeDefaultsSoilFertile) {
@@ -117,10 +129,14 @@ TEST_F(GroundTypeTest, ApplyGroundTypeDefaultsSoilFertile) {
   applyGroundTypeDefaults(settings, GroundType::SoilFertile);
 
   EXPECT_EQ(settings.groundType, GroundType::SoilFertile);
-  EXPECT_FLOAT_EQ(settings.soilColor.x(), 0.22F);
-  EXPECT_FLOAT_EQ(settings.soilColor.y(), 0.18F);
-  EXPECT_FLOAT_EQ(settings.soilColor.z(), 0.14F);
-  EXPECT_FLOAT_EQ(settings.terrainRockDetailStrength, 0.25F);
+  EXPECT_FLOAT_EQ(settings.soilColor.x(), 0.20F);
+  EXPECT_FLOAT_EQ(settings.soilColor.y(), 0.16F);
+  EXPECT_FLOAT_EQ(settings.soilColor.z(), 0.12F);
+  EXPECT_FLOAT_EQ(settings.terrainRockDetailStrength, 0.22F);
+  // Check ground-type-specific parameters
+  EXPECT_FLOAT_EQ(settings.moistureLevel, 0.80F);
+  EXPECT_FLOAT_EQ(settings.grassSaturation, 1.15F);
+  EXPECT_FLOAT_EQ(settings.rockExposure, 0.12F);
 }
 
 TEST_F(GroundTypeTest, MapLoaderWithGroundType) {