Skip to content

Commit f7eb1d6

Browse files
committed
Implement a particle trail system
1 parent 86d6887 commit f7eb1d6

File tree

7 files changed

+197
-97
lines changed

7 files changed

+197
-97
lines changed

RtxOptions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,7 @@ Tables below enumerate all the options and their defaults set by RTX Remix. Note
502502
|rtx.particles.globalPreset.collisionRestitution|float|0.5|The fraction of velocity retained after a collision with scene geometry\. 1\.0 = perfectly elastic \(no speed loss\), 0\.0 = completely inelastic \(velocity zeroed\)\. Values outside \[0,1\] will be clamped to this range\.|
503503
|rtx.particles.globalPreset.collisionThickness|float|5|The maximum penetration depth \(in world units\) at which a particle will still collide with geometry\. Particles that penetrate deeper than this value are considered to have passed through thin objects and will not collide\.|
504504
|rtx.particles.globalPreset.enableCollisionDetection|bool|False|Enables particle collisions with the world\.|
505+
|rtx.particles.globalPreset.enableMotionTrail|bool|False|Elongates the particle with respect to velocity, texture edges are preserved, with only the center being stretched which provides a motion blur like effect on the particles themselves\. This will automatically align particles rotation with their individual velocitys \(similar to rtx\.particles\.globalPreset\.alignParticlesToVelocity\) and so rotation parameters are no longer taken into account when this setting is enabled\.|
505506
|rtx.particles.globalPreset.gravityForce|float|-0.5|Net influence of gravity acting on each particle \(meters per second squared\)\.|
506507
|rtx.particles.globalPreset.initialVelocityConeAngleDegrees|float|0|Specifies the half angle, in degrees, of the random emission cone around the triangles surface normal when spawning a new particle\. A value in the range of 0 to 180 degrees is expected\.|
507508
|rtx.particles.globalPreset.initialVelocityFromNormal|float|10|Initial speed to apply on spawn \(units/sec\) along the normal vector of the spawning triangle\.|
@@ -514,6 +515,7 @@ Tables below enumerate all the options and their defaults set by RTX Remix. Note
514515
|rtx.particles.globalPreset.minParticleSize|float|1|Minimum size \(in world units\) to give to a particle when spawned\.|
515516
|rtx.particles.globalPreset.minRotationSpeed|float|0.1|Minimum rotation speed \(in revolutions per second\) to give to a particle when spawned\.|
516517
|rtx.particles.globalPreset.minSpawnColor|float4|1, 1, 1, 1|Minimum range of the color to tint a particle with when spawned\.|
518+
|rtx.particles.globalPreset.motionTrailMultiplier|float|1|When enableMotionTrail is set to enabled, this value can be used to increase \(or decrease\) the length of the tail artificially, which is determined by the velocity\. A value of 1 \(the default\) will ensure each particle is the exact size of the motion over the previous frame\. Values geater than 1 will increase that size linearly\. Likewise for smaller than 1\. 0 and below is an invalid value\.|
517519
|rtx.particles.globalPreset.numberOfParticlesPerMaterial|int|98304|Maximum number of particles to simulate per material simultaneously\. There is a performance consideration, lower numbers are more performant\. Ideal is to tune this number for your specific needs\.|
518520
|rtx.particles.globalPreset.spawnRatePerSecond|int|100|Number of particles \(per system\) to spawn per second on average\.|
519521
|rtx.particles.globalPreset.turbulenceAmplitude|float|5|How much turbulence influences the force of a particle\.|

src/dxvk/rtx_render/rtx_mod_usd.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,12 +586,14 @@ bool UsdMod::Impl::processParticleSystem(Args& args, const pxr::UsdPrim& prim) {
586586
READ_ATTR(float, TurbulenceAmplitude, turbulenceAmplitude);
587587
READ_ATTR(float, CollisionThickness, collisionThickness);
588588
READ_ATTR(float, CollisionRestitution, collisionRestitution);
589+
READ_ATTR(float, MotionTrailMultiplier, motionTrailMultiplier);
589590
READ_ATTR(int, MaxNumParticles, maxNumParticles);
590591
READ_ATTR(bool, UseTurbulence, useTurbulence);
591592
READ_ATTR(bool, UseSpawnTexcoords, useSpawnTexcoords);
592593
READ_ATTR(bool, EnableCollisionDetection, enableCollisionDetection);
593594
READ_ATTR(bool, AlignParticlesToVelocity, alignParticlesToVelocity);
594-
READ_ATTR(bool, SpawnRatePerSecond, spawnRate);
595+
READ_ATTR(bool, EnableMotionTrail, enableMotionTrail);
596+
READ_ATTR(float, SpawnRatePerSecond, spawnRate);
595597
READ_ATTR_CONV(GfVec4f, MaxSpawnColor, maxSpawnColor, toFloat4);
596598
READ_ATTR_CONV(GfVec4f, MinSpawnColor, minSpawnColor, toFloat4);
597599

src/dxvk/rtx_render/rtx_particle_system.cpp

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ namespace dxvk {
105105
ImGui::DragFloat("Max Speed", &maxSpeedObject(), 0.01f, 0.f, 100000.f, "%.2f", ImGuiSliderFlags_AlwaysClamp);
106106

107107
ImGui::Checkbox("Align Particles with Velocity", &alignParticlesToVelocityObject());
108+
ImGui::Checkbox("Enable Motion Trail", &enableMotionTrailObject());
109+
ImGui::BeginDisabled(!enableMotionTrail());
110+
ImGui::DragFloat("Motion Trail Length Multiplier", &motionTrailMultiplierObject(), 0.01f, 0.001f, 10000.f, "%.2f", ImGuiSliderFlags_AlwaysClamp);
111+
ImGui::EndDisabled();
108112

109113
ImGui::Checkbox("Enable Particle World Collisions", &enableCollisionDetectionObject());
110114
ImGui::BeginDisabled(!enableCollisionDetection());
@@ -141,6 +145,7 @@ namespace dxvk {
141145
constants.upDirection.y = ctx->getSceneManager().getSceneUp().y;
142146
constants.upDirection.z = ctx->getSceneManager().getSceneUp().z;
143147
constants.deltaTimeSecs = GlobalTime::get().deltaTime() * timeScale();
148+
constants.invDeltaTimeSecs = 1.f / constants.deltaTimeSecs;
144149
constants.absoluteTimeSecs = GlobalTime::get().absoluteTimeMs() * 0.001f * timeScale();
145150
}
146151

@@ -172,6 +177,8 @@ namespace dxvk {
172177
desc.alignParticlesToVelocity = RtxParticleSystemManager::alignParticlesToVelocity() ? 1 : 0;
173178
desc.collisionRestitution = RtxParticleSystemManager::collisionRestitution();
174179
desc.collisionThickness = RtxParticleSystemManager::collisionThickness();
180+
desc.enableMotionTrail = RtxParticleSystemManager::enableMotionTrail() ? 1 : 0;
181+
desc.motionTrailMultiplier = RtxParticleSystemManager::motionTrailMultiplier();
175182
return desc;
176183
}
177184

@@ -198,7 +205,7 @@ namespace dxvk {
198205
uint32_t RtxParticleSystemManager::getNumberOfParticlesToSpawn(ParticleSystem* particleSystem, const DrawCallState& drawCallState) {
199206
ScopedCpuProfileZone();
200207

201-
float lambda = spawnRatePerSecond() * GlobalTime::get().deltaTime();
208+
float lambda = particleSystem->context.desc.spawnRate * GlobalTime::get().deltaTime();
202209

203210
// poisson dist wont work well with these values (inf loop)
204211
if (isnan(lambda) || lambda < 0.0f) {
@@ -423,6 +430,46 @@ namespace dxvk {
423430
}
424431
}
425432

433+
RtxParticleSystemManager::ParticleSystem::ParticleSystem(const RtxParticleSystemDesc& desc, const MaterialData& matData, const CategoryFlags& cats, const uint32_t seed) : context(desc)
434+
, materialData(matData)
435+
, categories(cats) {
436+
// Seed the RNG with a parameter from the manager, so we get unique random values for each particle system
437+
generator = std::default_random_engine(seed);
438+
// Store this hash since it cannot change now.
439+
// NOTE: This material data hash is stable within a run, but since hash depends on VK handles, it is not reliable across runs.
440+
m_cachedHash = materialData.getHash() ^ context.desc.calcHash();
441+
context.numVerticesPerParticle = getVerticesPerParticle();
442+
443+
// classic square billboard
444+
static const float2 offsets[4] = {
445+
float2(-0.5f, 0.5f),
446+
float2(0.5f, 0.5f),
447+
float2(-0.5f, -0.5f),
448+
float2(0.5f, -0.5f)
449+
};
450+
451+
// motion trail - first 4 are "head", last 4 are "tail"
452+
static const float2 offsetsMotionTrail[8] = {
453+
// TAIL quad (fixed)
454+
float2(-0.5f, -0.5f),
455+
float2(-0.5f, 0.0f),
456+
float2( 0.5f, -0.5f),
457+
float2( 0.5f, 0.0f),
458+
459+
// HEAD quad (stretched)
460+
float2(-0.5f, 0.0f),
461+
float2(-0.5f, 0.5f),
462+
float2( 0.5f, 0.0f),
463+
float2( 0.5f, 0.5f)
464+
};
465+
466+
if (desc.enableMotionTrail) {
467+
memcpy(&context.particleVertexOffsets[0], &offsetsMotionTrail[0], sizeof(offsetsMotionTrail));
468+
} else {
469+
memcpy(&context.particleVertexOffsets[0], &offsets[0], sizeof(offsets));
470+
}
471+
}
472+
426473
void RtxParticleSystemManager::ParticleSystem::allocStaticBuffers(DxvkContext* ctx) {
427474
ScopedCpuProfileZone();
428475

@@ -446,11 +493,11 @@ namespace dxvk {
446493
ctx->clearBuffer(m_particles, 0, info.size, 0);
447494
}
448495

449-
if (m_vb == nullptr || m_vb->info().size != sizeof(ParticleVertex) * context.desc.maxNumParticles * 4) {
496+
if (m_vb == nullptr || m_vb->info().size != sizeof(ParticleVertex) * getVertexCount()) {
450497
const Rc<DxvkDevice>& device = ctx->getDevice();
451498

452499
DxvkBufferCreateInfo info;
453-
info.size = sizeof(ParticleVertex) * context.desc.maxNumParticles * 4;
500+
info.size = sizeof(ParticleVertex) * getVertexCount();
454501
info.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
455502
| VK_BUFFER_USAGE_TRANSFER_DST_BIT
456503
| VK_BUFFER_USAGE_TRANSFER_SRC_BIT
@@ -465,27 +512,48 @@ namespace dxvk {
465512
ctx->clearBuffer(m_vb, 0, info.size, 0);
466513
}
467514

468-
if (m_ib == nullptr || m_ib->info().size != sizeof(uint32_t) * context.desc.maxNumParticles * 6) {
515+
if (m_ib == nullptr || m_ib->info().size != sizeof(uint32_t) * getIndexCount()) {
469516
const Rc<DxvkDevice>& device = ctx->getDevice();
470517

471518
DxvkBufferCreateInfo info;
472-
info.size = sizeof(uint32_t) * context.desc.maxNumParticles * 6;
519+
info.size = sizeof(uint32_t) * getIndexCount();
473520
info.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
474-
| VK_BUFFER_USAGE_TRANSFER_DST_BIT
475-
| VK_BUFFER_USAGE_TRANSFER_SRC_BIT
476-
| VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR;
521+
| VK_BUFFER_USAGE_TRANSFER_DST_BIT
522+
| VK_BUFFER_USAGE_TRANSFER_SRC_BIT
523+
| VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR;
477524
info.stages = VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR;
478525
info.access = VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT;
479526
m_ib = device->createBuffer(info, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, DxvkMemoryStats::Category::RTXBuffer, "RTX Particles - Index Buffer");
480527

481-
std::vector<uint32_t> indices(context.desc.maxNumParticles * 6);
528+
std::vector<uint32_t> indices(getIndexCount());
482529
for (int i = 0; i < context.desc.maxNumParticles; i++) {
483-
indices[i * 6 + 0] = i * 4 + 0;
484-
indices[i * 6 + 1] = i * 4 + 1;
485-
indices[i * 6 + 2] = i * 4 + 2;
486-
indices[i * 6 + 3] = i * 4 + 2;
487-
indices[i * 6 + 4] = i * 4 + 1;
488-
indices[i * 6 + 5] = i * 4 + 3;
530+
indices[i * getIndicesPerParticle() + 0] = i * getVerticesPerParticle() + 0;
531+
indices[i * getIndicesPerParticle() + 1] = i * getVerticesPerParticle() + 1;
532+
indices[i * getIndicesPerParticle() + 2] = i * getVerticesPerParticle() + 2;
533+
534+
indices[i * getIndicesPerParticle() + 3] = i * getVerticesPerParticle() + 2;
535+
indices[i * getIndicesPerParticle() + 4] = i * getVerticesPerParticle() + 1;
536+
indices[i * getIndicesPerParticle() + 5] = i * getVerticesPerParticle() + 3;
537+
}
538+
539+
if (context.desc.enableMotionTrail) {
540+
for (int i = 0; i < context.desc.maxNumParticles; i++) {
541+
indices[i * getIndicesPerParticle() + 6] = i * getVerticesPerParticle() + 1;
542+
indices[i * getIndicesPerParticle() + 7] = i * getVerticesPerParticle() + 4;
543+
indices[i * getIndicesPerParticle() + 8] = i * getVerticesPerParticle() + 3;
544+
545+
indices[i * getIndicesPerParticle() + 9] = i * getVerticesPerParticle() + 3;
546+
indices[i * getIndicesPerParticle() + 10] = i * getVerticesPerParticle() + 4;
547+
indices[i * getIndicesPerParticle() + 11] = i * getVerticesPerParticle() + 6;
548+
549+
indices[i * getIndicesPerParticle() + 12] = i * getVerticesPerParticle() + 4;
550+
indices[i * getIndicesPerParticle() + 13] = i * getVerticesPerParticle() + 5;
551+
indices[i * getIndicesPerParticle() + 14] = i * getVerticesPerParticle() + 6;
552+
553+
indices[i * getIndicesPerParticle() + 15] = i * getVerticesPerParticle() + 6;
554+
indices[i * getIndicesPerParticle() + 16] = i * getVerticesPerParticle() + 5;
555+
indices[i * getIndicesPerParticle() + 17] = i * getVerticesPerParticle() + 7;
556+
}
489557
}
490558

491559
ctx->updateBuffer(m_ib, 0, info.size, indices.data());

src/dxvk/rtx_render/rtx_particle_system.h

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,7 @@ namespace dxvk {
7575
uint32_t generationIdx = 0;
7676

7777
ParticleSystem() = delete;
78-
ParticleSystem(const RtxParticleSystemDesc& desc, const MaterialData& matData, const CategoryFlags& cats, const uint32_t seed)
79-
: context(desc)
80-
, materialData(matData)
81-
, categories(cats) {
82-
// Seed the RNG with a parameter from the manager, so we get unique random values for each particle system
83-
generator = std::default_random_engine(seed);
84-
// Store this hash since it cannot change now.
85-
// NOTE: This material data hash is stable within a run, but since hash depends on VK handles, it is not reliable across runs.
86-
m_cachedHash = materialData.getHash() ^ context.desc.calcHash();
87-
}
78+
ParticleSystem(const RtxParticleSystemDesc& desc, const MaterialData& matData, const CategoryFlags& cats, const uint32_t seed);
8879

8980
XXH64_hash_t getHash() const {
9081
return m_cachedHash;
@@ -110,12 +101,20 @@ namespace dxvk {
110101
return generationIdx;
111102
}
112103

104+
uint32_t getVerticesPerParticle() const {
105+
return context.desc.enableMotionTrail ? 8 : 4;
106+
}
107+
108+
uint32_t getIndicesPerParticle() const {
109+
return context.desc.enableMotionTrail ? 18 : 6;
110+
}
111+
113112
uint32_t getVertexCount() const {
114-
return context.desc.maxNumParticles * 4;
113+
return context.desc.maxNumParticles * getVerticesPerParticle();
115114
}
116115

117116
uint32_t getIndexCount() const {
118-
return context.desc.maxNumParticles * 6;
117+
return context.desc.maxNumParticles * getIndicesPerParticle();
119118
}
120119

121120
void allocStaticBuffers(DxvkContext* pCtx);
@@ -157,6 +156,8 @@ namespace dxvk {
157156
RTX_OPTION("rtx.particles.globalPreset", bool, useTurbulence, true, "Enable turbulence simulation.");
158157
RTX_OPTION("rtx.particles.globalPreset", float, turbulenceAmplitude, 5.f, "How much turbulence influences the force of a particle.");
159158
RTX_OPTION("rtx.particles.globalPreset", float, turbulenceFrequency, .05f, "The rate of change of turbulence forces.");
159+
RTX_OPTION("rtx.particles.globalPreset", bool, enableMotionTrail, false, "Elongates the particle with respect to velocity, texture edges are preserved, with only the center being stretched which provides a motion blur like effect on the particles themselves. This will automatically align particles rotation with their individual velocitys (similar to rtx.particles.globalPreset.alignParticlesToVelocity) and so rotation parameters are no longer taken into account when this setting is enabled.");
160+
RTX_OPTION("rtx.particles.globalPreset", float, motionTrailMultiplier, 1.f, "When enableMotionTrail is set to enabled, this value can be used to increase (or decrease) the length of the tail artificially, which is determined by the velocity. A value of 1 (the default) will ensure each particle is the exact size of the motion over the previous frame. Values geater than 1 will increase that size linearly. Likewise for smaller than 1. 0 and below is an invalid value.");
160161

161162

162163
void setupConstants(RtxContext* ctx, ParticleSystemConstants& constants);

src/dxvk/shaders/rtx/pass/particles/particle_system_common.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ struct RtxParticleSystemDesc {
7272
uint8_t enableCollisionDetection;
7373

7474
float collisionRestitution;
75-
uint pad0;
76-
uint pad1;
75+
uint enableMotionTrail;
76+
float motionTrailMultiplier;
7777
uint pad2;
7878

7979
#ifdef __cplusplus
@@ -91,10 +91,12 @@ struct RtxParticleSystemDesc {
9191
struct GpuParticleSystem {
9292
RtxParticleSystemDesc desc; // TODO: Can compress this further.
9393

94-
// These members arent hashed
94+
// These members aren't hashed
95+
float2 particleVertexOffsets[8];
96+
9597
uint spawnParticleOffset = 0;
9698
uint spawnParticleCount = 0;
97-
uint pad0;
99+
uint numVerticesPerParticle = 4;
98100
uint pad1;
99101

100102
#ifndef __cplusplus
@@ -138,6 +140,7 @@ struct ParticleSystemConstants {
138140
float deltaTimeSecs;
139141

140142
float absoluteTimeSecs;
143+
float invDeltaTimeSecs;
141144
uint frameIdx;
142145
uint16_t renderingWidth;
143146
uint16_t renderingHeight;

0 commit comments

Comments
 (0)