From c608369dcf48b52b8adee27a066e55f8ab3b2149 Mon Sep 17 00:00:00 2001 From: ImBenji Date: Sat, 18 Oct 2025 19:49:59 +0100 Subject: [PATCH] Add terrain shader for height, heat, and moisture rendering --- Shaders/terrain.gdshader | 221 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 Shaders/terrain.gdshader diff --git a/Shaders/terrain.gdshader b/Shaders/terrain.gdshader new file mode 100644 index 0000000..a881cc1 --- /dev/null +++ b/Shaders/terrain.gdshader @@ -0,0 +1,221 @@ +shader_type canvas_item; + +// Textures +uniform sampler2D height_map : filter_nearest; + +// Thresholds +uniform float water_threshold = 0.0; +uniform float rock_threshold = 0.05; +uniform float snow_height_threshold = 0.8; +uniform float desert_temperature_threshold = 0.7; +uniform float desert_moisture_threshold = 0.3; + +// Noise parameters +uniform float temperature_noise_frequency = 0.01; +uniform float temperature_noise_amplitude = 0.1; +uniform float moisture_noise_frequency = 0.01; +uniform float moisture_noise_amplitude = 0.1; + +// Debug mode +uniform int debug_type = 0; // 0=None, 1=Slope, 2=Height, 3=Temperature, 4=Moisture + +// Colors +const vec3 COLOR_WATER = vec3(0.263, 0.502, 0.690); // #4380b0 +const vec3 COLOR_GRASS = vec3(0.745, 0.835, 0.541); // #bed58a +const vec3 COLOR_DESERT = vec3(0.929, 0.788, 0.686); // #edc9af +const vec3 COLOR_BEACH = vec3(0.502, 0.502, 0.502); // #808080 +const vec3 COLOR_SNOW = vec3(0.957, 0.957, 0.957); // #f4f4f4 +const vec3 COLOR_ROCK = vec3(0.894, 0.827, 0.651); // #e4d3a6 + +// Better hash function for noise +vec2 hash22(vec2 p, float seed) { + vec3 p3 = fract(vec3(p.xyx) * vec3(443.897, 441.423, 437.195)); + p3 += dot(p3, p3.yzx + 19.19 + seed); + return fract((p3.xx + p3.yz) * p3.zy); +} + +float hash12(vec2 p, float seed) { + vec3 p3 = fract(vec3(p.xyx) * 0.1031); + p3 += dot(p3, p3.yzx + 33.33 + seed); + return fract((p3.x + p3.y) * p3.z); +} + +// Perlin noise +float perlin_noise(vec2 p, float seed) { + vec2 i = floor(p); + vec2 f = fract(p); + + // Quintic interpolation + vec2 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0); + + // Get gradients + float a = hash12(i + vec2(0.0, 0.0), seed); + float b = hash12(i + vec2(1.0, 0.0), seed); + float c = hash12(i + vec2(0.0, 1.0), seed); + float d = hash12(i + vec2(1.0, 1.0), seed); + + // Mix + return mix(mix(a, b, u.x), mix(c, d, u.x), u.y) * 2.0 - 1.0; +} + +// FBM with 12 octaves +float fbm(vec2 p, float freq, float seed) { + float value = 0.0; + float amplitude = 0.5; + float frequency = freq; + + for(int i = 0; i < 12; i++) { + value += amplitude * perlin_noise(p * frequency, seed); + frequency *= 2.0; + amplitude *= 0.5; + } + + return value; +} + +// Get height at UV coordinates +float get_height(vec2 uv) { + return texture(height_map, uv).r; +} + +// Get heat value with latitude falloff +float get_heat(vec2 uv) { + vec2 heightmap_size = vec2(textureSize(height_map, 0)); + vec2 heightmap_coords = uv * heightmap_size; + + // Original C# uses 1/frequency for FastNoiseLite, so we invert + float actualFreq = 1.0 / temperature_noise_frequency; + float heat = fbm(heightmap_coords, actualFreq, 1738.0); + heat *= temperature_noise_amplitude; + + // Latitude falloff (sine wave from poles to equator) + float latitude_factor = sin(3.14159265 * uv.y); + heat = latitude_factor + heat; + + return heat; +} + +// Get moisture value with latitude falloff +float get_moisture(vec2 uv) { + vec2 heightmap_size = vec2(textureSize(height_map, 0)); + vec2 heightmap_coords = uv * heightmap_size; + + // Original C# uses 1/frequency for FastNoiseLite, so we invert + float actualFreq = 1.0 / moisture_noise_frequency; + float moisture = fbm(heightmap_coords, actualFreq, 1337.0); + moisture *= moisture_noise_amplitude; + + // Latitude falloff (sine wave from poles to equator) + float latitude_factor = sin(3.14159265 * uv.y); + moisture = latitude_factor + moisture; + + return moisture; +} + +// Check if position is sea +bool is_sea(vec2 uv) { + return get_height(uv) <= water_threshold; +} + +// Check if position is desert +bool is_desert(vec2 uv, float heat, float moisture) { + return moisture < desert_moisture_threshold && heat > desert_temperature_threshold; +} + +// Calculate max height difference with neighbors (for slope) +float get_max_slope(vec2 uv) { + vec2 heightmap_size = vec2(textureSize(height_map, 0)); + vec2 texel_size = 1.0 / heightmap_size; + + float center_height = get_height(uv); + float max_diff = 0.0; + + // Check 8 neighbors + for(int x = -1; x <= 1; x++) { + for(int y = -1; y <= 1; y++) { + if(x == 0 && y == 0) continue; + + vec2 neighbor_uv = uv + vec2(float(x), float(y)) * texel_size; + float neighbor_height = get_height(neighbor_uv); + float diff = abs(center_height - neighbor_height); + max_diff = max(max_diff, diff); + } + } + + return max_diff; +} + +// Check if any neighbor is sea (for beach detection) +bool borders_sea(vec2 uv) { + vec2 heightmap_size = vec2(textureSize(height_map, 0)); + vec2 texel_size = 1.0 / heightmap_size; + + for(int x = -1; x <= 1; x++) { + for(int y = -1; y <= 1; y++) { + if(x == 0 && y == 0) continue; + + vec2 neighbor_uv = uv + vec2(float(x), float(y)) * texel_size; + if(is_sea(neighbor_uv)) { + return true; + } + } + } + + return false; +} + +void fragment() { + vec2 uv = UV; + + float height_value = get_height(uv); + float heat_value = get_heat(uv); + float moisture_value = get_moisture(uv); + float slope = get_max_slope(uv); + + vec3 color = COLOR_GRASS; // Default + + if(debug_type == 0) { + // Normal rendering + + // Water + if(is_sea(uv)) { + color = COLOR_WATER; + } + // Desert + else if(is_desert(uv, heat_value, moisture_value)) { + color = COLOR_DESERT; + } + // Beach (borders sea) + else if(borders_sea(uv)) { + color = COLOR_BEACH; + } + // Snow (high altitude, not desert) + else if(height_value > snow_height_threshold && !is_desert(uv, heat_value, moisture_value)) { + color = COLOR_SNOW; + } + + // Rock (steep areas) - override everything except water + if(slope > rock_threshold && !is_sea(uv)) { + color = COLOR_ROCK; + } + } + else if(debug_type == 1) { + // Slope debug + float gray = slope * 5.0; + color = vec3(gray); + } + else if(debug_type == 2) { + // Height debug + color = vec3(height_value); + } + else if(debug_type == 3) { + // Temperature debug + color = vec3(heat_value); + } + else if(debug_type == 4) { + // Moisture debug + color = vec3(moisture_value); + } + + COLOR = vec4(color, 1.0); +} \ No newline at end of file