Repository: godot
Issue: Shader compilation error when defining the MASS property to a Particle ShaderMaterial
Labels: bug,good first issue,topic:shaders

### Tested versions

Reproducible in: 4.3.stable

### System information

Godot v4.3.stable - Pop!_OS 22.04 LTS - X11 - Vulkan (Forward+) - dedicated AMD Radeon RX 6750 XT (RADV NAVI22) - AMD Ryzen 5 2400G with Radeon Vega Graphics (8 Threads)

### Issue description

When writing a ShaderMaterial to a GPUParticles3D a compilation error occurs when defining the MASS built-in variable, either in the start() or process() function.

```
servers/rendering/renderer_rd/storage_rd/particles_storage.cpp:1699 - Condition "!particles_storage->particles_shader.shader.version_is_valid(version)" is true.
  servers/rendering/renderer_rd/shader_rd.cpp:293 - Error compiling Compute  shader, variant #0 ().
  servers/rendering/renderer_rd/shader_rd.cpp:294 - Failed parse:
  ERROR: 0:462: 'm_MASS' : undeclared identifier 
  ERROR: 0:462: '' : compilation terminated 
  ERROR: 2 compilation errors.  No code generated.
  
  

  servers/rendering/renderer_rd/shader_rd.cpp:297 - code:
     1 | 
     2 | #version 450
     3 | 
     4 | 
     5 | #define SAMPLERS_BINDING_FIRST_INDEX 3
     6 | 
     7 | #define START_CODE_USED
     8 | #define PROCESS_CODE_USED
     9 | #define RENDER_DRIVER_VULKAN
    10 | 
    11 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
    12 | 
    13 | #define SDF_MAX_LENGTH 16384.0
    14 | 
    15 | /* SET 0: GLOBAL DATA */
    16 | 
    17 | layout(set = 0, binding = SAMPLERS_BINDING_FIRST_INDEX + 0) uniform sampler SAMPLER_NEAREST_CLAMP;
    18 | layout(set = 0, binding = SAMPLERS_BINDING_FIRST_INDEX + 1) uniform sampler SAMPLER_LINEAR_CLAMP;
    19 | layout(set = 0, binding = SAMPLERS_BINDING_FIRST_INDEX + 2) uniform sampler SAMPLER_NEAREST_WITH_MIPMAPS_CLAMP;
    20 | layout(set = 0, binding = SAMPLERS_BINDING_FIRST_INDEX + 3) uniform sampler SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP;
    21 | layout(set = 0, binding = SAMPLERS_BINDING_FIRST_INDEX + 4) uniform sampler SAMPLER_NEAREST_WITH_MIPMAPS_ANISOTROPIC_CLAMP;
    22 | layout(set = 0, binding = SAMPLERS_BINDING_FIRST_INDEX + 5) uniform sampler SAMPLER_LINEAR_WITH_MIPMAPS_ANISOTROPIC_CLAMP;
    23 | layout(set = 0, binding = SAMPLERS_BINDING_FIRST_INDEX + 6) uniform sampler SAMPLER_NEAREST_REPEAT;
    24 | layout(set = 0, binding = SAMPLERS_BINDING_FIRST_INDEX + 7) uniform sampler SAMPLER_LINEAR_REPEAT;
    25 | layout(set = 0, binding = SAMPLERS_BINDING_FIRST_INDEX + 8) uniform sampler SAMPLER_NEAREST_WITH_MIPMAPS_REPEAT;
    26 | layout(set = 0, binding = SAMPLERS_BINDING_FIRST_INDEX + 9) uniform sampler SAMPLER_LINEAR_WITH_MIPMAPS_REPEAT;
    27 | layout(set = 0, binding = SAMPLERS_BINDING_FIRST_INDEX + 10) uniform sampler SAMPLER_NEAREST_WITH_MIPMAPS_ANISOTROPIC_REPEAT;
    28 | layout(set = 0, binding = SAMPLERS_BINDING_FIRST_INDEX + 11) uniform sampler SAMPLER_LINEAR_WITH_MIPMAPS_ANISOTROPIC_REPEAT;
    29 | 
    30 | layout(set = 0, binding = 2, std430) restrict readonly buffer GlobalShaderUniformData {
    31 | 	vec4 data[];
    32 | }
    33 | global_shader_uniforms;
    34 | 
    35 | /* Set 1: FRAME AND PARTICLE DATA */
    36 | 
    37 | 
    38 | 
    39 | #define MAX_ATTRACTORS 32
    40 | 
    41 | #define ATTRACTOR_TYPE_SPHERE 0
    42 | #define ATTRACTOR_TYPE_BOX 1
    43 | #define ATTRACTOR_TYPE_VECTOR_FIELD 2
    44 | 
    45 | struct Attractor {
    46 | 	mat4 transform;
    47 | 	vec3 extents; 
    48 | 	uint type;
    49 | 	uint texture_index; 
    50 | 	float strength;
    51 | 	float attenuation;
    52 | 	float directionality;
    53 | };
    54 | 
    55 | #define MAX_COLLIDERS 32
    56 | 
    57 | #define COLLIDER_TYPE_SPHERE 0
    58 | #define COLLIDER_TYPE_BOX 1
    59 | #define COLLIDER_TYPE_SDF 2
    60 | #define COLLIDER_TYPE_HEIGHT_FIELD 3
    61 | #define COLLIDER_TYPE_2D_SDF 4
    62 | 
    63 | struct Collider {
    64 | 	mat4 transform;
    65 | 	vec3 extents; 
    66 | 	uint type;
    67 | 
    68 | 	uint texture_index; 
    69 | 	float scale;
    70 | 	uint pad[2];
    71 | };
    72 | 
    73 | struct FrameParams {
    74 | 	bool emitting;
    75 | 	float system_phase;
    76 | 	float prev_system_phase;
    77 | 	uint cycle;
    78 | 
    79 | 	float explosiveness;
    80 | 	float randomness;
    81 | 	float time;
    82 | 	float delta;
    83 | 
    84 | 	uint frame;
    85 | 	float amount_ratio;
    86 | 	uint pad1;
    87 | 	uint pad2;
    88 | 
    89 | 	uint random_seed;
    90 | 	uint attractor_count;
    91 | 	uint collider_count;
    92 | 	float particle_size;
    93 | 
    94 | 	mat4 emission_transform;
    95 | 	vec3 emitter_velocity;
    96 | 	float interp_to_end;
    97 | 
    98 | 	Attractor attractors[MAX_ATTRACTORS];
    99 | 	Collider colliders[MAX_COLLIDERS];
   100 | };
   101 | 
   102 | layout(set = 1, binding = 0, std430) restrict buffer FrameHistory {
   103 | 	FrameParams data[];
   104 | }
   105 | frame_history;
   106 | 
   107 | #define PARTICLE_FLAG_ACTIVE uint(1)
   108 | #define PARTICLE_FLAG_STARTED uint(2)
   109 | #define PARTICLE_FLAG_TRAILED uint(4)
   110 | #define PARTICLE_FRAME_MASK uint(0xFFFF)
   111 | #define PARTICLE_FRAME_SHIFT uint(16)
   112 | 
   113 | struct ParticleData {
   114 | 	mat4 xform;
   115 | 	vec3 velocity;
   116 | 	uint flags;
   117 | 	vec4 color;
   118 | 	vec4 custom;
   119 | #ifdef USERDATA1_USED
   120 | 	vec4 userdata1;
   121 | #endif
   122 | #ifdef USERDATA2_USED
   123 | 	vec4 userdata2;
   124 | #endif
   125 | #ifdef USERDATA3_USED
   126 | 	vec4 userdata3;
   127 | #endif
   128 | #ifdef USERDATA4_USED
   129 | 	vec4 userdata4;
   130 | #endif
   131 | #ifdef USERDATA5_USED
   132 | 	vec4 userdata5;
   133 | #endif
   134 | #ifdef USERDATA6_USED
   135 | 	vec4 userdata6;
   136 | #endif
   137 | };
   138 | 
   139 | layout(set = 1, binding = 1, std430) restrict buffer Particles {
   140 | 	ParticleData data[];
   141 | }
   142 | particles;
   143 | 
   144 | #define EMISSION_FLAG_HAS_POSITION 1
   145 | #define EMISSION_FLAG_HAS_ROTATION_SCALE 2
   146 | #define EMISSION_FLAG_HAS_VELOCITY 4
   147 | #define EMISSION_FLAG_HAS_COLOR 8
   148 | #define EMISSION_FLAG_HAS_CUSTOM 16
   149 | 
   150 | struct ParticleEmission {
   151 | 	mat4 xform;
   152 | 	vec3 velocity;
   153 | 	uint flags;
   154 | 	vec4 color;
   155 | 	vec4 custom;
   156 | };
   157 | 
   158 | layout(set = 1, binding = 2, std430) restrict buffer SourceEmission {
   159 | 	int particle_count;
   160 | 	uint pad0;
   161 | 	uint pad1;
   162 | 	uint pad2;
   163 | 	ParticleEmission data[];
   164 | }
   165 | src_particles;
   166 | 
   167 | layout(set = 1, binding = 3, std430) restrict buffer DestEmission {
   168 | 	int particle_count;
   169 | 	int particle_max;
   170 | 	uint pad1;
   171 | 	uint pad2;
   172 | 	ParticleEmission data[];
   173 | }
   174 | dst_particles;
   175 | 
   176 | /* SET 2: COLLIDER/ATTRACTOR TEXTURES */
   177 | 
   178 | #define MAX_3D_TEXTURES 7
   179 | 
   180 | layout(set = 2, binding = 0) uniform texture3D sdf_vec_textures[MAX_3D_TEXTURES];
   181 | layout(set = 2, binding = 1) uniform texture2D height_field_texture;
   182 | 
   183 | /* SET 3: MATERIAL */
   184 | 
   185 | #ifdef MATERIAL_UNIFORMS_USED
   186 | layout(set = 3, binding = 0, std140) uniform MaterialUniforms{
   187 | 
   188 | 
   189 | } material;
   190 | #endif
   191 | 
   192 | layout(push_constant, std430) uniform Params {
   193 | 	float lifetime;
   194 | 	bool clear;
   195 | 	uint total_particles;
   196 | 	uint trail_size;
   197 | 	bool use_fractional_delta;
   198 | 	bool sub_emitter_mode;
   199 | 	bool can_emit;
   200 | 	bool trail_pass;
   201 | }
   202 | params;
   203 | 
   204 | uint hash(uint x) {
   205 | 	x = ((x >> uint(16)) ^ x) * uint(0x45d9f3b);
   206 | 	x = ((x >> uint(16)) ^ x) * uint(0x45d9f3b);
   207 | 	x = (x >> uint(16)) ^ x;
   208 | 	return x;
   209 | }
   210 | 
   211 | bool emit_subparticle(mat4 p_xform, vec3 p_velocity, vec4 p_color, vec4 p_custom, uint p_flags) {
   212 | 	if (!params.can_emit) {
   213 | 		return false;
   214 | 	}
   215 | 
   216 | 	bool valid = false;
   217 | 
   218 | 	int dst_index = atomicAdd(dst_particles.particle_count, 1);
   219 | 
   220 | 	if (dst_index >= dst_particles.particle_max) {
   221 | 		atomicAdd(dst_particles.particle_count, -1);
   222 | 		return false;
   223 | 	}
   224 | 
   225 | 	dst_particles.data[dst_index].xform = p_xform;
   226 | 	dst_particles.data[dst_index].velocity = p_velocity;
   227 | 	dst_particles.data[dst_index].color = p_color;
   228 | 	dst_particles.data[dst_index].custom = p_custom;
   229 | 	dst_particles.data[dst_index].flags = p_flags;
   230 | 
   231 | 	return true;
   232 | }
   233 | 
   234 | vec3 safe_normalize(vec3 direction) {
   235 | 	const float EPSILON = 0.001;
   236 | 	if (length(direction) < EPSILON) {
   237 | 		return vec3(0.0);
   238 | 	}
   239 | 	return normalize(direction);
   240 | }
   241 | 
   242 | 
   243 | void main() {
   244 | 	uint particle = gl_GlobalInvocationID.x;
   245 | 
   246 | 	if (params.trail_size > 1) {
   247 | 		if (params.trail_pass) {
   248 | 			if (particle >= params.total_particles * (params.trail_size - 1)) {
   249 | 				return;
   250 | 			}
   251 | 			particle += (particle / (params.trail_size - 1)) + 1;
   252 | 		} else {
   253 | 			if (particle >= params.total_particles) {
   254 | 				return;
   255 | 			}
   256 | 			particle *= params.trail_size;
   257 | 		}
   258 | 	}
   259 | 
   260 | 	if (particle >= params.total_particles * params.trail_size) {
   261 | 		return; 
   262 | 	}
   263 | 
   264 | 	uint index = particle / params.trail_size;
   265 | 	uint frame = (particle % params.trail_size);
   266 | 
   267 | #define FRAME frame_history.data[frame]
   268 | #define PARTICLE particles.data[particle]
   269 | 
   270 | 	bool apply_forces = true;
   271 | 	bool apply_velocity = true;
   272 | 	float local_delta = FRAME.delta;
   273 | 
   274 | 	float mass = 1.0;
   275 | 
   276 | 	bool restart = false;
   277 | 
   278 | 	bool restart_position = false;
   279 | 	bool restart_rotation_scale = false;
   280 | 	bool restart_velocity = false;
   281 | 	bool restart_color = false;
   282 | 	bool restart_custom = false;
   283 | 
   284 | 	if (params.clear) {
   285 | 		PARTICLE.color = vec4(1.0);
   286 | 		PARTICLE.custom = vec4(0.0);
   287 | 		PARTICLE.velocity = vec3(0.0);
   288 | 		PARTICLE.flags = 0;
   289 | 		PARTICLE.xform = mat4(
   290 | 				vec4(1.0, 0.0, 0.0, 0.0),
   291 | 				vec4(0.0, 1.0, 0.0, 0.0),
   292 | 				vec4(0.0, 0.0, 1.0, 0.0),
   293 | 				vec4(0.0, 0.0, 0.0, 1.0));
   294 | 	}
   295 | 
   296 | 	
   297 | 
   298 | 	if (params.trail_pass) {
   299 | 		
   300 | 		uint src_idx = index * params.trail_size;
   301 | 		if (bool(particles.data[src_idx].flags & PARTICLE_FLAG_STARTED)) {
   302 | 			
   303 | 			PARTICLE.color = particles.data[src_idx].color;
   304 | 			PARTICLE.custom = particles.data[src_idx].custom;
   305 | 			PARTICLE.velocity = particles.data[src_idx].velocity;
   306 | 			PARTICLE.flags = PARTICLE_FLAG_TRAILED | ((frame_history.data[0].frame & PARTICLE_FRAME_MASK) << PARTICLE_FRAME_SHIFT); 
   307 | 			PARTICLE.xform = particles.data[src_idx].xform;
   308 | #ifdef USERDATA1_USED
   309 | 			PARTICLE.userdata1 = particles.data[src_idx].userdata1;
   310 | #endif
   311 | #ifdef USERDATA2_USED
   312 | 			PARTICLE.userdata2 = particles.data[src_idx].userdata2;
   313 | #endif
   314 | #ifdef USERDATA3_USED
   315 | 			PARTICLE.userdata3 = particles.data[src_idx].userdata3;
   316 | #endif
   317 | #ifdef USERDATA4_USED
   318 | 			PARTICLE.userdata4 = particles.data[src_idx].userdata4;
   319 | #endif
   320 | #ifdef USERDATA5_USED
   321 | 			PARTICLE.userdata5 = particles.data[src_idx].userdata5;
   322 | #endif
   323 | #ifdef USERDATA6_USED
   324 | 			PARTICLE.userdata6 = particles.data[src_idx].userdata6;
   325 | #endif
   326 | 		}
   327 | 		if (!bool(particles.data[src_idx].flags & PARTICLE_FLAG_ACTIVE)) {
   328 | 			
   329 | 			PARTICLE.flags = 0;
   330 | 			return;
   331 | 		}
   332 | 		if (bool(PARTICLE.flags & PARTICLE_FLAG_TRAILED) && ((PARTICLE.flags >> PARTICLE_FRAME_SHIFT) == (FRAME.frame & PARTICLE_FRAME_MASK))) { 
   333 | 			
   334 | 			PARTICLE.flags = PARTICLE_FLAG_ACTIVE | (particles.data[src_idx].flags & (PARTICLE_FRAME_MASK << PARTICLE_FRAME_SHIFT));
   335 | 			return; 
   336 | 		}
   337 | 
   338 | 	} else {
   339 | 		PARTICLE.flags &= ~PARTICLE_FLAG_STARTED;
   340 | 	}
   341 | 
   342 | 	bool collided = false;
   343 | 	vec3 collision_normal = vec3(0.0);
   344 | 	float collision_depth = 0.0;
   345 | 
   346 | 	vec3 attractor_force = vec3(0.0);
   347 | 
   348 | #if !defined(DISABLE_VELOCITY)
   349 | 
   350 | 	if (bool(PARTICLE.flags & PARTICLE_FLAG_ACTIVE)) {
   351 | 		PARTICLE.xform[3].xyz += PARTICLE.velocity * local_delta;
   352 | 	}
   353 | #endif
   354 | 
   355 | 	if (!params.trail_pass && params.sub_emitter_mode) {
   356 | 		if (!bool(PARTICLE.flags & PARTICLE_FLAG_ACTIVE)) {
   357 | 			int src_index = atomicAdd(src_particles.particle_count, -1) - 1;
   358 | 
   359 | 			if (src_index >= 0) {
   360 | 				PARTICLE.flags = (PARTICLE_FLAG_ACTIVE | PARTICLE_FLAG_STARTED | (FRAME.cycle << PARTICLE_FRAME_SHIFT));
   361 | 				restart = true;
   362 | 
   363 | 				if (bool(src_particles.data[src_index].flags & EMISSION_FLAG_HAS_POSITION)) {
   364 | 					PARTICLE.xform[3] = src_particles.data[src_index].xform[3];
   365 | 				} else {
   366 | 					PARTICLE.xform[3] = vec4(0, 0, 0, 1);
   367 | 					restart_position = true;
   368 | 				}
   369 | 				if (bool(src_particles.data[src_index].flags & EMISSION_FLAG_HAS_ROTATION_SCALE)) {
   370 | 					PARTICLE.xform[0] = src_particles.data[src_index].xform[0];
   371 | 					PARTICLE.xform[1] = src_particles.data[src_index].xform[1];
   372 | 					PARTICLE.xform[2] = src_particles.data[src_index].xform[2];
   373 | 				} else {
   374 | 					PARTICLE.xform[0] = vec4(1, 0, 0, 0);
   375 | 					PARTICLE.xform[1] = vec4(0, 1, 0, 0);
   376 | 					PARTICLE.xform[2] = vec4(0, 0, 1, 0);
   377 | 					restart_rotation_scale = true;
   378 | 				}
   379 | 				if (bool(src_particles.data[src_index].flags & EMISSION_FLAG_HAS_VELOCITY)) {
   380 | 					PARTICLE.velocity = src_particles.data[src_index].velocity;
   381 | 				} else {
   382 | 					PARTICLE.velocity = vec3(0);
   383 | 					restart_velocity = true;
   384 | 				}
   385 | 				if (bool(src_particles.data[src_index].flags & EMISSION_FLAG_HAS_COLOR)) {
   386 | 					PARTICLE.color = src_particles.data[src_index].color;
   387 | 				} else {
   388 | 					PARTICLE.color = vec4(1);
   389 | 					restart_color = true;
   390 | 				}
   391 | 
   392 | 				if (bool(src_particles.data[src_index].flags & EMISSION_FLAG_HAS_CUSTOM)) {
   393 | 					PARTICLE.custom = src_particles.data[src_index].custom;
   394 | 				} else {
   395 | 					PARTICLE.custom = vec4(0);
   396 | 					restart_custom = true;
   397 | 				}
   398 | 			}
   399 | 		}
   400 | 
   401 | 	} else if (FRAME.emitting) {
   402 | 		float restart_phase = float(index) / float(params.total_particles);
   403 | 
   404 | 		if (FRAME.randomness > 0.0) {
   405 | 			uint seed = FRAME.cycle;
   406 | 			if (restart_phase >= FRAME.system_phase) {
   407 | 				seed -= uint(1);
   408 | 			}
   409 | 			seed *= uint(params.total_particles);
   410 | 			seed += uint(index);
   411 | 			float random = float(hash(seed) % uint(65536)) / 65536.0;
   412 | 			restart_phase += FRAME.randomness * random * 1.0 / float(params.total_particles);
   413 | 		}
   414 | 
   415 | 		restart_phase *= (1.0 - FRAME.explosiveness);
   416 | 
   417 | 		if (FRAME.system_phase > FRAME.prev_system_phase) {
   418 | 			
   419 | 
   420 | 			if (restart_phase >= FRAME.prev_system_phase && restart_phase < FRAME.system_phase) {
   421 | 				restart = true;
   422 | 				if (params.use_fractional_delta) {
   423 | 					local_delta = (FRAME.system_phase - restart_phase) * params.lifetime;
   424 | 				}
   425 | 			}
   426 | 
   427 | 		} else if (FRAME.delta > 0.0) {
   428 | 			if (restart_phase >= FRAME.prev_system_phase) {
   429 | 				restart = true;
   430 | 				if (params.use_fractional_delta) {
   431 | 					local_delta = (1.0 - restart_phase + FRAME.system_phase) * params.lifetime;
   432 | 				}
   433 | 
   434 | 			} else if (restart_phase < FRAME.system_phase) {
   435 | 				restart = true;
   436 | 				if (params.use_fractional_delta) {
   437 | 					local_delta = (FRAME.system_phase - restart_phase) * params.lifetime;
   438 | 				}
   439 | 			}
   440 | 		}
   441 | 
   442 | 		if (params.trail_pass) {
   443 | 			restart = false;
   444 | 		}
   445 | 
   446 | 		if (restart) {
   447 | 			PARTICLE.flags = FRAME.emitting ? (PARTICLE_FLAG_ACTIVE | PARTICLE_FLAG_STARTED | (FRAME.cycle << PARTICLE_FRAME_SHIFT)) : 0;
   448 | 			restart_position = true;
   449 | 			restart_rotation_scale = true;
   450 | 			restart_velocity = true;
   451 | 			restart_color = true;
   452 | 			restart_custom = true;
   453 | 		}
   454 | 	}
   455 | 
   456 | 	bool particle_active = bool(PARTICLE.flags & PARTICLE_FLAG_ACTIVE);
   457 | 
   458 | 	uint particle_number = (PARTICLE.flags >> PARTICLE_FRAME_SHIFT) * uint(params.total_particles) + index;
   459 | 
   460 | 	if (restart && particle_active) {
   461 | 	{
   462 | 		m_MASS=1.0;
   463 | 		PARTICLE.xform[3].xyz=vec3(0.0,0.0,0.0);
   464 | 		PARTICLE.velocity.x=2.0;
   465 | 	}
   466 | 	}
   467 | 
   468 | 	if (particle_active) {
   469 | 		for (uint i = 0; i < FRAME.attractor_count; i++) {
   470 | 			vec3 dir;
   471 | 			float amount;
   472 | 			vec3 rel_vec = PARTICLE.xform[3].xyz - FRAME.attractors[i].transform[3].xyz;
   473 | 			vec3 local_pos = rel_vec * mat3(FRAME.attractors[i].transform);
   474 | 
   475 | 			switch (FRAME.attractors[i].type) {
   476 | 				case ATTRACTOR_TYPE_SPHERE: {
   477 | 					dir = safe_normalize(rel_vec);
   478 | 					float d = length(local_pos) / FRAME.attractors[i].extents.x;
   479 | 					if (d > 1.0) {
   480 | 						continue;
   481 | 					}
   482 | 					amount = max(0.0, 1.0 - d);
   483 | 				} break;
   484 | 				case ATTRACTOR_TYPE_BOX: {
   485 | 					dir = safe_normalize(rel_vec);
   486 | 
   487 | 					vec3 abs_pos = abs(local_pos / FRAME.attractors[i].extents);
   488 | 					float d = max(abs_pos.x, max(abs_pos.y, abs_pos.z));
   489 | 					if (d > 1.0) {
   490 | 						continue;
   491 | 					}
   492 | 					amount = max(0.0, 1.0 - d);
   493 | 
   494 | 				} break;
   495 | 				case ATTRACTOR_TYPE_VECTOR_FIELD: {
   496 | 					vec3 uvw_pos = (local_pos / FRAME.attractors[i].extents + 1.0) * 0.5;
   497 | 					if (any(lessThan(uvw_pos, vec3(0.0))) || any(greaterThan(uvw_pos, vec3(1.0)))) {
   498 | 						continue;
   499 | 					}
   500 | 					vec3 s = texture(sampler3D(sdf_vec_textures[FRAME.attractors[i].texture_index], SAMPLER_LINEAR_CLAMP), uvw_pos).xyz * -2.0 + 1.0;
   501 | 					dir = mat3(FRAME.attractors[i].transform) * safe_normalize(s); 
   502 | 					amount = length(s);
   503 | 
   504 | 				} break;
   505 | 			}
   506 | 			amount = pow(amount, FRAME.attractors[i].attenuation);
   507 | 			dir = safe_normalize(mix(dir, FRAME.attractors[i].transform[2].xyz, FRAME.attractors[i].directionality));
   508 | 			attractor_force -= amount * dir * FRAME.attractors[i].strength;
   509 | 		}
   510 | 
   511 | 		float particle_size = FRAME.particle_size;
   512 | 
   513 | #ifdef USE_COLLISION_SCALE
   514 | 
   515 | 		particle_size *= dot(vec3(length(PARTICLE.xform[0].xyz), length(PARTICLE.xform[1].xyz), length(PARTICLE.xform[2].xyz)), vec3(0.33333333333));
   516 | 
   517 | #endif
   518 | 
   519 | 		if (FRAME.collider_count == 1 && FRAME.colliders[0].type == COLLIDER_TYPE_2D_SDF) {
   520 | 			
   521 | 
   522 | 			vec2 pos = PARTICLE.xform[3].xy;
   523 | 			vec4 to_sdf_x = FRAME.colliders[0].transform[0];
   524 | 			vec4 to_sdf_y = FRAME.colliders[0].transform[1];
   525 | 			vec2 sdf_pos = vec2(dot(vec4(pos, 0, 1), to_sdf_x), dot(vec4(pos, 0, 1), to_sdf_y));
   526 | 
   527 | 			vec4 sdf_to_screen = vec4(FRAME.colliders[0].extents, FRAME.colliders[0].scale);
   528 | 
   529 | 			vec2 uv_pos = sdf_pos * sdf_to_screen.xy + sdf_to_screen.zw;
   530 | 
   531 | 			if (all(greaterThan(uv_pos, vec2(0.0))) && all(lessThan(uv_pos, vec2(1.0)))) {
   532 | 				vec2 pos2 = pos + vec2(0, particle_size);
   533 | 				vec2 sdf_pos2 = vec2(dot(vec4(pos2, 0, 1), to_sdf_x), dot(vec4(pos2, 0, 1), to_sdf_y));
   534 | 				float sdf_particle_size = distance(sdf_pos, sdf_pos2);
   535 | 
   536 | 				float d = texture(sampler2D(height_field_texture, SAMPLER_LINEAR_CLAMP), uv_pos).r * SDF_MAX_LENGTH;
   537 | 
   538 | 				d -= sdf_particle_size;
   539 | 
   540 | 				if (d < 0.0) {
   541 | 					const float EPSILON = 0.001;
   542 | 					vec2 n = normalize(vec2(
   543 | 							texture(sampler2D(height_field_texture, SAMPLER_LINEAR_CLAMP), uv_pos + vec2(EPSILON, 0.0)).r - texture(sampler2D(height_field_texture, SAMPLER_LINEAR_CLAMP), uv_pos - vec2(EPSILON, 0.0)).r,
   544 | 							texture(sampler2D(height_field_texture, SAMPLER_LINEAR_CLAMP), uv_pos + vec2(0.0, EPSILON)).r - texture(sampler2D(height_field_texture, SAMPLER_LINEAR_CLAMP), uv_pos - vec2(0.0, EPSILON)).r));
   545 | 
   546 | 					collided = true;
   547 | 					sdf_pos2 = sdf_pos + n * d;
   548 | 					pos2 = vec2(dot(vec4(sdf_pos2, 0, 1), FRAME.colliders[0].transform[2]), dot(vec4(sdf_pos2, 0, 1), FRAME.colliders[0].transform[3]));
   549 | 
   550 | 					n = pos - pos2;
   551 | 
   552 | 					collision_normal = normalize(vec3(n, 0.0));
   553 | 					collision_depth = length(n);
   554 | 				}
   555 | 			}
   556 | 
   557 | 		} else {
   558 | 			for (uint i = 0; i < FRAME.collider_count; i++) {
   559 | 				vec3 normal;
   560 | 				float depth;
   561 | 				bool col = false;
   562 | 
   563 | 				vec3 rel_vec = PARTICLE.xform[3].xyz - FRAME.colliders[i].transform[3].xyz;
   564 | 				vec3 local_pos = rel_vec * mat3(FRAME.colliders[i].transform);
   565 | 
   566 | 				
   567 | 				const float EPSILON = 0.001;
   568 | 				switch (FRAME.colliders[i].type) {
   569 | 					case COLLIDER_TYPE_SPHERE: {
   570 | 						float d = length(rel_vec) - (particle_size + FRAME.colliders[i].extents.x);
   571 | 
   572 | 						if (d <= EPSILON) {
   573 | 							col = true;
   574 | 							depth = -d;
   575 | 							normal = normalize(rel_vec);
   576 | 						}
   577 | 
   578 | 					} break;
   579 | 					case COLLIDER_TYPE_BOX: {
   580 | 						vec3 abs_pos = abs(local_pos);
   581 | 						vec3 sgn_pos = sign(local_pos);
   582 | 
   583 | 						if (any(greaterThan(abs_pos, FRAME.colliders[i].extents))) {
   584 | 							
   585 | 
   586 | 							vec3 closest = min(abs_pos, FRAME.colliders[i].extents);
   587 | 							vec3 rel = abs_pos - closest;
   588 | 							depth = length(rel) - particle_size;
   589 | 							if (depth <= EPSILON) {
   590 | 								col = true;
   591 | 								normal = mat3(FRAME.colliders[i].transform) * (normalize(rel) * sgn_pos);
   592 | 								depth = -depth;
   593 | 							}
   594 | 						} else {
   595 | 							
   596 | 							vec3 axis_len = FRAME.colliders[i].extents - abs_pos;
   597 | 							
   598 | 							if (all(lessThan(axis_len.xx, axis_len.yz))) {
   599 | 								normal = vec3(1, 0, 0);
   600 | 							} else if (all(lessThan(axis_len.yy, axis_len.xz))) {
   601 | 								normal = vec3(0, 1, 0);
   602 | 							} else {
   603 | 								normal = vec3(0, 0, 1);
   604 | 							}
   605 | 
   606 | 							col = true;
   607 | 							depth = dot(normal * axis_len, vec3(1)) + particle_size;
   608 | 							normal = mat3(FRAME.colliders[i].transform) * (normal * sgn_pos);
   609 | 						}
   610 | 
   611 | 					} break;
   612 | 					case COLLIDER_TYPE_SDF: {
   613 | 						vec3 apos = abs(local_pos);
   614 | 						float extra_dist = 0.0;
   615 | 						if (any(greaterThan(apos, FRAME.colliders[i].extents))) { 
   616 | 							vec3 mpos = min(apos, FRAME.colliders[i].extents);
   617 | 							extra_dist = distance(mpos, apos);
   618 | 						}
   619 | 
   620 | 						if (extra_dist > particle_size) {
   621 | 							continue;
   622 | 						}
   623 | 
   624 | 						vec3 uvw_pos = (local_pos / FRAME.colliders[i].extents) * 0.5 + 0.5;
   625 | 						float s = texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], SAMPLER_LINEAR_CLAMP), uvw_pos).r;
   626 | 						s *= FRAME.colliders[i].scale;
   627 | 						s += extra_dist;
   628 | 						if (s <= particle_size + EPSILON) {
   629 | 							col = true;
   630 | 							depth = particle_size - s;
   631 | 
   632 | 							normal = mat3(FRAME.colliders[i].transform) *
   633 | 									normalize(
   634 | 											vec3(
   635 | 													texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], SAMPLER_LINEAR_CLAMP), uvw_pos + vec3(EPSILON, 0.0, 0.0)).r - texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], SAMPLER_LINEAR_CLAMP), uvw_pos - vec3(EPSILON, 0.0, 0.0)).r,
   636 | 													texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], SAMPLER_LINEAR_CLAMP), uvw_pos + vec3(0.0, EPSILON, 0.0)).r - texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], SAMPLER_LINEAR_CLAMP), uvw_pos - vec3(0.0, EPSILON, 0.0)).r,
   637 | 													texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], SAMPLER_LINEAR_CLAMP), uvw_pos + vec3(0.0, 0.0, EPSILON)).r - texture(sampler3D(sdf_vec_textures[FRAME.colliders[i].texture_index], SAMPLER_LINEAR_CLAMP), uvw_pos - vec3(0.0, 0.0, EPSILON)).r));
   638 | 						}
   639 | 
   640 | 					} break;
   641 | 					case COLLIDER_TYPE_HEIGHT_FIELD: {
   642 | 						vec3 local_pos_bottom = local_pos;
   643 | 						local_pos_bottom.y -= particle_size;
   644 | 
   645 | 						if (any(greaterThan(abs(local_pos_bottom), FRAME.colliders[i].extents))) {
   646 | 							continue;
   647 | 						}
   648 | 						const float DELTA = 1.0 / 8192.0;
   649 | 
   650 | 						vec3 uvw_pos = vec3(local_pos_bottom / FRAME.colliders[i].extents) * 0.5 + 0.5;
   651 | 
   652 | 						float y = texture(sampler2D(height_field_texture, SAMPLER_LINEAR_CLAMP), uvw_pos.xz).r;
   653 | 
   654 | 						if (y + EPSILON >= uvw_pos.y) {
   655 | 							
   656 | 
   657 | 							vec3 pos1 = (vec3(uvw_pos.x, y, uvw_pos.z) * 2.0 - 1.0) * FRAME.colliders[i].extents;
   658 | 							vec3 pos2 = (vec3(uvw_pos.x + DELTA, texture(sampler2D(height_field_texture, SAMPLER_LINEAR_CLAMP), uvw_pos.xz + vec2(DELTA, 0)).r, uvw_pos.z) * 2.0 - 1.0) * FRAME.colliders[i].extents;
   659 | 							vec3 pos3 = (vec3(uvw_pos.x, texture(sampler2D(height_field_texture, SAMPLER_LINEAR_CLAMP), uvw_pos.xz + vec2(0, DELTA)).r, uvw_pos.z + DELTA) * 2.0 - 1.0) * FRAME.colliders[i].extents;
   660 | 
   661 | 							normal = normalize(cross(pos1 - pos2, pos1 - pos3));
   662 | 							float local_y = (vec3(local_pos / FRAME.colliders[i].extents) * 0.5 + 0.5).y;
   663 | 
   664 | 							col = true;
   665 | 							depth = dot(normal, pos1) - dot(normal, local_pos_bottom);
   666 | 						}
   667 | 
   668 | 					} break;
   669 | 				}
   670 | 
   671 | 				if (col) {
   672 | 					if (!collided) {
   673 | 						collided = true;
   674 | 						collision_normal = normal;
   675 | 						collision_depth = depth;
   676 | 					} else {
   677 | 						vec3 c = collision_normal * collision_depth;
   678 | 						c += normal * max(0.0, depth - dot(normal, c));
   679 | 						collision_normal = normalize(c);
   680 | 						collision_depth = length(c);
   681 | 					}
   682 | 				}
   683 | 			}
   684 | 		}
   685 | 	}
   686 | 
   687 | 	if (particle_active) {
   688 | 	{
   689 | 	}
   690 | 	}
   691 | 
   692 | 	PARTICLE.flags &= ~PARTICLE_FLAG_ACTIVE;
   693 | 	if (particle_active) {
   694 | 		PARTICLE.flags |= PARTICLE_FLAG_ACTIVE;
   695 | 	}
   696 | }
   697 | 
   698 | 
  servers/rendering/rendering_device.cpp:4304 - Parameter "pipeline" is null.
  This compute pipeline requires (0) bytes of push constant data, supplied: (32)
  No compute pipeline was set before attempting to draw.

 
***
The last three lines above repeat forever.
***
  ```

### Steps to reproduce

Set the Process Material to a GPUParticles3D to a ShaderMaterial, write in the start() function of the shader script the following:
  ```
void start() {
	// Called when a particle is spawned.
	MASS = 1.0;
	TRANSFORM[3].xyz = vec3(0.0);
	VELOCITY.x = 2.0;
}
  ```

### Minimal reproduction project (MRP)

[particle-shadermaterial.zip](https://github.com/user-attachments/files/18547694/particle-shadermaterial.zip)