From a517b1effcd35a7b0f7c23508bac93eae7fbc1a5 Mon Sep 17 00:00:00 2001 From: Xtarsia <69606701+Xtarsia@users.noreply.github.com> Date: Sun, 8 Mar 2026 10:06:31 +0000 Subject: [PATCH 1/6] update particle grass --- .../particle_example/particles.gdshader | 83 +++++++++++++------ .../particle_example/process_material.tres | 2 + .../particle_example/terrain_3D_particles.gd | 3 + 3 files changed, 62 insertions(+), 26 deletions(-) diff --git a/project/addons/terrain_3d/extras/particle_example/particles.gdshader b/project/addons/terrain_3d/extras/particle_example/particles.gdshader index f5f06dd21..dbc90f75f 100644 --- a/project/addons/terrain_3d/extras/particle_example/particles.gdshader +++ b/project/addons/terrain_3d/extras/particle_example/particles.gdshader @@ -39,6 +39,8 @@ uniform float surface_slope_min : hint_range(0.0, 1.0, 0.01) = 0.87; uniform float distance_fade_ammount : hint_range(0.0, 1.0, 0.01) = 0.5; group_uniforms private; +uniform float ground_level : hint_range(-1000., 1000.) = -20.0; +uniform float region_blend : hint_range(.001, 1., 0.001) = 0.25; uniform float max_dist = 1.; uniform vec3 camera_position = vec3(0.); uniform uint instance_rows = 1; @@ -60,29 +62,57 @@ uniform highp sampler2DArray _color_maps : repeat_disable; #define VERTEX_PASS 1 #define FRAGMENT_PASS 2 -// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none) +// Takes in world space XZ (UV) coordinates // Returns ivec3 with: // XY: (0 to _region_size - 1) coordinates within a region // Z: layer index used for texturearrays, -1 if not in a region -ivec3 get_index_coord(const vec2 uv, const int search) { +ivec3 get_index_coord(const vec2 uv) { vec2 r_uv = round(uv); - vec2 o_uv = mod(r_uv,_region_size); - ivec2 pos; - int bounds, layer_index = -1; - for (int i = -1; i < 0; i++) { - if ((layer_index == -1 && _background_mode == 0u) || i < 0) { - r_uv -= i == -1 ? vec2(0.0) : vec2(float(o_uv.x <= o_uv.y), float(o_uv.y <= o_uv.x)); - pos = ivec2(floor((r_uv) * _region_texel_size)) + (_region_map_size / 2); - bounds = int(uint(pos.x | pos.y) < uint(_region_map_size)); - layer_index = (_region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1); - } - } - return ivec3(ivec2(mod(r_uv,_region_size)), layer_index); + ivec2 pos = ivec2(floor(r_uv * _region_texel_size)) + (_region_map_size / 2); + int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size)); + int layer_index = _region_map[pos.y * _region_map_size + pos.x] * bounds - 1; + return ivec3(ivec2(mod(r_uv, _region_size)), layer_index); } #if CURRENT_RENDERER == RENDERER_COMPATIBILITY #define fma(a, b, c) ((a) * (b) + (c)) #endif + +// Takes in UV2 region space coordinates, returns 1.0 or 0.0 if a region is present or not. +float check_region(const vec2 uv2) { + ivec2 pos = ivec2(floor(uv2)) + (_region_map_size / 2); + int layer_index = 0; + if (uint(pos.x | pos.y) < uint(_region_map_size)) { + layer_index = clamp(_region_map[ pos.y * _region_map_size + pos.x ] - 1, -1, 0) + 1; + } + return float(layer_index); +} + +// Takes in UV2 region space coordinates, returns a blend value (0 - 1 range) between empty, and valid regions +float get_region_blend(vec2 uv2) { + uv2 -= 0.5011; // correct for floating point error + const vec2 offset = vec2(0.0, 1.0); + float a = check_region(uv2 + offset.xy); + float b = check_region(uv2 + offset.yy); + float c = check_region(uv2 + offset.yx); + float d = check_region(uv2 + offset.xx); + vec2 blend_factor = vec2(2.0 + 126.0 * (1.0 - region_blend)); + vec2 f = fract(uv2); + vec2 w = 1.0 / (1.0 + exp(blend_factor * log((1.0 - f) / f))); + float blend = mix(mix(d, c, w.x), mix(a, b, w.x), w.y); + return (1.0 - blend) * 2.0; +} + +float get_height(vec2 index_id, vec2 offset) { + float height = texelFetch(_height_maps, get_index_coord(index_id + offset), 0).r; + if (_background_mode != 0u) { + float blend = get_region_blend(index_id * _region_texel_size + offset * _region_texel_size); + height = mix(height, ground_level, smoothstep(0., 1., blend)); + } + + return height; +} + float random(vec2 v) { return fract(1e4 * sin(fma(17.0, v.x, v.y * 0.1)) * (0.1 + abs(sin(fma(v.y, 13.0, v.x))))); } @@ -142,20 +172,21 @@ void start() { ivec3 index[4]; // Map lookups - index[0] = get_index_coord(index_id + offsets.xy, VERTEX_PASS); - index[1] = get_index_coord(index_id + offsets.yy, VERTEX_PASS); - index[2] = get_index_coord(index_id + offsets.yx, VERTEX_PASS); - index[3] = get_index_coord(index_id + offsets.xx, VERTEX_PASS); + index[0] = get_index_coord(index_id + offsets.xy); + index[1] = get_index_coord(index_id + offsets.yy); + index[2] = get_index_coord(index_id + offsets.yx); + index[3] = get_index_coord(index_id + offsets.xx); highp float h[8]; - h[0] = texelFetch(_height_maps, index[0], 0).r; // 0 (0,1) - h[1] = texelFetch(_height_maps, index[1], 0).r; // 1 (1,1) - h[2] = texelFetch(_height_maps, index[2], 0).r; // 2 (1,0) - h[3] = texelFetch(_height_maps, index[3], 0).r; // 3 (0,0) - h[4] = texelFetch(_height_maps, get_index_coord(index_id + offsets.yz, VERTEX_PASS), 0).r; // 4 (1,2) - h[5] = texelFetch(_height_maps, get_index_coord(index_id + offsets.zy, VERTEX_PASS), 0).r; // 5 (2,1) - h[6] = texelFetch(_height_maps, get_index_coord(index_id + offsets.zx, VERTEX_PASS), 0).r; // 6 (2,0) - h[7] = texelFetch(_height_maps, get_index_coord(index_id + offsets.xz, VERTEX_PASS), 0).r; // 7 (0,2) + h[0] = get_height(index_id, offsets.xy); // 0 (0,1) + h[1] = get_height(index_id, offsets.yy); // 1 (1,1) + h[2] = get_height(index_id, offsets.yx); // 2 (1,0) + h[3] = get_height(index_id, offsets.xx); // 3 (0,0) + h[4] = get_height(index_id, offsets.yz); // 4 (1,2) + h[5] = get_height(index_id, offsets.zy); // 5 (2,1) + h[6] = get_height(index_id, offsets.zx); // 6 (2,0) + h[7] = get_height(index_id, offsets.xz); // 7 (0,2) + vec3 index_normal[4]; index_normal[0] = vec3(h[0] - h[1], _vertex_spacing, h[0] - h[7]); index_normal[1] = vec3(h[1] - h[5], _vertex_spacing, h[1] - h[4]); diff --git a/project/addons/terrain_3d/extras/particle_example/process_material.tres b/project/addons/terrain_3d/extras/particle_example/process_material.tres index 27504e00e..0fa0796ec 100644 --- a/project/addons/terrain_3d/extras/particle_example/process_material.tres +++ b/project/addons/terrain_3d/extras/particle_example/process_material.tres @@ -35,6 +35,8 @@ shader_parameter/patch_max_threshold = 0.2 shader_parameter/condition_dither_range = 0.15 shader_parameter/surface_slope_min = 0.87 shader_parameter/distance_fade_ammount = 0.66 +shader_parameter/ground_level = -618.9519819002201 +shader_parameter/region_blend = 0.95600004541 shader_parameter/max_dist = 1.0 shader_parameter/camera_position = Vector3(0, 0, 0) shader_parameter/instance_rows = 1 diff --git a/project/addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd b/project/addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd index 46f7572e2..e08cd9514 100644 --- a/project/addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd +++ b/project/addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd @@ -222,6 +222,9 @@ func _update_process_parameters() -> void: var process_rid: RID = process_material.get_rid() if terrain and process_rid.is_valid(): RenderingServer.material_set_param(process_rid, "_background_mode", terrain.material.world_background) + if terrain.material.world_background > 0: + RenderingServer.material_set_param(process_rid, "ground_level", terrain.material.ground_level) + RenderingServer.material_set_param(process_rid, "region_blend", terrain.material.region_blend) RenderingServer.material_set_param(process_rid, "_vertex_spacing", terrain.vertex_spacing) RenderingServer.material_set_param(process_rid, "_vertex_density", 1.0 / terrain.vertex_spacing) RenderingServer.material_set_param(process_rid, "_region_size", terrain.region_size) From b9831d6e0215ad7d05e301f003208a46bf36c5dd Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Sun, 8 Mar 2026 18:52:14 +0700 Subject: [PATCH 2/6] Add optional texture_filter --- .../extras/particle_example/grass.gdshader | 2 +- .../extras/particle_example/particles.gdshader | 13 ++++++++----- .../extras/particle_example/process_material.tres | 1 + 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/project/addons/terrain_3d/extras/particle_example/grass.gdshader b/project/addons/terrain_3d/extras/particle_example/grass.gdshader index a149fd6dd..558611dfb 100644 --- a/project/addons/terrain_3d/extras/particle_example/grass.gdshader +++ b/project/addons/terrain_3d/extras/particle_example/grass.gdshader @@ -53,7 +53,7 @@ void vertex() { } void fragment() { - // Hard coded color. + // Hard coded color ALBEDO = vec3(0.20, 0.22, 0.05) * (data[2] * 0.5 + 0.5); ALBEDO.rg *= (data.rg * 0.3 + 0.9); ALBEDO *= pow(COLOR.rgb, vec3(2.2)); diff --git a/project/addons/terrain_3d/extras/particle_example/particles.gdshader b/project/addons/terrain_3d/extras/particle_example/particles.gdshader index dbc90f75f..5ed11a29e 100644 --- a/project/addons/terrain_3d/extras/particle_example/particles.gdshader +++ b/project/addons/terrain_3d/extras/particle_example/particles.gdshader @@ -34,6 +34,7 @@ uniform float patch_min_threshold : hint_range(0.0, 1.0, 0.001) = 0.025; uniform float patch_max_threshold : hint_range(0.0, 1.0, 0.001) = 0.2; group_uniforms filtering; +uniform int texture_filter : hint_range(-1, 31) = -1; // Specify a texture ID or -1 for all uniform float condition_dither_range : hint_range(0.0, 1.0, 0.01) = 0.15; uniform float surface_slope_min : hint_range(0.0, 1.0, 0.01) = 0.87; uniform float distance_fade_ammount : hint_range(0.0, 1.0, 0.01) = 0.5; @@ -186,7 +187,7 @@ void start() { h[5] = get_height(index_id, offsets.zy); // 5 (2,1) h[6] = get_height(index_id, offsets.zx); // 6 (2,0) h[7] = get_height(index_id, offsets.xz); // 7 (0,2) - + vec3 index_normal[4]; index_normal[0] = vec3(h[0] - h[1], _vertex_spacing, h[0] - h[7]); index_normal[1] = vec3(h[1] - h[5], _vertex_spacing, h[1] - h[4]); @@ -280,10 +281,12 @@ void start() { pos.xz = vec2(100000.0); } - // Hardcoded example, hand painted texture id 0 is filtered out. - if (!auto && ((base == 0 && blend < 0.7) || (over == 0 && blend >= 0.3))) { - pos.y = 0. / 0.; - pos.xz = vec2(100000.0); + // Apply only to specified texture id, or -1 for all. Adjust blend values as needed + if (!auto && texture_filter >= 0 && + ((base != texture_filter && blend < 0.7) || + (over != texture_filter && blend >= 0.3))) { + pos.y = 0. / 0.; + pos.xz = vec2(100000.0); } if (length(camera_position - pos) > max_dist) { diff --git a/project/addons/terrain_3d/extras/particle_example/process_material.tres b/project/addons/terrain_3d/extras/particle_example/process_material.tres index 0fa0796ec..733cb584d 100644 --- a/project/addons/terrain_3d/extras/particle_example/process_material.tres +++ b/project/addons/terrain_3d/extras/particle_example/process_material.tres @@ -32,6 +32,7 @@ shader_parameter/clod_min_threshold = 0.2 shader_parameter/clod_max_threshold = 0.8 shader_parameter/patch_min_threshold = 0.025 shader_parameter/patch_max_threshold = 0.2 +shader_parameter/texture_filter = -1 shader_parameter/condition_dither_range = 0.15 shader_parameter/surface_slope_min = 0.87 shader_parameter/distance_fade_ammount = 0.66 From a0fa5c372b9f52d34f76eb1465576244853859ca Mon Sep 17 00:00:00 2001 From: Xtarsia <69606701+Xtarsia@users.noreply.github.com> Date: Sun, 8 Mar 2026 12:06:13 +0000 Subject: [PATCH 3/6] only allow grass inside regions --- .../terrain_3d/extras/particle_example/particles.gdshader | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/addons/terrain_3d/extras/particle_example/particles.gdshader b/project/addons/terrain_3d/extras/particle_example/particles.gdshader index 5ed11a29e..bf9eff9b1 100644 --- a/project/addons/terrain_3d/extras/particle_example/particles.gdshader +++ b/project/addons/terrain_3d/extras/particle_example/particles.gdshader @@ -289,7 +289,7 @@ void start() { pos.xz = vec2(100000.0); } - if (length(camera_position - pos) > max_dist) { + if (length(camera_position - pos) > max_dist || get_index_coord(pos.xz).z < 0) { pos.y = 0. / 0.; pos.xz = vec2(100000.0); } else { From 9314e1d6c0c8b2f3ed1e575c2ba2b6c89c368a70 Mon Sep 17 00:00:00 2001 From: Cory Petkovsek <632766+TokisanGames@users.noreply.github.com> Date: Sun, 8 Mar 2026 19:28:22 +0700 Subject: [PATCH 4/6] Update demo materials & settings --- .../extras/particle_example/process_material.tres | 4 ++-- project/demo/Demo.tscn | 2 +- project/demo/data/M_terrain.tres | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/project/addons/terrain_3d/extras/particle_example/process_material.tres b/project/addons/terrain_3d/extras/particle_example/process_material.tres index 733cb584d..ba4742029 100644 --- a/project/addons/terrain_3d/extras/particle_example/process_material.tres +++ b/project/addons/terrain_3d/extras/particle_example/process_material.tres @@ -32,9 +32,9 @@ shader_parameter/clod_min_threshold = 0.2 shader_parameter/clod_max_threshold = 0.8 shader_parameter/patch_min_threshold = 0.025 shader_parameter/patch_max_threshold = 0.2 -shader_parameter/texture_filter = -1 +shader_parameter/texture_filter = 1 shader_parameter/condition_dither_range = 0.15 -shader_parameter/surface_slope_min = 0.87 +shader_parameter/surface_slope_min = 0.6999999843536 shader_parameter/distance_fade_ammount = 0.66 shader_parameter/ground_level = -618.9519819002201 shader_parameter/region_blend = 0.95600004541 diff --git a/project/demo/Demo.tscn b/project/demo/Demo.tscn index 761937b66..99f7d8e6d 100644 --- a/project/demo/Demo.tscn +++ b/project/demo/Demo.tscn @@ -31,7 +31,7 @@ collision_mask = 3 clipmap_target = NodePath("../Player") tessellation_level = 3 mesh_size = 96 -cull_margin = 1662.5 +cull_margin = 450.0 ocean_enabled = true ocean_material = ExtResource("7_fwrtk") ocean_light_target = NodePath("../Environment/DirectionalLight3D") diff --git a/project/demo/data/M_terrain.tres b/project/demo/data/M_terrain.tres index 0d920f586..03890d694 100644 --- a/project/demo/data/M_terrain.tres +++ b/project/demo/data/M_terrain.tres @@ -36,7 +36,7 @@ _shader_parameters = { "dual_scale_reduction": 0.3, "dual_scale_texture": 0, &"flat_terrain_normals": false, -&"ground_level": -618.9519819002201, +&"ground_level": -199.999962, "macro_variation1": Color(0.855, 0.8625, 0.9, 1), "macro_variation2": Color(0.9, 0.885, 0.81, 1), "macro_variation_slope": 0.333, @@ -46,7 +46,7 @@ _shader_parameters = { "noise1_scale": 0.04, "noise2_scale": 0.076, "noise_texture": SubResource("NoiseTexture2D_bov7h"), -&"region_blend": 0.95600004541, +&"region_blend": 0.9600000456, &"shader_uniforms": null, &"shader_uniforms::auto_shader": null, &"shader_uniforms::displacement": null, @@ -57,11 +57,11 @@ _shader_parameters = { &"shader_uniforms::world_background_noise": null, "tri_scale_reduction": 0.3, &"world_noise_fragment_normals": false, -&"world_noise_height": 75.00001601878989, +&"world_noise_height": 64.00001585487689, &"world_noise_lod_distance": 7500.0, &"world_noise_max_octaves": 4, &"world_noise_min_octaves": 2, -&"world_noise_offset": Vector3(0, 0, 0), +&"world_noise_offset": Vector3(-0.67, -4, -2.61), &"world_noise_scale": 5.0 } world_background = 2 From 8899fe5ecaf479fe150f617c76e6a676114b77bd Mon Sep 17 00:00:00 2001 From: Xtarsia <69606701+Xtarsia@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:33:56 +0000 Subject: [PATCH 5/6] remove unused constants --- .../terrain_3d/extras/particle_example/particles.gdshader | 5 ----- 1 file changed, 5 deletions(-) diff --git a/project/addons/terrain_3d/extras/particle_example/particles.gdshader b/project/addons/terrain_3d/extras/particle_example/particles.gdshader index bf9eff9b1..d152ea1d6 100644 --- a/project/addons/terrain_3d/extras/particle_example/particles.gdshader +++ b/project/addons/terrain_3d/extras/particle_example/particles.gdshader @@ -58,11 +58,6 @@ uniform highp sampler2DArray _height_maps : repeat_disable; uniform highp sampler2DArray _control_maps : repeat_disable; uniform highp sampler2DArray _color_maps : repeat_disable; -// Defined Constants -#define SKIP_PASS 0 -#define VERTEX_PASS 1 -#define FRAGMENT_PASS 2 - // Takes in world space XZ (UV) coordinates // Returns ivec3 with: // XY: (0 to _region_size - 1) coordinates within a region From 890456ac5fdb7650af165144dd91ebf554f222b2 Mon Sep 17 00:00:00 2001 From: Xtarsia <69606701+Xtarsia@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:34:29 +0000 Subject: [PATCH 6/6] reorder and update properties --- .../particle_example/terrain_3D_particles.gd | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/project/addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd b/project/addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd index e08cd9514..ab9757317 100644 --- a/project/addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd +++ b/project/addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd @@ -7,8 +7,32 @@ @tool extends Node3D +@export_category("Info") +## The maximum distance that particles will be drawn upto +## If using fade out effects like pixel alpha this is the limit to use. +@export_custom(PROPERTY_HINT_NONE, "suffix:m", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY) var max_draw_distance: float = 1.0: + set(value): + max_draw_distance = float(cell_width * grid_width) * 0.5 + + +## Displays current total particle count based on Cell Width and Instance Spacing +@export_custom(PROPERTY_HINT_NONE, "suffix:particles", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY) var particle_count: int = 1: + set(value): + particle_count = amount * grid_width * grid_width + + +@export_storage var rows: int = 1 +@export_storage var amount: int = 1: + set(value): + amount = value + particle_count = value + last_pos = Vector3.ZERO + for p in particle_nodes: + p.amount = amount + #region settings +@export_category("Settings") ## Auto set if attached as a child of a Terrain3D node @export var terrain: Terrain3D: set(value): @@ -31,7 +55,7 @@ extends Node3D cell_width = clamp(value, 8.0, 256.0) rows = maxi(int(cell_width / instance_spacing), 1) amount = rows * rows - min_draw_distance = 1.0 + max_draw_distance = 1.0 # Have to update aabb if terrain and terrain.data: var height_range: Vector2 = terrain.data.get_height_range() @@ -51,21 +75,10 @@ extends Node3D set(value): grid_width = value particle_count = 1 - min_draw_distance = 1.0 + max_draw_distance = 1.0 _create_grid() -@export_storage var rows: int = 1 - -@export_storage var amount: int = 1: - set(value): - amount = value - particle_count = value - last_pos = Vector3.ZERO - for p in particle_nodes: - p.amount = amount - - @export_range(1, 256, 1) var process_fixed_fps: int = 30: set(value): process_fixed_fps = maxi(value, 1) @@ -73,13 +86,6 @@ extends Node3D p.fixed_fps = process_fixed_fps p.preprocess = 1.0 / float(process_fixed_fps) - -## Access to process material parameters -@export var process_material: ShaderMaterial - -## The mesh that each particle will render -@export var mesh: Mesh - @export var shadow_mode: GeometryInstance3D.ShadowCastingSetting = ( GeometryInstance3D.ShadowCastingSetting.SHADOW_CASTING_SETTING_ON): set(value): @@ -87,6 +93,12 @@ extends Node3D for p in particle_nodes: p.cast_shadow = value +## Access to process material parameters +@export var process_material: ShaderMaterial + +## The mesh that each particle will render +@export var mesh: Mesh + ## Override material for the particle mesh @export_custom( @@ -97,20 +109,6 @@ extends Node3D for p in particle_nodes: p.material_override = mesh_material_override - -@export_group("Info") -## The minimum distance that particles will be drawn upto -## If using fade out effects like pixel alpha this is the limit to use. -@export var min_draw_distance: float = 1.0: - set(value): - min_draw_distance = float(cell_width * grid_width) * 0.5 - - -## Displays current total particle count based on Cell Width and Instance Spacing -@export var particle_count: int = 1: - set(value): - particle_count = amount * grid_width * grid_width - #endregion @@ -237,4 +235,4 @@ func _update_process_parameters() -> void: RenderingServer.material_set_param(process_rid, "_color_maps", terrain.data.get_color_maps_rid()) RenderingServer.material_set_param(process_rid, "instance_spacing", instance_spacing) RenderingServer.material_set_param(process_rid, "instance_rows", rows) - RenderingServer.material_set_param(process_rid, "max_dist", min_draw_distance) + RenderingServer.material_set_param(process_rid, "max_dist", max_draw_distance)