Implementation of Fast and Gorgeous Erosion Filter - runevision
Its the basic implementation, I stopped at the fade for peak and valley.
1.The script is attached to a Node3D.
2.You need to create a shader material and assign the shader to the material
@tool
extends Node3D
@export_group("Resources")
@export var material: ShaderMaterial = preload("res://terrain_erosion_test/terrain_erosion_test_material.res")
@export var noise: FastNoiseLite
@export_group("Base")
@export var size: float = 20000.0
@export var altitude: float = 8000.0
@export var resolution: int = 128
@export_group("Erosion (Single Pass)")
@export var sine_amplitude: float = 40.0
@export var sine_frequency: float = 0.01
@export var cell_size: float = 400.0
@export_group("Slope")
@export var smoothstep_min: float = 0.05
@export var smoothstep_max: float = 0.15
var height_data: PackedFloat32Array
var base_slope_mask: PackedFloat32Array
var grid_size: int
var mask_tex: ImageTexture
func _ready():
for c in get_children(): if c is MeshInstance3D: c.queue_free()
if !noise: return
grid_size = resolution + 1
height_data.resize(grid_size**2)
base_slope_mask.resize(grid_size**2)
# Initial Height Generation
for z in grid_size:
for x in grid_size:
height_data[x + z * grid_size] = noise.get_noise_2d((float(x)/resolution)*size, (float(z)/resolution)*size) * altitude
# Slope Mask Generation
var step = size / resolution
var img = Image.create(grid_size, grid_size, false, Image.FORMAT_L8)
for z in grid_size:
for x in grid_size:
var idx = x + z * grid_size
var g = Vector2(height_data[clamp(x+1,0,resolution)+z*grid_size]-height_data[idx], height_data[x+clamp(z+1,0,resolution)*grid_size]-height_data[idx])
var m = smoothstep(smoothstep_min, smoothstep_max, g.length()/step)
base_slope_mask[idx] = m
img.set_pixel(x, z, Color(m, m, m, 1.0))
mask_tex = ImageTexture.create_from_image(img)
# Apply single erosion pass
apply_erosion_pass()
if material: material.set_shader_parameter("slope_mask_tex", mask_tex)
create_mesh()
func apply_erosion_pass():
var step = size / resolution
var prev = height_data.duplicate()
for z in grid_size:
for x in grid_size:
var idx = x + z * grid_size
var m = base_slope_mask[idx]
if m <= 0.001: continue
# Calculate Gradient Tangent
var g = Vector2(prev[clamp(x+1,0,resolution)+z*grid_size]-prev[idx], prev[x+clamp(z+1,0,resolution)*grid_size]-prev[idx])
var tan = Vector2(-g.y, g.x).normalized()
var w_pos = Vector2(x, z) * step
var grid_c = (w_pos / cell_size).floor()
var o_s = 0.0
var o_w = 0.0
# Neighborhood sampling (No octave scaling)
for ny in range(-1, 2):
for nx in range(-1, 2):
var rng = RandomNumberGenerator.new()
# Seeded by cell position and noise seed
rng.seed = hash(str(grid_c + Vector2(nx, ny)) + str(noise.seed))
var piv = ((grid_c + Vector2(nx, ny)) * cell_size) + Vector2(rng.randf(), rng.randf()) * cell_size
var w = pow(1.0 - smoothstep(0.0, cell_size * 1.5, w_pos.distance_to(piv)), 2.0)
if w > 0.001:
o_s += sin((w_pos - piv).dot(tan) * sine_frequency) * w
o_w += w
if o_w > 0.0:
height_data[idx] += (o_s / o_w) * sine_amplitude * m
func create_mesh():
var st = SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
var step = size / resolution
for z in grid_size:
for x in grid_size:
st.set_uv(Vector2(float(x)/resolution, float(z)/resolution))
st.add_vertex(Vector3(x * step, height_data[x + z * grid_size], z * step))
for z in resolution:
for x in resolution:
var i = x + z * grid_size
for off in [0, 1, grid_size, 1, grid_size + 1, grid_size]: st.add_index(i + off)
st.generate_normals()
st.set_material(material)
var mi = MeshInstance3D.new()
mi.mesh = st.commit()
add_child(mi)
if Engine.is_editor_hint(): mi.owner = get_tree().edited_scene_root
shader_type spatial;
uniform sampler2D stripe_texture;
uniform bool show_stripes = false;
uniform float alt_coeff = 1.0; // use this to compress altitude range
varying vec3 world_pos;
varying vec3 world_norm;
void vertex() {
world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
world_norm = normalize((MODEL_MATRIX * vec4(NORMAL, 0.0)).xyz);
}
void fragment() {
float y = world_pos.y;
float flatten = dot(world_norm, vec3(0, 1, 0));
vec3 color;
float beach_line = 20.0 * alt_coeff;
float tree_line = 1700.0 * alt_coeff;
if (y < -1000.0 * alt_coeff) color = vec3(0.00, 0.11, 0.23);
else if (y <= -100.0 * alt_coeff) color = vec3(0.00, 0.31, 0.48);
else if (y <= -20.0 * alt_coeff) color = vec3(0.29, 0.59, 0.69);
else if (y <= beach_line) color = vec3(0.82, 0.71, 0.55);
else if (y <= tree_line) color = vec3(0.46, 0.63, 0.36);
else if (y <= 2800.0 * alt_coeff) color = vec3(0.55, 0.55, 0.48);
else if (y <= 3500.0 * alt_coeff) color = vec3(0.43, 0.43, 0.43);
else color = vec3(0.94, 0.97, 1.00);
if (y > beach_line) {
if (flatten < 0.85) color = vec3(0.30, 0.30, 0.30);
else if (flatten < 0.90) color = vec3(0.35, 0.25, 0.15);
else if (flatten < 0.97 && y <= tree_line) color = vec3(0.18, 0.31, 0.16);
}
if (show_stripes) {
float value = texture(stripe_texture, UV).r;
ALBEDO = vec3(value);
} else {
ALBEDO = color;
}
}
- You can visualize the stripes (set show_stripes to true).



