mirror of
https://github.com/godotengine/godot.git
synced 2025-01-30 21:33:18 +08:00
Merge pull request #80688 from DarioSamo/gpu-particles-motion-vectors
Add motion vector support for GPU 3D Particles
This commit is contained in:
commit
4b69e8be85
@ -67,6 +67,7 @@
|
||||
</member>
|
||||
<member name="draw_order" type="int" setter="set_draw_order" getter="get_draw_order" enum="GPUParticles3D.DrawOrder" default="0">
|
||||
Particle draw order. Uses [enum DrawOrder] values.
|
||||
[b]Note:[/b] [constant DRAW_ORDER_INDEX] is the only option that supports motion vectors for effects like TAA. It is suggested to use this draw order if the particles are opaque to fix ghosting artifacts.
|
||||
</member>
|
||||
<member name="draw_pass_1" type="Mesh" setter="set_draw_pass_mesh" getter="get_draw_pass_mesh">
|
||||
[Mesh] that is drawn for the first draw pass.
|
||||
|
@ -252,6 +252,7 @@ bool RenderForwardClustered::free(RID p_rid) {
|
||||
template <RenderForwardClustered::PassMode p_pass_mode, uint32_t p_color_pass_flags>
|
||||
void RenderForwardClustered::_render_list_template(RenderingDevice::DrawListID p_draw_list, RenderingDevice::FramebufferFormatID p_framebuffer_Format, RenderListParameters *p_params, uint32_t p_from_element, uint32_t p_to_element) {
|
||||
RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
|
||||
RendererRD::ParticlesStorage *particles_storage = RendererRD::ParticlesStorage::get_singleton();
|
||||
RD::DrawListID draw_list = p_draw_list;
|
||||
RD::FramebufferFormatID framebuffer_format = p_framebuffer_Format;
|
||||
|
||||
@ -477,7 +478,9 @@ void RenderForwardClustered::_render_list_template(RenderingDevice::DrawListID p
|
||||
prev_material_uniform_set = material_uniform_set;
|
||||
}
|
||||
|
||||
if ((surf->owner->base_flags & (INSTANCE_DATA_FLAG_MULTIMESH | INSTANCE_DATA_FLAG_PARTICLES)) == INSTANCE_DATA_FLAG_MULTIMESH) {
|
||||
if (surf->owner->base_flags & INSTANCE_DATA_FLAG_PARTICLES) {
|
||||
particles_storage->particles_get_instance_buffer_motion_vectors_offsets(surf->owner->data->base, push_constant.multimesh_motion_vectors_current_offset, push_constant.multimesh_motion_vectors_previous_offset);
|
||||
} else if (surf->owner->base_flags & INSTANCE_DATA_FLAG_MULTIMESH) {
|
||||
mesh_storage->_multimesh_get_motion_vectors_offsets(surf->owner->data->base, push_constant.multimesh_motion_vectors_current_offset, push_constant.multimesh_motion_vectors_previous_offset);
|
||||
} else {
|
||||
push_constant.multimesh_motion_vectors_current_offset = 0;
|
||||
@ -3716,6 +3719,10 @@ void RenderForwardClustered::_geometry_instance_update(RenderGeometryInstance *p
|
||||
// Particles haven't been cleared or updated, update once now to ensure they are ready to render.
|
||||
particles_storage->update_particles();
|
||||
}
|
||||
|
||||
if (ginstance->data->dirty_dependencies) {
|
||||
particles_storage->particles_update_dependency(ginstance->data->base, &ginstance->data->dependency_tracker);
|
||||
}
|
||||
} else if (ginstance->data->base_type == RS::INSTANCE_MESH) {
|
||||
if (mesh_storage->skeleton_is_valid(ginstance->data->skeleton)) {
|
||||
ginstance->transforms_uniform_set = mesh_storage->skeleton_get_3d_uniform_set(ginstance->data->skeleton, scene_shader.default_shader_rd, TRANSFORMS_UNIFORM_SET);
|
||||
@ -3755,6 +3762,7 @@ void RenderForwardClustered::_geometry_instance_dependency_changed(Dependency::D
|
||||
case Dependency::DEPENDENCY_CHANGED_MATERIAL:
|
||||
case Dependency::DEPENDENCY_CHANGED_MESH:
|
||||
case Dependency::DEPENDENCY_CHANGED_PARTICLES:
|
||||
case Dependency::DEPENDENCY_CHANGED_PARTICLES_INSTANCES:
|
||||
case Dependency::DEPENDENCY_CHANGED_MULTIMESH:
|
||||
case Dependency::DEPENDENCY_CHANGED_SKELETON_DATA: {
|
||||
static_cast<RenderGeometryInstance *>(p_tracker->userdata)->_mark_dirty();
|
||||
|
@ -2614,6 +2614,10 @@ void RenderForwardMobile::_geometry_instance_update(RenderGeometryInstance *p_ge
|
||||
// Particles haven't been cleared or updated, update once now to ensure they are ready to render.
|
||||
particles_storage->update_particles();
|
||||
}
|
||||
|
||||
if (ginstance->data->dirty_dependencies) {
|
||||
particles_storage->particles_update_dependency(ginstance->data->base, &ginstance->data->dependency_tracker);
|
||||
}
|
||||
} else if (ginstance->data->base_type == RS::INSTANCE_MESH) {
|
||||
if (mesh_storage->skeleton_is_valid(ginstance->data->skeleton)) {
|
||||
ginstance->transforms_uniform_set = mesh_storage->skeleton_get_3d_uniform_set(ginstance->data->skeleton, scene_shader.default_shader_rd, TRANSFORMS_UNIFORM_SET);
|
||||
|
@ -45,6 +45,9 @@ layout(set = 2, binding = 0, std430) restrict readonly buffer TrailBindPoses {
|
||||
}
|
||||
trail_bind_poses;
|
||||
|
||||
#define PARAMS_FLAG_ORDER_BY_LIFETIME 1
|
||||
#define PARAMS_FLAG_COPY_MODE_2D 2
|
||||
|
||||
layout(push_constant, std430) uniform Params {
|
||||
vec3 sort_direction;
|
||||
uint total_particles;
|
||||
@ -57,10 +60,10 @@ layout(push_constant, std430) uniform Params {
|
||||
vec3 align_up;
|
||||
uint align_mode;
|
||||
|
||||
bool order_by_lifetime;
|
||||
uint lifetime_split;
|
||||
bool lifetime_reverse;
|
||||
bool copy_mode_2d;
|
||||
uint motion_vectors_current_offset;
|
||||
uint flags;
|
||||
|
||||
mat4 inv_emission_transform;
|
||||
}
|
||||
@ -103,7 +106,7 @@ void main() {
|
||||
particle = uint(sort_buffer.data[particle].y); //use index from sort buffer
|
||||
}
|
||||
#else
|
||||
if (params.order_by_lifetime) {
|
||||
if (bool(params.flags & PARAMS_FLAG_ORDER_BY_LIFETIME)) {
|
||||
if (params.trail_size > 1) {
|
||||
uint limit = (params.total_particles / params.trail_size) - params.lifetime_split;
|
||||
|
||||
@ -201,7 +204,7 @@ void main() {
|
||||
txform = txform * trail_bind_poses.data[part_ofs];
|
||||
}
|
||||
|
||||
if (params.copy_mode_2d) {
|
||||
if (bool(params.flags & PARAMS_FLAG_COPY_MODE_2D)) {
|
||||
// In global mode, bring 2D particles to local coordinates
|
||||
// as they will be drawn with the node position as origin.
|
||||
txform = params.inv_emission_transform * txform;
|
||||
@ -213,15 +216,16 @@ void main() {
|
||||
}
|
||||
txform = transpose(txform);
|
||||
|
||||
if (params.copy_mode_2d) {
|
||||
uint write_offset = gl_GlobalInvocationID.x * (2 + 1 + 1); //xform + color + custom
|
||||
uint instance_index = gl_GlobalInvocationID.x + params.motion_vectors_current_offset;
|
||||
if (bool(params.flags & PARAMS_FLAG_COPY_MODE_2D)) {
|
||||
uint write_offset = instance_index * (2 + 1 + 1); //xform + color + custom
|
||||
|
||||
instances.data[write_offset + 0] = txform[0];
|
||||
instances.data[write_offset + 1] = txform[1];
|
||||
instances.data[write_offset + 2] = particles.data[particle].color;
|
||||
instances.data[write_offset + 3] = particles.data[particle].custom;
|
||||
} else {
|
||||
uint write_offset = gl_GlobalInvocationID.x * (3 + 1 + 1); //xform + color + custom
|
||||
uint write_offset = instance_index * (3 + 1 + 1); //xform + color + custom
|
||||
|
||||
instances.data[write_offset + 0] = txform[0];
|
||||
instances.data[write_offset + 1] = txform[1];
|
||||
|
@ -666,6 +666,19 @@ RID ParticlesStorage::particles_get_draw_pass_mesh(RID p_particles, int p_pass)
|
||||
return particles->draw_passes[p_pass];
|
||||
}
|
||||
|
||||
void ParticlesStorage::particles_update_dependency(RID p_particles, DependencyTracker *p_instance) {
|
||||
Particles *particles = particles_owner.get_or_null(p_particles);
|
||||
ERR_FAIL_COND(!particles);
|
||||
p_instance->update_dependency(&particles->dependency);
|
||||
}
|
||||
|
||||
void ParticlesStorage::particles_get_instance_buffer_motion_vectors_offsets(RID p_particles, uint32_t &r_current_offset, uint32_t &r_prev_offset) {
|
||||
Particles *particles = particles_owner.get_or_null(p_particles);
|
||||
ERR_FAIL_COND(!particles);
|
||||
r_current_offset = particles->instance_motion_vectors_current_offset;
|
||||
r_prev_offset = particles->instance_motion_vectors_previous_offset;
|
||||
}
|
||||
|
||||
void ParticlesStorage::particles_add_collision(RID p_particles, RID p_particles_collision_instance) {
|
||||
Particles *particles = particles_owner.get_or_null(p_particles);
|
||||
ERR_FAIL_COND(!particles);
|
||||
@ -1185,6 +1198,7 @@ void ParticlesStorage::particles_set_view_axis(RID p_particles, const Vector3 &p
|
||||
copy_push_constant.order_by_lifetime = (particles->draw_order == RS::PARTICLES_DRAW_ORDER_LIFETIME || particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME);
|
||||
copy_push_constant.lifetime_split = (MIN(int(particles->amount * particles->phase), particles->amount - 1) + 1) % particles->amount;
|
||||
copy_push_constant.lifetime_reverse = particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME;
|
||||
copy_push_constant.motion_vectors_current_offset = particles->instance_motion_vectors_current_offset;
|
||||
|
||||
copy_push_constant.frame_remainder = particles->interpolate ? particles->frame_remainder : 0.0;
|
||||
copy_push_constant.total_particles = particles->amount;
|
||||
@ -1252,28 +1266,50 @@ void ParticlesStorage::_particles_update_buffers(Particles *particles) {
|
||||
userdata_count = particle_shader_data->userdata_count;
|
||||
}
|
||||
|
||||
bool uses_motion_vectors = RSG::viewport->get_num_viewports_with_motion_vectors() > 0;
|
||||
bool index_draw_order = particles->draw_order == RS::ParticlesDrawOrder::PARTICLES_DRAW_ORDER_INDEX;
|
||||
bool enable_motion_vectors = uses_motion_vectors && index_draw_order && !particles->instance_motion_vectors_enabled;
|
||||
bool only_instances_changed = false;
|
||||
|
||||
if (userdata_count != particles->userdata_count) {
|
||||
// Mismatch userdata, re-create buffers.
|
||||
// Mismatch userdata, re-create all buffers.
|
||||
_particles_free_data(particles);
|
||||
} else if (enable_motion_vectors) {
|
||||
// Only motion vectors are required, release the transforms buffer and uniform set.
|
||||
if (particles->particle_instance_buffer.is_valid()) {
|
||||
RD::get_singleton()->free(particles->particle_instance_buffer);
|
||||
particles->particle_instance_buffer = RID();
|
||||
}
|
||||
|
||||
particles->particles_transforms_buffer_uniform_set = RID();
|
||||
only_instances_changed = true;
|
||||
} else if (!particles->particle_buffer.is_null()) {
|
||||
// No operation is required because a buffer already exists, return early.
|
||||
return;
|
||||
}
|
||||
|
||||
if (particles->amount > 0 && particles->particle_buffer.is_null()) {
|
||||
if (particles->amount > 0) {
|
||||
int total_amount = particles->amount;
|
||||
if (particles->trails_enabled && particles->trail_bind_poses.size() > 1) {
|
||||
total_amount *= particles->trail_bind_poses.size();
|
||||
}
|
||||
|
||||
uint32_t xform_size = particles->mode == RS::PARTICLES_MODE_2D ? 2 : 3;
|
||||
|
||||
particles->particle_buffer = RD::get_singleton()->storage_buffer_create((sizeof(ParticleData) + userdata_count * sizeof(float) * 4) * total_amount);
|
||||
|
||||
particles->userdata_count = userdata_count;
|
||||
if (particles->particle_buffer.is_null()) {
|
||||
particles->particle_buffer = RD::get_singleton()->storage_buffer_create((sizeof(ParticleData) + userdata_count * sizeof(float) * 4) * total_amount);
|
||||
particles->userdata_count = userdata_count;
|
||||
}
|
||||
|
||||
PackedByteArray data;
|
||||
data.resize_zeroed(sizeof(float) * 4 * (xform_size + 1 + 1) * total_amount);
|
||||
uint32_t particle_instance_buffer_size = total_amount * (xform_size + 1 + 1) * sizeof(float) * 4;
|
||||
if (uses_motion_vectors) {
|
||||
particle_instance_buffer_size *= 2;
|
||||
particles->instance_motion_vectors_enabled = true;
|
||||
}
|
||||
|
||||
particles->particle_instance_buffer = RD::get_singleton()->storage_buffer_create(sizeof(float) * 4 * (xform_size + 1 + 1) * total_amount, data);
|
||||
//needs to clear it
|
||||
data.resize_zeroed(particle_instance_buffer_size);
|
||||
|
||||
particles->particle_instance_buffer = RD::get_singleton()->storage_buffer_create(particle_instance_buffer_size, data);
|
||||
|
||||
{
|
||||
Vector<RD::Uniform> uniforms;
|
||||
@ -1295,9 +1331,20 @@ void ParticlesStorage::_particles_update_buffers(Particles *particles) {
|
||||
|
||||
particles->particles_copy_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, particles_shader.copy_shader.version_get_shader(particles_shader.copy_shader_version, 0), 0);
|
||||
}
|
||||
|
||||
particles->instance_motion_vectors_current_offset = 0;
|
||||
particles->instance_motion_vectors_previous_offset = 0;
|
||||
particles->instance_motion_vectors_last_change = -1;
|
||||
|
||||
if (only_instances_changed) {
|
||||
// Notify the renderer the instances uniform must be retrieved again, as it's the only element that has been changed because motion vectors were enabled.
|
||||
particles->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_PARTICLES_INSTANCES);
|
||||
}
|
||||
}
|
||||
}
|
||||
void ParticlesStorage::update_particles() {
|
||||
uint32_t frame = RSG::rasterizer->get_frame_number();
|
||||
bool uses_motion_vectors = RSG::viewport->get_num_viewports_with_motion_vectors() > 0;
|
||||
while (particle_update_list) {
|
||||
//use transform feedback to process particles
|
||||
|
||||
@ -1461,16 +1508,25 @@ void ParticlesStorage::update_particles() {
|
||||
// Ensure that memory is initialized (the code above should ensure that _particles_process is always called at least once upon clearing).
|
||||
DEV_ASSERT(!particles->clear);
|
||||
|
||||
int total_amount = particles->amount;
|
||||
if (particles->trails_enabled && particles->trail_bind_poses.size() > 1) {
|
||||
total_amount *= particles->trail_bind_poses.size();
|
||||
}
|
||||
|
||||
// Swap offsets for motion vectors. Motion vectors can only be used when the draw order keeps the indices consistent across frames.
|
||||
bool index_draw_order = particles->draw_order == RS::ParticlesDrawOrder::PARTICLES_DRAW_ORDER_INDEX;
|
||||
particles->instance_motion_vectors_previous_offset = particles->instance_motion_vectors_current_offset;
|
||||
if (uses_motion_vectors && index_draw_order && particles->instance_motion_vectors_enabled && (frame - particles->instance_motion_vectors_last_change) == 1) {
|
||||
particles->instance_motion_vectors_current_offset = total_amount - particles->instance_motion_vectors_current_offset;
|
||||
}
|
||||
|
||||
particles->instance_motion_vectors_last_change = frame;
|
||||
|
||||
// Copy particles to instance buffer.
|
||||
if (particles->draw_order != RS::PARTICLES_DRAW_ORDER_VIEW_DEPTH && particles->transform_align != RS::PARTICLES_TRANSFORM_ALIGN_Z_BILLBOARD && particles->transform_align != RS::PARTICLES_TRANSFORM_ALIGN_Z_BILLBOARD_Y_TO_VELOCITY) {
|
||||
//does not need view dependent operation, do copy here
|
||||
ParticlesShader::CopyPushConstant copy_push_constant;
|
||||
|
||||
int total_amount = particles->amount;
|
||||
if (particles->trails_enabled && particles->trail_bind_poses.size() > 1) {
|
||||
total_amount *= particles->trail_bind_poses.size();
|
||||
}
|
||||
|
||||
// Affect 2D only.
|
||||
if (particles->use_local_coords) {
|
||||
// In local mode, particle positions are calculated locally (relative to the node position)
|
||||
@ -1506,6 +1562,7 @@ void ParticlesStorage::update_particles() {
|
||||
copy_push_constant.order_by_lifetime = (particles->draw_order == RS::PARTICLES_DRAW_ORDER_LIFETIME || particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME);
|
||||
copy_push_constant.lifetime_split = (MIN(int(particles->amount * particles->phase), particles->amount - 1) + 1) % particles->amount;
|
||||
copy_push_constant.lifetime_reverse = particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME;
|
||||
copy_push_constant.motion_vectors_current_offset = particles->instance_motion_vectors_current_offset;
|
||||
|
||||
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
|
||||
copy_push_constant.copy_mode_2d = particles->mode == RS::PARTICLES_MODE_2D ? 1 : 0;
|
||||
|
@ -225,6 +225,11 @@ private:
|
||||
double frame_remainder = 0;
|
||||
real_t collision_base_size = 0.01;
|
||||
|
||||
uint32_t instance_motion_vectors_current_offset = 0;
|
||||
uint32_t instance_motion_vectors_previous_offset = 0;
|
||||
uint64_t instance_motion_vectors_last_change = -1;
|
||||
bool instance_motion_vectors_enabled = false;
|
||||
|
||||
bool clear = true;
|
||||
|
||||
bool force_sub_emit = false;
|
||||
@ -288,10 +293,13 @@ private:
|
||||
float align_up[3];
|
||||
uint32_t align_mode;
|
||||
|
||||
uint32_t order_by_lifetime;
|
||||
uint32_t lifetime_split;
|
||||
uint32_t lifetime_reverse;
|
||||
uint32_t copy_mode_2d;
|
||||
uint32_t motion_vectors_current_offset;
|
||||
struct {
|
||||
uint32_t order_by_lifetime : 1;
|
||||
uint32_t copy_mode_2d : 1;
|
||||
};
|
||||
|
||||
float inv_emission_transform[16];
|
||||
};
|
||||
@ -521,12 +529,15 @@ public:
|
||||
return particles->particles_transforms_buffer_uniform_set;
|
||||
}
|
||||
|
||||
void particles_get_instance_buffer_motion_vectors_offsets(RID p_particles, uint32_t &r_current_offset, uint32_t &r_prev_offset);
|
||||
|
||||
virtual void particles_add_collision(RID p_particles, RID p_particles_collision_instance) override;
|
||||
virtual void particles_remove_collision(RID p_particles, RID p_particles_collision_instance) override;
|
||||
void particles_set_canvas_sdf_collision(RID p_particles, bool p_enable, const Transform2D &p_xform, const Rect2 &p_to_screen, RID p_texture);
|
||||
|
||||
virtual void update_particles() override;
|
||||
|
||||
void particles_update_dependency(RID p_particles, DependencyTracker *p_instance);
|
||||
Dependency *particles_get_dependency(RID p_particles) const;
|
||||
|
||||
/* Particles Collision */
|
||||
|
@ -506,6 +506,9 @@ public:
|
||||
}
|
||||
|
||||
} break;
|
||||
default: {
|
||||
// Ignored notifications.
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,7 @@ public:
|
||||
DEPENDENCY_CHANGED_MULTIMESH,
|
||||
DEPENDENCY_CHANGED_MULTIMESH_VISIBLE_INSTANCES,
|
||||
DEPENDENCY_CHANGED_PARTICLES,
|
||||
DEPENDENCY_CHANGED_PARTICLES_INSTANCES,
|
||||
DEPENDENCY_CHANGED_DECAL,
|
||||
DEPENDENCY_CHANGED_SKELETON_DATA,
|
||||
DEPENDENCY_CHANGED_SKELETON_BONES,
|
||||
|
Loading…
Reference in New Issue
Block a user