Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
101 changes: 65 additions & 36 deletions project/addons/terrain_3d/extras/particle_example/particles.gdshader
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@ 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;

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;
Expand All @@ -55,34 +58,57 @@ 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 & 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)))));
}
Expand Down Expand Up @@ -142,20 +168,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]);
Expand Down Expand Up @@ -249,13 +276,15 @@ 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) {
if (length(camera_position - pos) > max_dist || get_index_coord(pos.xz).z < 0) {
pos.y = 0. / 0.;
pos.xz = vec2(100000.0);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ 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/surface_slope_min = 0.6999999843536
shader_parameter/distance_fade_ammount = 0.66
shader_parameter/ground_level = -618.9519819002201
Copy link
Copy Markdown
Owner

@TokisanGames TokisanGames Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this and region_blend get updated and saved in this file?

I changed ground level in the terrain, saved, and restarted it didn't change in the particle material or file.
Because you're updating with the render server instead of the material, the changes don't get pushed or updated in the inspector, or apparently saved in the file.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should be synced by _update_process_parameters() interrain_3D_particles.gd

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That probably updates it in the renderer, but not in the resource in the inspector or the file from what I've seen. So I don't know how you got it into the file so it could be pushed in this PR. I couldn't get it to update on mine. And if someone looks in the private folder the settings will appear wrong.

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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()
Expand All @@ -51,42 +75,30 @@ 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)
for p in particle_nodes:
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):
shadow_mode = value
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(
Expand All @@ -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


Expand Down Expand Up @@ -222,6 +220,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)
Expand All @@ -234,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)
2 changes: 1 addition & 1 deletion project/demo/Demo.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
8 changes: 4 additions & 4 deletions project/demo/data/M_terrain.tres
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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
Expand Down