2
0
Эх сурвалжийг харах

Merge pull request #55387 from RPicster/particles-turbulence

Rémi Verschelde 3 жил өмнө
parent
commit
beb8fd5e8b

+ 47 - 1
doc/classes/ParticlesMaterial.xml

@@ -271,6 +271,42 @@
 		<member name="tangential_accel_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0">
 			Minimum equivalent of [member tangential_accel_max].
 		</member>
+		<member name="turbulence_active" type="bool" setter="set_turbulence_active" getter="get_turbulence_active" default="false">
+			Enables and disables Turbulence for the particle system.
+		</member>
+		<member name="turbulence_influence_max" type="float" setter="set_param_max" getter="get_param_max">
+			Minimum turbulence influence on each particle.
+			 The actual amount of turbulence influence on each particle is calculated as a random value between [member turbulence_influence_min] and [member turbulence_influence_max] and multiplied by the amount of turbulence influence from [member turbulence_influence_over_life].
+		</member>
+		<member name="turbulence_influence_min" type="float" setter="set_param_min" getter="get_param_min">
+			Maximum turbulence influence on each particle.
+			The actual amount of turbulence influence on each particle is calculated as a random value between [member turbulence_influence_min] and [member turbulence_influence_max] and multiplied by the amount of turbulence influence from [member turbulence_influence_over_life].
+		</member>
+		<member name="turbulence_influence_over_life" type="Texture2D" setter="set_param_texture" getter="get_param_texture">
+			Each particle's amount of turbulence will be influenced along this [CurveTexture] over its life time.
+		</member>
+		<member name="turbulence_initial_displacement_max" type="float" setter="set_param_max" getter="get_param_max">
+			Maximum displacement of each particles spawn position by the turbulence.
+			The actual amount of displacement will be a factor of the underlying turbulence multiplied by a random value between [member turbulence_initial_displacement_min] and [member turbulence_initial_displacement_max].
+		</member>
+		<member name="turbulence_initial_displacement_min" type="float" setter="set_param_min" getter="get_param_min">
+			Minimum displacement of each particles spawn position by the turbulence.
+			The actual amount of displacement will be a factor of the underlying turbulence multiplied by a random value between [member turbulence_initial_displacement_min] and [member turbulence_initial_displacement_max].
+		</member>
+		<member name="turbulence_noise_scale" type="float" setter="set_turbulence_noise_scale" getter="get_turbulence_noise_scale">
+			This value controls the overall scale/frequency of the turbulence noise pattern.
+			A small scale will result in smaller features with more detail while a high scale will result in smoother noise with larger features.
+		</member>
+		<member name="turbulence_noise_speed" type="Vector3" setter="set_turbulence_noise_speed" getter="get_turbulence_noise_speed">
+			The movement speed of the turbulence pattern. This changes how quickly the noise changes over time.
+			A value of [code]Vector3(0.0, 0.0, 0.0)[/code] will freeze the turbulence pattern in place.
+		</member>
+		<member name="turbulence_noise_speed_random" type="float" setter="set_turbulence_noise_speed_random" getter="get_turbulence_noise_speed_random">
+			Use to influence the noise speed in a random pattern. This helps to break up visible movement patterns.
+		</member>
+		<member name="turbulence_noise_strength" type="float" setter="set_turbulence_noise_strength" getter="get_turbulence_noise_strength">
+			The turbulence noise strength. Increasing this will result in a stronger, more contrasting, noise pattern.
+		</member>
 	</members>
 	<constants>
 		<constant name="PARAM_INITIAL_LINEAR_VELOCITY" value="0" enum="Parameter">
@@ -309,7 +345,7 @@
 		<constant name="PARAM_ANIM_OFFSET" value="11" enum="Parameter">
 			Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set animation offset properties.
 		</constant>
-		<constant name="PARAM_MAX" value="12" enum="Parameter">
+		<constant name="PARAM_MAX" value="15" enum="Parameter">
 			Represents the size of the [enum Parameter] enum.
 		</constant>
 		<constant name="PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY" value="0" enum="ParticleFlags">
@@ -348,6 +384,15 @@
 		<constant name="EMISSION_SHAPE_MAX" value="7" enum="EmissionShape">
 			Represents the size of the [enum EmissionShape] enum.
 		</constant>
+		<constant name="PARAM_TURB_VEL_INFLUENCE" value="13" enum="Parameter">
+			Use with [method set_param_min] and [method set_param_max] to set the turbulence minimum und maximum influence on each particles velocity.
+		</constant>
+		<constant name="PARAM_TURB_INIT_DISPLACEMENT" value="14" enum="Parameter">
+			Use with [method set_param_min] and [method set_param_max] to set the turbulence minimum and maximum displacement of the particles spawn position.
+		</constant>
+		<constant name="PARAM_TURB_INFLUENCE_OVER_LIFE" value="12" enum="Parameter">
+			Use with [method set_param_texture] to set the turbulence influence over the particles life time.
+		</constant>
 		<constant name="SUB_EMITTER_DISABLED" value="0" enum="SubEmitterMode">
 		</constant>
 		<constant name="SUB_EMITTER_CONSTANT" value="1" enum="SubEmitterMode">
@@ -357,6 +402,7 @@
 		<constant name="SUB_EMITTER_AT_COLLISION" value="3" enum="SubEmitterMode">
 		</constant>
 		<constant name="SUB_EMITTER_MAX" value="4" enum="SubEmitterMode">
+			Represents the size of the [enum SubEmitterMode] enum.
 		</constant>
 	</constants>
 </class>

+ 282 - 17
scene/resources/particles_material.cpp

@@ -98,6 +98,17 @@ void ParticlesMaterial::init_shaders() {
 	shader_names->emission_ring_radius = "emission_ring_radius";
 	shader_names->emission_ring_inner_radius = "emission_ring_inner_radius";
 
+	shader_names->turbulence_active = "turbulence_active";
+	shader_names->turbulence_noise_strength = "turbulence_noise_strength";
+	shader_names->turbulence_noise_scale = "turbulence_noise_scale";
+	shader_names->turbulence_noise_speed = "turbulence_noise_speed";
+	shader_names->turbulence_noise_speed_random = "turbulence_noise_speed_random";
+	shader_names->turbulence_influence_over_life = "turbulence_influence_over_life";
+	shader_names->turbulence_influence_min = "turbulence_influence_min";
+	shader_names->turbulence_influence_max = "turbulence_influence_max";
+	shader_names->turbulence_initial_displacement_min = "turbulence_initial_displacement_min";
+	shader_names->turbulence_initial_displacement_max = "turbulence_initial_displacement_max";
+
 	shader_names->gravity = "gravity";
 
 	shader_names->lifetime_randomness = "lifetime_randomness";
@@ -141,7 +152,6 @@ void ParticlesMaterial::_update_shader() {
 		shader_map[mk].users++;
 		return;
 	}
-
 	//must create a shader!
 
 	// Add a comment to describe the shader origin (useful when converting to ShaderMaterial).
@@ -282,6 +292,77 @@ void ParticlesMaterial::_update_shader() {
 		code += "uniform float collision_bounce;\n";
 	}
 
+	if (turbulence_active) {
+		code += "uniform float turbulence_noise_strength;\n";
+		code += "uniform float turbulence_noise_scale;\n";
+		code += "uniform float turbulence_influence_min;\n";
+		code += "uniform float turbulence_influence_max;\n";
+		code += "uniform float turbulence_initial_displacement_min;\n";
+		code += "uniform float turbulence_initial_displacement_max;\n";
+		code += "uniform float turbulence_noise_speed_random;\n";
+		code += "uniform vec3 turbulence_noise_speed = vec3(1.0, 1.0, 1.0);\n";
+		if (tex_parameters[PARAM_TURB_INFLUENCE_OVER_LIFE].is_valid()) {
+			code += "uniform sampler2D turbulence_influence_over_life;\n";
+		}
+		if (turbulence_color_ramp.is_valid()) {
+			code += "uniform sampler2D turbulence_color_ramp;\n";
+		}
+		code += "\n";
+
+		//functions for 3D noise / turbulence
+		code += "\n\n";
+		code += "// 3D Noise with friendly permission by Inigo Quilez\n";
+		code += "vec3 hash_noise( vec3 p ) {\n";
+		code += "	p *= mat3(vec3(127.1, 311.7, -53.7), vec3(269.5, 183.3, 77.1), vec3(-301.7, 27.3, 215.3));\n";
+		code += "	return 2.0 * fract(fract(p)*4375.55) -1.;\n";
+		code += "}\n";
+		code += "\n";
+		code += "float noise( vec3 p) {\n";
+		code += "	vec3 i = floor(p);;\n";
+		code += "	vec3 f = fract(p);\n ";
+		code += "	vec3 u = f * f * (3.0 - 2.0 * f);\n";
+		code += "\n";
+		code += "	return 2.0*mix( mix( mix( dot( hash_noise( i + vec3(0.0,0.0,0.0) ), f - vec3(0.0,0.0,0.0) ), dot( hash_noise( i + vec3(1.0,0.0,0.0) ), f - vec3(1.0,0.0,0.0) ), u.x),\n";
+		code += "			mix( dot( hash_noise( i + vec3(0.0,1.0,0.0) ), f - vec3(0.0,1.0,0.0) ), dot( hash_noise( i + vec3(1.0,1.0,0.0) ), f - vec3(1.0,1.0,0.0) ), u.x), u.y),\n";
+		code += "		mix( mix( dot( hash_noise( i + vec3(0.0,0.0,1.0) ), f - vec3(0.0,0.0,1.0) ), dot( hash_noise( i + vec3(1.0,0.0,1.0) ), f - vec3(1.0,0.0,1.0) ), u.x),\n";
+		code += "			mix( dot( hash_noise( i + vec3(0.0,1.0,1.0) ), f - vec3(0.0,1.0,1.0) ), dot( hash_noise( i + vec3(1.0,1.0,1.0) ), f - vec3(1.0,1.0,1.0) ), u.x), u.y), u.z);\n";
+		code += "}\n\n";
+		code += "// Curl 3D and noise_3d function with friendly permission by Isaac Cohen\n";
+		code += "vec3 noise_3d(vec3 p) {\n";
+		code += "	float s = noise(p);\n";
+		code += "	float s1 = noise(vec3(p.y - 19.1, p.z + 33.4, p.x + 47.2));\n";
+		code += "   float s2 = noise(vec3(p.z + 74.2, p.x - 124.5, p.y + 99.4));\n";
+		code += "	vec3 c = vec3(s, s1, s2);\n";
+		code += "	return c;\n";
+		code += "}\n\n";
+		code += "vec3 curl_3d(vec3 p, float c) {\n";
+		code += "	float epsilon = 0.001 + c;\n";
+		code += "	vec3 dx = vec3(epsilon, 0.0, 0.0);\n";
+		code += "	vec3 dy = vec3(0.0, epsilon, 0.0);\n";
+		code += "	vec3 dz = vec3(0.0, 0.0, epsilon);\n";
+		code += "	vec3 x0 = noise_3d(p - dx).xyz;\n";
+		code += "	vec3 x1 = noise_3d(p + dx).xyz;\n";
+		code += "	vec3 y0 = noise_3d(p - dy).xyz;\n";
+		code += "	vec3 y1 = noise_3d(p + dy).xyz;\n";
+		code += "	vec3 z0 = noise_3d(p - dz).xyz;\n";
+		code += "	vec3 z1 = noise_3d(p + dz).xyz;\n";
+		code += "	float x = y1.z - y0.z - z1.y + z0.y;\n";
+		code += "	float y = z1.x - z0.x - x1.z + x0.z;\n";
+		code += "	float z = x1.y - x0.y - y1.x + y0.x;\n";
+		code += "	float divisor = 1.0 / (2.0 * epsilon);\n";
+		code += "	return vec3(normalize(vec3(x, y, z) * divisor));\n";
+		code += "}\n";
+		code += "vec3 get_noise_direction(vec3 pos, vec3 emission_pos, vec3 time_noise) {\n";
+		code += "	float adj_contrast = max((turbulence_noise_strength - 1.0), 0.0) * 70.0;\n";
+		code += "	vec3 noise_time = (vec3(TIME) * turbulence_noise_speed) + time_noise;\n";
+		code += "	vec3 noise_pos = (pos * turbulence_noise_scale) - emission_pos;\n";
+		code += "	vec3 diff = pos - emission_pos;\n";
+		code += "	vec3 noise_direction = curl_3d(noise_pos + noise_time - diff, adj_contrast);\n";
+		code += "	noise_direction = mix(0.9 * noise_direction, noise_direction, turbulence_noise_strength - 9.0);\n";
+		code += "	return noise_direction;\n";
+		code += "}\n";
+	}
+
 	//need a random function
 	code += "\n\n";
 	code += "float rand_from_seed(inout uint seed) {\n";
@@ -463,8 +544,18 @@ void ParticlesMaterial::_update_shader() {
 			break;
 		}
 	}
-
 	code += "	if (RESTART_VELOCITY) VELOCITY = (EMISSION_TRANSFORM * vec4(VELOCITY, 0.0)).xyz;\n";
+	// Apply noise/turbulence: initial displacement.
+	if (turbulence_active) {
+		if (get_turbulence_noise_speed_random() >= 0.0) {
+			code += "	vec3 time_noise = noise_3d( vec3(TIME) * turbulence_noise_speed_random ) * -turbulence_noise_speed;\n";
+		} else {
+			code += "	const vec3 time_noise = vec3(0.0);\n";
+		}
+		code += "	vec3 noise_direction = get_noise_direction(TRANSFORM[3].xyz, EMISSION_TRANSFORM[3].xyz, time_noise);\n";
+		code += "	float turb_init_displacement = mix(turbulence_initial_displacement_min, turbulence_initial_displacement_max, rand_from_seed(alt_seed));";
+		code += "	TRANSFORM[3].xyz += noise_direction * turb_init_displacement;\n";
+	}
 	code += "	TRANSFORM = EMISSION_TRANSFORM * TRANSFORM;\n";
 	if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
 		code += "	VELOCITY.z = 0.0;\n";
@@ -483,7 +574,6 @@ void ParticlesMaterial::_update_shader() {
 	if (color_initial_ramp.is_valid()) {
 		code += "	float color_initial_rand = rand_from_seed(alt_seed);\n";
 	}
-
 	code += "	float pi = 3.14159;\n";
 	code += "	float degree_to_rad = pi / 180.0;\n";
 	code += "\n";
@@ -570,7 +660,7 @@ void ParticlesMaterial::_update_shader() {
 	code += "	vec3 diff = pos - org;\n";
 	code += "	force += length(diff) > 0.0 ? normalize(diff) * tex_radial_accel * mix(radial_accel_min, radial_accel_max, rand_from_seed(alt_seed)) : vec3(0.0);\n";
 	code += "	// apply tangential acceleration;\n";
-	code += "	float tangent_accel_val = tex_tangent_accel * mix(tangent_accel_min, tangent_accel_max, rand_from_seed(alt_seed))\n;";
+	code += "	float tangent_accel_val = tex_tangent_accel * mix(tangent_accel_min, tangent_accel_max, rand_from_seed(alt_seed));\n";
 	if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
 		code += "	force += length(diff.yx) > 0.0 ? vec3(normalize(diff.yx * vec2(-1.0, 1.0)), 0.0) * tangent_accel_val : vec3(0.0);\n";
 
@@ -584,6 +674,41 @@ void ParticlesMaterial::_update_shader() {
 
 	code += "	// apply attractor forces\n";
 	code += "	VELOCITY += force * DELTA;\n";
+
+	if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
+		code += "	VELOCITY = normalize(VELOCITY) * tex_linear_velocity;\n";
+	}
+
+	// Apply noise/turbulence.
+	if (turbulence_active) {
+		code += "	// apply turbulence\n";
+		if (tex_parameters[PARAM_TURB_INFLUENCE_OVER_LIFE].is_valid()) {
+			code += "	float turbulence_influence = textureLod(turbulence_influence_over_life, vec2(tv, 0.0), 0.0).r;\n";
+		} else {
+			code += "	const float turbulence_influence = 1.0;\n";
+		}
+		code += "	\n";
+		if (get_turbulence_noise_speed_random() >= 0.0) {
+			code += "	vec3 time_noise = noise_3d( vec3(TIME) * turbulence_noise_speed_random ) * -turbulence_noise_speed;\n";
+		} else {
+			code += "	const vec3 time_noise = vec3(0.0);\n";
+		}
+		code += "	vec3 noise_direction = get_noise_direction(TRANSFORM[3].xyz, EMISSION_TRANSFORM[3].xyz, time_noise);\n";
+		// If collision happened, turbulence is no longer applied.
+		String extra_tab = "";
+		if (collision_enabled) {
+			code += "	if (!COLLIDED) {\n";
+			extra_tab = "	";
+		}
+		code += extra_tab + "	\n";
+		code += extra_tab + "	float vel_mag = length(VELOCITY);\n";
+		code += extra_tab + "	float vel_infl = clamp(mix(turbulence_influence_min, turbulence_influence_max, rand_from_seed(alt_seed)) * turbulence_influence, 0.0, 1.0);\n";
+		code += extra_tab + "	VELOCITY = mix(VELOCITY, normalize(noise_direction) * vel_mag * (1.0 + (1.0 - vel_infl) * 0.2), vel_infl);\n";
+		if (collision_enabled) {
+			code += "	}";
+		}
+	}
+	code += "	\n";
 	code += "	// orbit velocity\n";
 	if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
 		code += "	float orbit_amount = tex_orbit_velocity * mix(orbit_velocity_min, orbit_velocity_max, rand_from_seed(alt_seed));\n";
@@ -595,9 +720,6 @@ void ParticlesMaterial::_update_shader() {
 		code += "	}\n";
 	}
 
-	if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
-		code += "	VELOCITY = normalize(VELOCITY) * tex_linear_velocity;\n";
-	}
 	code += "	float dmp = mix(damping_min, damping_max, rand_from_seed(alt_seed));\n";
 	code += "	if (dmp * tex_damping > 0.0) {\n";
 	code += "		float v = length(VELOCITY);\n";
@@ -701,24 +823,34 @@ void ParticlesMaterial::_update_shader() {
 			code += "	TRANSFORM[3] = origin;\n";
 		}
 	}
-	//scale by scale
-	code += "	float base_scale = mix(scale_min, scale_max, scale_rand);\n";
-	code += "	base_scale = sign(base_scale) * max(abs(base_scale), 0.001);\n";
 
-	code += "	TRANSFORM[0].xyz *= base_scale * sign(tex_scale.r) * max(abs(tex_scale.r), 0.001);\n";
-	code += "	TRANSFORM[1].xyz *= base_scale * sign(tex_scale.g) * max(abs(tex_scale.g), 0.001);\n";
-	code += "	TRANSFORM[2].xyz *= base_scale * sign(tex_scale.b) * max(abs(tex_scale.b), 0.001);\n";
 	if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
-		code += "	VELOCITY.z = 0.0;\n";
 		code += "	TRANSFORM[3].z = 0.0;\n";
 	}
+
 	if (collision_enabled) {
 		code += "	if (COLLIDED) {\n";
-		code += "		TRANSFORM[3].xyz+=COLLISION_NORMAL * COLLISION_DEPTH;\n";
-		code += "		VELOCITY -= COLLISION_NORMAL * dot(COLLISION_NORMAL, VELOCITY) * (1.0 + collision_bounce);\n";
-		code += "		VELOCITY = mix(VELOCITY,vec3(0.0),collision_friction * DELTA * 100.0);\n";
+		code += "		if (length(VELOCITY) > 3.0) {\n";
+		code += "			TRANSFORM[3].xyz += COLLISION_NORMAL * COLLISION_DEPTH;\n";
+		code += "			VELOCITY -= COLLISION_NORMAL * dot(COLLISION_NORMAL, VELOCITY) * (1.0 + collision_bounce);\n";
+		code += "			VELOCITY = mix(VELOCITY,vec3(0.0),clamp(collision_friction, 0.0, 1.0));\n";
+		code += "		} else {\n";
+		code += "			VELOCITY = vec3(0.0);\n";
+		// If turbulence is enabled, set the noise direction to up so the turbulence color is "neutral"
+		if (turbulence_active) {
+			code += "			noise_direction = vec3(1.0, 0.0, 0.0);\n";
+		}
+		code += "		}\n";
 		code += "	}\n";
 	}
+
+	// scale by scale
+	code += "	float base_scale = mix(scale_min, scale_max, scale_rand);\n";
+	code += "	base_scale = sign(base_scale) * max(abs(base_scale), 0.001);\n";
+	code += "	TRANSFORM[0].xyz *= base_scale * sign(tex_scale.r) * max(abs(tex_scale.r), 0.001);\n";
+	code += "	TRANSFORM[1].xyz *= base_scale * sign(tex_scale.g) * max(abs(tex_scale.g), 0.001);\n";
+	code += "	TRANSFORM[2].xyz *= base_scale * sign(tex_scale.b) * max(abs(tex_scale.b), 0.001);\n";
+
 	if (sub_emitter_mode != SUB_EMITTER_DISABLED) {
 		code += "	int emit_count = 0;\n";
 		switch (sub_emitter_mode) {
@@ -856,6 +988,15 @@ void ParticlesMaterial::set_param_min(Parameter p_param, float p_value) {
 		case PARAM_ANIM_OFFSET: {
 			RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_min, p_value);
 		} break;
+		case PARAM_TURB_VEL_INFLUENCE: {
+			RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->turbulence_influence_min, p_value);
+		} break;
+		case PARAM_TURB_INIT_DISPLACEMENT: {
+			RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->turbulence_initial_displacement_min, p_value);
+		} break;
+		case PARAM_TURB_INFLUENCE_OVER_LIFE: {
+			// Can't happen, but silences warning
+		} break;
 		case PARAM_MAX:
 			break; // Can't happen, but silences warning
 	}
@@ -912,6 +1053,15 @@ void ParticlesMaterial::set_param_max(Parameter p_param, float p_value) {
 		case PARAM_ANIM_OFFSET: {
 			RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_max, p_value);
 		} break;
+		case PARAM_TURB_VEL_INFLUENCE: {
+			RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->turbulence_influence_max, p_value);
+		} break;
+		case PARAM_TURB_INIT_DISPLACEMENT: {
+			RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->turbulence_initial_displacement_max, p_value);
+		} break;
+		case PARAM_TURB_INFLUENCE_OVER_LIFE: {
+			// Can't happen, but silences warning
+		} break;
 		case PARAM_MAX:
 			break; // Can't happen, but silences warning
 	}
@@ -986,6 +1136,16 @@ void ParticlesMaterial::set_param_texture(Parameter p_param, const Ref<Texture2D
 		case PARAM_ANIM_OFFSET: {
 			RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_texture, tex_rid);
 		} break;
+		case PARAM_TURB_INFLUENCE_OVER_LIFE: {
+			RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->turbulence_influence_over_life, tex_rid);
+			_adjust_curve_range(p_texture, 0, 1);
+		} break;
+		case PARAM_TURB_VEL_INFLUENCE: {
+			// Can't happen, but silences warning
+		} break;
+		case PARAM_TURB_INIT_DISPLACEMENT: {
+			// Can't happen, but silences warning
+		} break;
 		case PARAM_MAX:
 			break; // Can't happen, but silences warning
 	}
@@ -1151,6 +1311,54 @@ real_t ParticlesMaterial::get_emission_ring_inner_radius() const {
 	return emission_ring_inner_radius;
 }
 
+void ParticlesMaterial::set_turbulence_active(const bool p_turbulence_active) {
+	turbulence_active = p_turbulence_active;
+	RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->turbulence_active, turbulence_active);
+	_queue_shader_change();
+	notify_property_list_changed();
+}
+
+bool ParticlesMaterial::get_turbulence_active() const {
+	return turbulence_active;
+}
+
+void ParticlesMaterial::set_turbulence_noise_strength(float p_turbulence_noise_strength) {
+	turbulence_noise_strength = p_turbulence_noise_strength;
+	RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->turbulence_noise_strength, p_turbulence_noise_strength);
+}
+
+float ParticlesMaterial::get_turbulence_noise_strength() const {
+	return turbulence_noise_strength;
+}
+
+void ParticlesMaterial::set_turbulence_noise_scale(float p_turbulence_noise_scale) {
+	turbulence_noise_scale = p_turbulence_noise_scale;
+	float shader_turbulence_noise_scale = (pow(p_turbulence_noise_scale, 0.25) * 5.6234 / 10.0) * 4.0 - 3.0;
+	RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->turbulence_noise_scale, shader_turbulence_noise_scale);
+}
+
+float ParticlesMaterial::get_turbulence_noise_scale() const {
+	return turbulence_noise_scale;
+}
+
+void ParticlesMaterial::set_turbulence_noise_speed_random(float p_turbulence_noise_speed_random) {
+	turbulence_noise_speed_random = p_turbulence_noise_speed_random;
+	RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->turbulence_noise_speed_random, p_turbulence_noise_speed_random);
+}
+
+float ParticlesMaterial::get_turbulence_noise_speed_random() const {
+	return turbulence_noise_speed_random;
+}
+
+void ParticlesMaterial::set_turbulence_noise_speed(const Vector3 &p_turbulence_noise_speed) {
+	turbulence_noise_speed = p_turbulence_noise_speed;
+	RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->turbulence_noise_speed, turbulence_noise_speed);
+}
+
+Vector3 ParticlesMaterial::get_turbulence_noise_speed() const {
+	return turbulence_noise_speed;
+}
+
 void ParticlesMaterial::set_gravity(const Vector3 &p_gravity) {
 	gravity = p_gravity;
 	Vector3 gset = gravity;
@@ -1214,6 +1422,20 @@ void ParticlesMaterial::_validate_property(PropertyInfo &property) const {
 	if (property.name.begins_with("orbit_") && !particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
 		property.usage = PROPERTY_USAGE_NONE;
 	}
+
+	if (!turbulence_active) {
+		if (property.name == "turbulence_noise_strength" ||
+				property.name == "turbulence_noise_scale" ||
+				property.name == "turbulence_noise_speed" ||
+				property.name == "turbulence_noise_speed_random" ||
+				property.name == "turbulence_influence_over_life" ||
+				property.name == "turbulence_influence_min" ||
+				property.name == "turbulence_influence_max" ||
+				property.name == "turbulence_initial_displacement_min" ||
+				property.name == "turbulence_initial_displacement_max") {
+			property.usage = PROPERTY_USAGE_NONE;
+		}
+	}
 }
 
 void ParticlesMaterial::set_sub_emitter_mode(SubEmitterMode p_sub_emitter_mode) {
@@ -1365,6 +1587,21 @@ void ParticlesMaterial::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &ParticlesMaterial::set_emission_ring_inner_radius);
 	ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &ParticlesMaterial::get_emission_ring_inner_radius);
 
+	ClassDB::bind_method(D_METHOD("get_turbulence_active"), &ParticlesMaterial::get_turbulence_active);
+	ClassDB::bind_method(D_METHOD("set_turbulence_active", "turbulence_active"), &ParticlesMaterial::set_turbulence_active);
+
+	ClassDB::bind_method(D_METHOD("get_turbulence_noise_strength"), &ParticlesMaterial::get_turbulence_noise_strength);
+	ClassDB::bind_method(D_METHOD("set_turbulence_noise_strength", "turbulence_noise_strength"), &ParticlesMaterial::set_turbulence_noise_strength);
+
+	ClassDB::bind_method(D_METHOD("get_turbulence_noise_scale"), &ParticlesMaterial::get_turbulence_noise_scale);
+	ClassDB::bind_method(D_METHOD("set_turbulence_noise_scale", "turbulence_noise_scale"), &ParticlesMaterial::set_turbulence_noise_scale);
+
+	ClassDB::bind_method(D_METHOD("get_turbulence_noise_speed_random"), &ParticlesMaterial::get_turbulence_noise_speed_random);
+	ClassDB::bind_method(D_METHOD("set_turbulence_noise_speed_random", "turbulence_noise_speed_random"), &ParticlesMaterial::set_turbulence_noise_speed_random);
+
+	ClassDB::bind_method(D_METHOD("get_turbulence_noise_speed"), &ParticlesMaterial::get_turbulence_noise_speed);
+	ClassDB::bind_method(D_METHOD("set_turbulence_noise_speed", "turbulence_noise_speed"), &ParticlesMaterial::set_turbulence_noise_speed);
+
 	ClassDB::bind_method(D_METHOD("get_gravity"), &ParticlesMaterial::get_gravity);
 	ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &ParticlesMaterial::set_gravity);
 
@@ -1467,6 +1704,19 @@ void ParticlesMaterial::_bind_methods() {
 	ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_min", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_min", "get_param_min", PARAM_HUE_VARIATION);
 	ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_max", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_max", "get_param_max", PARAM_HUE_VARIATION);
 	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "hue_variation_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_HUE_VARIATION);
+
+	ADD_GROUP("Turbulence", "turbulence_");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "turbulence_active"), "set_turbulence_active", "get_turbulence_active");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "turbulence_noise_strength", PROPERTY_HINT_RANGE, "0,20,0.01"), "set_turbulence_noise_strength", "get_turbulence_noise_strength");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "turbulence_noise_scale", PROPERTY_HINT_RANGE, "0,10,0.01"), "set_turbulence_noise_scale", "get_turbulence_noise_scale");
+	ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "turbulence_noise_speed"), "set_turbulence_noise_speed", "get_turbulence_noise_speed");
+	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "turbulence_noise_speed_random", PROPERTY_HINT_RANGE, "0,10,0.01"), "set_turbulence_noise_speed_random", "get_turbulence_noise_speed_random");
+	ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "turbulence_influence_min", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_min", "get_param_min", PARAM_TURB_VEL_INFLUENCE);
+	ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "turbulence_influence_max", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_max", "get_param_max", PARAM_TURB_VEL_INFLUENCE);
+	ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "turbulence_initial_displacement_min", PROPERTY_HINT_RANGE, "-100,100,0.1"), "set_param_min", "get_param_min", PARAM_TURB_INIT_DISPLACEMENT);
+	ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "turbulence_initial_displacement_max", PROPERTY_HINT_RANGE, "-100,100,0.1"), "set_param_max", "get_param_max", PARAM_TURB_INIT_DISPLACEMENT);
+	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "turbulence_influence_over_life", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_TURB_INFLUENCE_OVER_LIFE);
+
 	ADD_GROUP("Animation", "anim_");
 	ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_min", PROPERTY_HINT_RANGE, "0,16,0.01,or_lesser,or_greater"), "set_param_min", "get_param_min", PARAM_ANIM_SPEED);
 	ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_max", PROPERTY_HINT_RANGE, "0,16,0.01,or_lesser,or_greater"), "set_param_max", "get_param_max", PARAM_ANIM_SPEED);
@@ -1517,6 +1767,10 @@ void ParticlesMaterial::_bind_methods() {
 	BIND_ENUM_CONSTANT(EMISSION_SHAPE_RING);
 	BIND_ENUM_CONSTANT(EMISSION_SHAPE_MAX);
 
+	BIND_ENUM_CONSTANT(PARAM_TURB_VEL_INFLUENCE);
+	BIND_ENUM_CONSTANT(PARAM_TURB_INIT_DISPLACEMENT);
+	BIND_ENUM_CONSTANT(PARAM_TURB_INFLUENCE_OVER_LIFE);
+
 	BIND_ENUM_CONSTANT(SUB_EMITTER_DISABLED);
 	BIND_ENUM_CONSTANT(SUB_EMITTER_CONSTANT);
 	BIND_ENUM_CONSTANT(SUB_EMITTER_AT_END);
@@ -1560,6 +1814,17 @@ ParticlesMaterial::ParticlesMaterial() :
 	set_emission_ring_height(1);
 	set_emission_ring_radius(1);
 	set_emission_ring_inner_radius(0);
+
+	set_turbulence_active(false);
+	set_turbulence_noise_speed(Vector3(0.5, 0.5, 0.5));
+	set_turbulence_noise_strength(1);
+	set_turbulence_noise_scale(9);
+	set_turbulence_noise_speed_random(0);
+	set_param_min(PARAM_TURB_VEL_INFLUENCE, 0.1);
+	set_param_max(PARAM_TURB_VEL_INFLUENCE, 0.1);
+	set_param_min(PARAM_TURB_INIT_DISPLACEMENT, 0.0);
+	set_param_max(PARAM_TURB_INIT_DISPLACEMENT, 0.0);
+
 	set_gravity(Vector3(0, -9.8, 0));
 	set_lifetime_randomness(0);
 

+ 37 - 1
scene/resources/particles_material.h

@@ -58,6 +58,9 @@ public:
 		PARAM_HUE_VARIATION,
 		PARAM_ANIM_SPEED,
 		PARAM_ANIM_OFFSET,
+		PARAM_TURB_INFLUENCE_OVER_LIFE,
+		PARAM_TURB_VEL_INFLUENCE,
+		PARAM_TURB_INIT_DISPLACEMENT,
 		PARAM_MAX
 	};
 
@@ -105,9 +108,10 @@ private:
 			uint32_t attractor_enabled : 1;
 			uint32_t collision_enabled : 1;
 			uint32_t collision_scale : 1;
+			uint32_t turbulence_active : 1;
 		};
 
-		uint32_t key = 0;
+		uint64_t key = 0;
 
 		static uint32_t hash(const MaterialKey &p_key) {
 			return hash_murmur3_one_32(p_key.key);
@@ -152,6 +156,7 @@ private:
 		mk.collision_enabled = collision_enabled;
 		mk.attractor_enabled = attractor_interaction_enabled;
 		mk.collision_scale = collision_scale;
+		mk.turbulence_active = turbulence_active;
 
 		return mk;
 	}
@@ -216,6 +221,17 @@ private:
 		StringName emission_ring_radius;
 		StringName emission_ring_inner_radius;
 
+		StringName turbulence_active;
+		StringName turbulence_noise_strength;
+		StringName turbulence_noise_scale;
+		StringName turbulence_noise_speed;
+		StringName turbulence_noise_speed_random;
+		StringName turbulence_influence_over_life;
+		StringName turbulence_influence_min;
+		StringName turbulence_influence_max;
+		StringName turbulence_initial_displacement_min;
+		StringName turbulence_initial_displacement_max;
+
 		StringName gravity;
 
 		StringName lifetime_randomness;
@@ -243,6 +259,7 @@ private:
 
 	float params_min[PARAM_MAX];
 	float params_max[PARAM_MAX];
+	float params[PARAM_MAX];
 
 	Ref<Texture2D> tex_parameters[PARAM_MAX];
 	Color color;
@@ -265,6 +282,13 @@ private:
 
 	bool anim_loop = false;
 
+	bool turbulence_active;
+	Vector3 turbulence_noise_speed;
+	Ref<Texture2D> turbulence_color_ramp;
+	float turbulence_noise_strength = 0.0f;
+	float turbulence_noise_scale = 0.0f;
+	float turbulence_noise_speed_random = 0.0f;
+
 	Vector3 gravity;
 
 	double lifetime_randomness = 0.0;
@@ -340,6 +364,18 @@ public:
 	real_t get_emission_ring_inner_radius() const;
 	int get_emission_point_count() const;
 
+	void set_turbulence_active(bool p_turbulence_active);
+	void set_turbulence_noise_strength(float p_turbulence_noise_strength);
+	void set_turbulence_noise_scale(float p_turbulence_noise_scale);
+	void set_turbulence_noise_speed_random(float p_turbulence_noise_speed_random);
+	void set_turbulence_noise_speed(const Vector3 &p_turbulence_noise_speed);
+
+	bool get_turbulence_active() const;
+	float get_turbulence_noise_strength() const;
+	float get_turbulence_noise_scale() const;
+	float get_turbulence_noise_speed_random() const;
+	Vector3 get_turbulence_noise_speed() const;
+
 	void set_gravity(const Vector3 &p_gravity);
 	Vector3 get_gravity() const;