godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
90.09k stars 21.15k forks source link

Converted `CPUParticles3D` to `GPUParticles3D` weird particle creation behavior #97621

Open FireCatMagic opened 1 week ago

FireCatMagic commented 1 week ago

Tested versions

4.4-dev2

System information

Godot v4.4.dev2 - Windows 10.0.19045 - OpenGL 3 (Compatibility) - NVIDIA GeForce RTX 4050 Laptop GPU (NVIDIA; 32.0.15.6070) - 13th Gen Intel(R) Core(TM) i5-13500HX (20 Threads)

Issue description

Here's a scene falling with beautiful "snow" using CPUParticles3D Godot_v4 4-dev2_win64_fQda594ACA

Here's what happens when the particles are converted to GPU particles. https://github.com/user-attachments/assets/ae589387-3adf-47e8-ba8b-ebdfa230d872

I'm not sure how to exactly describe it in words but when converting, it doesn't create the amount of particles it should on the default speed scale, and only actually creates the particles at a higher speed scale, and then resetting the speed scale to 1 the created particles fall as they should.

Steps to reproduce

Create a CPUParticles3D such as shown above, convert to GPUParticles3D, then see it doesn't create as many as it shou;d

Minimal reproduction project (MRP)

New Compressed (zipped) Folder.zip

ByHatcker commented 1 week ago

So you want to say that at first, after the transformation, particles are not created, but are created only when their velocity changes?

FireCatMagic commented 1 week ago

So you want to say that at first, after the transformation, particles are not created, but are created only when their velocity changes?

Not the velocity, but the speed scale of the whole system. And it seems like without the speed scale change, like 3 particles are created

ByHatcker commented 1 week ago

Very thanks! I'm sure that's where the problem lies:

void CPUParticles3D::convert_from_particles(Node *p_particles) {
        GPUParticles3D *gpu_particles = Object::cast_to<GPUParticles3D>(p_particles);
        ERR_FAIL_NULL_MSG(gpu_particles, "Only GPUParticles3D nodes can be converted to CPUParticles3D.");

        set_emitting(gpu_particles->is_emitting());
        set_amount(gpu_particles->get_amount());
        set_lifetime(gpu_particles->get_lifetime());
        set_one_shot(gpu_particles->get_one_shot());
        set_pre_process_time(gpu_particles->get_pre_process_time());
        set_explosiveness_ratio(gpu_particles->get_explosiveness_ratio());
        set_randomness_ratio(gpu_particles->get_randomness_ratio());
        set_visibility_aabb(gpu_particles->get_visibility_aabb());
        set_use_local_coordinates(gpu_particles->get_use_local_coordinates());
        set_fixed_fps(gpu_particles->get_fixed_fps());
        set_fractional_delta(gpu_particles->get_fractional_delta());
        set_speed_scale(gpu_particles->get_speed_scale());
        set_draw_order(DrawOrder(gpu_particles->get_draw_order()));
        set_mesh(gpu_particles->get_draw_pass_mesh(0));

        Ref<ParticleProcessMaterial> material = gpu_particles->get_process_material();
        if (material.is_null()) {
                return;
        }

        set_direction(material->get_direction());
        set_spread(material->get_spread());
        set_flatness(material->get_flatness());

        set_color(material->get_color());

        Ref<GradientTexture1D> gt = material->get_color_ramp();
        if (gt.is_valid()) {
                set_color_ramp(gt->get_gradient());
        }

        Ref<GradientTexture1D> gti = material->get_color_initial_ramp();
        if (gti.is_valid()) {
                set_color_initial_ramp(gti->get_gradient());
        }

        set_particle_flag(PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY, material->get_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY));
        set_particle_flag(PARTICLE_FLAG_ROTATE_Y, material->get_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_ROTATE_Y));
        set_particle_flag(PARTICLE_FLAG_DISABLE_Z, material->get_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_DISABLE_Z));

        set_emission_shape(EmissionShape(material->get_emission_shape()));
        set_emission_sphere_radius(material->get_emission_sphere_radius());
        set_emission_box_extents(material->get_emission_box_extents());
        Ref<CurveXYZTexture> scale3D = material->get_param_texture(ParticleProcessMaterial::PARAM_SCALE);
        if (scale3D.is_valid()) {
                split_scale = true;
                scale_curve_x = scale3D->get_curve_x();
                scale_curve_y = scale3D->get_curve_y();
                scale_curve_z = scale3D->get_curve_z();
        }

        set_gravity(material->get_gravity());
        set_lifetime_randomness(material->get_lifetime_randomness());

#define CONVERT_PARAM(m_param)                                                                  \
        set_param_min(m_param, material->get_param_min(ParticleProcessMaterial::m_param));          \
        {                                                                                           \
                Ref<CurveTexture> ctex = material->get_param_texture(ParticleProcessMaterial::m_param); \
                if (ctex.is_valid())                                                                    \
                        set_param_curve(m_param, ctex->get_curve());                                        \
        }                                                                                           \
        set_param_max(m_param, material->get_param_max(ParticleProcessMaterial::m_param));

        CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY);
        CONVERT_PARAM(PARAM_ANGULAR_VELOCITY);
        CONVERT_PARAM(PARAM_ORBIT_VELOCITY);
        CONVERT_PARAM(PARAM_LINEAR_ACCEL);
        CONVERT_PARAM(PARAM_RADIAL_ACCEL);
        CONVERT_PARAM(PARAM_TANGENTIAL_ACCEL);
        CONVERT_PARAM(PARAM_DAMPING);
        CONVERT_PARAM(PARAM_ANGLE);
        CONVERT_PARAM(PARAM_SCALE);
        CONVERT_PARAM(PARAM_HUE_VARIATION);
        CONVERT_PARAM(PARAM_ANIM_SPEED);
        CONVERT_PARAM(PARAM_ANIM_OFFSET);

#undef CONVERT_PARAM
}

I'll try to open a PR on this topic.

PrIzRaKDev commented 1 week ago

So you want to say that at first, after the transformation, particles are not created, but are created only when their velocity changes?

Not the velocity, but the speed scale of the whole system. And it seems like without the speed scale change, like 3 particles are created

My PR in moderation. Please, wait new version (if of course they accept the changes)

AtlaStar commented 1 week ago

The issue appears more complex than described, as reducing the speed scale back to 1 causes the particles to eventually stop being produced entirely. In fact, minimizing the window and then maximizing it after some time causes the particle count to be closer to what is expected, which then slowly tapers off until no particles are being generated.

This testing is all in editor, but is definitely a slightly different behavior than what is described here

AtlaStar commented 1 week ago

Further testing shows that the described behavior only occurs when the Fixed FPS is set to 0. Resetting it to a value other than 0 causes the particles to spawn in on switching as expected. As it appears that the default fixed FPS for CPU particles is 0, but GPU particles default to 30, this explains why the conversion would cause issues while creating a GPU particle node in the scene would not have the same behavior.

AtlaStar commented 1 week ago

Continued testing shows that turning off the materials "keep scale" setting on the billboard setting fixes the issue with 0 FPS