working splatter with particle layers

This commit is contained in:
jacob 2026-02-16 22:14:51 -06:00
parent a9ca711892
commit ed40711781
4 changed files with 202 additions and 84 deletions

View File

@ -2265,6 +2265,143 @@ void V_TickForever(WaveLaneCtx *lane)
// if (0)
{
for (P_Ent *bullet = P_FirstEnt(local_frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet))
{
if (bullet->is_bullet && bullet->has_hit)
{
P_MaterialKind material = bullet->hit_material;
Vec2 hit_entry = bullet->hit_entry;
Vec2 hit_entry_normal = bullet->hit_entry_normal;
Vec2 bullet_vel = SubVec2(bullet->bullet_end, bullet->bullet_start);
V_DrawLine(bullet->bullet_start, bullet->bullet_end, Color_Cyan);
//////////////////////////////
//- Wall particles
// if (material != P_MaterialKind_Flesh)
// {
// //- Wall debris
// {
// V_Emitter emitter = Zi;
// {
// // emitter.flags |= V_ParticleFlag_PruneWhenStill;
// // emitter.flags |= V_ParticleFlag_StainOnPrune;
// emitter.count = 4;
// emitter.start = hit_entry;
// emitter.end = emitter.start;
// emitter.color_lin = LinearFromSrgb(VEC4(0.4, 0.3, 0.2, 0.75));
// emitter.color_spread = VEC4(0, 0, 0, 0.25);
// emitter.speed = 2;
// // emitter.speed_spread = emitter.speed * 2;
// emitter.speed_spread = emitter.speed * 2.5;
// emitter.velocity_falloff = 5;
// emitter.velocity_falloff_spread = emitter.velocity_falloff_spread * 1.5;
// Vec2 dir = hit_entry_normal;
// emitter.angle = AngleFromVec2(dir);
// emitter.angle_spread = Tau * 0.5;
// // emitter.lifetime = 0.25;
// // emitter.lifetime = 0.05;
// // emitter.lifetime = 0.04;
// emitter.lifetime_spread = emitter.lifetime * 2;
// }
// V_PushParticles(emitter);
// }
// //- Wall dust
// {
// V_Emitter emitter = Zi;
// {
// // emitter.flags |= V_ParticleFlag_PruneWhenStill;
// emitter.count = 32;
// emitter.start = hit_entry;
// emitter.end = emitter.start;
// emitter.color_lin = LinearFromSrgb(VEC4(0.5, 0.5, 0.5, 0.75));
// emitter.speed = 4;
// // emitter.speed_spread = emitter.speed * 2;
// emitter.speed_spread = emitter.speed * 2;
// emitter.velocity_falloff = 12;
// emitter.velocity_falloff_spread = emitter.velocity_falloff_spread * 1.5;
// Vec2 dir = hit_entry_normal;
// emitter.angle = AngleFromVec2(dir);
// emitter.angle_spread = Tau * 0.1;
// emitter.lifetime = 1;
// // emitter.lifetime = 0.05;
// // emitter.lifetime = 0.04;
// emitter.lifetime_spread = emitter.lifetime * 2;
// }
// V_PushParticles(emitter);
// }
// }
//////////////////////////////
//- Blood particles
if (material == P_MaterialKind_Flesh)
{
{
V_Emitter emitter = Zi;
emitter.kind = V_ParticleKind_BloodTrail;
// emitter.kind = V_ParticleKind_BloodDebris;
Vec2 dir = NormVec2(NegVec2(bullet_vel));
f32 angle = AngleFromVec2(dir);
// f32 angle = 0;
// f32 angle_spread = Tau * 0.25;
f32 angle_spread = TweakFloat("Emitter angle spread", 0.1, 0, 1) * Tau;
// f32 angle_spread = Tau;
// f32 angle_spread = 0;
// f32 speed = 5;
// f32 speed = 25;
f32 speed = 50;
// f32 speed = 100;
f32 speed_spread = speed * 2;
emitter.pos.p0 = emitter.pos.p1 = hit_entry;
emitter.speed.min = speed - speed_spread * 0.5;
emitter.speed.max = speed + speed_spread * 0.5;
emitter.angle.min = angle - angle_spread * 0.5;
emitter.angle.max = angle + angle_spread * 0.5;
emitter.count = TweakFloat("Emitter count", 1, 0, 100) * (Kibi(32) * frame->dt);
V_PushParticles(emitter);
}
}
// V_DrawPoint(victim_raycast.p, Color_Green);
// V_DrawLine(victim_raycast.p, AddVec2(victim_raycast.p, MulVec2(victim_raycast.normal, 0.5)), Color_White);
}
}
}
// if (0) // if (0)
// { // {
// for (P_Ent *bullet = P_FirstEnt(local_frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet)) // for (P_Ent *bullet = P_FirstEnt(local_frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet))
@ -2556,32 +2693,32 @@ void V_TickForever(WaveLaneCtx *lane)
V_PushParticles(emitter); V_PushParticles(emitter);
} }
{ // {
V_Emitter emitter = Zi; // V_Emitter emitter = Zi;
// emitter.kind = V_ParticleKind_BloodTrail; // // emitter.kind = V_ParticleKind_BloodTrail;
emitter.kind = V_ParticleKind_BloodDebris; // emitter.kind = V_ParticleKind_BloodDebris;
f32 angle = AngleFromVec2(frame->look); // f32 angle = AngleFromVec2(frame->look);
// f32 angle = 0; // // f32 angle = 0;
f32 angle_spread = Tau * 0.25; // f32 angle_spread = Tau * 0.25;
// f32 angle_spread = Tau; // // f32 angle_spread = Tau;
// f32 angle_spread = 0; // // f32 angle_spread = 0;
// f32 speed = 5; // // f32 speed = 5;
f32 speed = 10; // f32 speed = 10;
// f32 speed = 50; // // f32 speed = 50;
// f32 speed = 100; // // f32 speed = 100;
f32 speed_spread = speed * 2; // f32 speed_spread = speed * 2;
emitter.pos.p0 = emitter.pos.p1 = frame->world_cursor; // emitter.pos.p0 = emitter.pos.p1 = frame->world_cursor;
emitter.speed.min = speed - speed_spread * 0.5; // emitter.speed.min = speed - speed_spread * 0.5;
emitter.speed.max = speed + speed_spread * 0.5; // emitter.speed.max = speed + speed_spread * 0.5;
emitter.angle.min = angle - angle_spread * 0.5; // emitter.angle.min = angle - angle_spread * 0.5;
emitter.angle.max = angle + angle_spread * 0.5; // emitter.angle.max = angle + angle_spread * 0.5;
emitter.count = Kibi(32) * frame->dt; // emitter.count = Kibi(32) * frame->dt;
V_PushParticles(emitter); // V_PushParticles(emitter);
} // }
} }
////////////////////////////// //////////////////////////////

View File

@ -18,21 +18,6 @@ Vec4 V_ColorFromParticle(V_ParticleDesc desc, u32 particle_idx, u32 density)
result = desc.color; result = desc.color;
// // FIXME: Base color on particle desc
// if (desc.kind == V_ParticleKind_Test)
// {
// // result.rgb = Vec3(0, 0, 0);
// result = LinearFromSrgb(Vec4(0.5, 0.1, 0.1, 0.5));
// }
// else if (desc.kind == V_ParticleKind_Debris)
// {
// result = Color_Orange;
// }
// else if (desc.kind == V_ParticleKind_Smoke)
// {
// result = Vec4(0.15, 0.15, 0.15, 1);
// }
// Apply density // Apply density
{ {
if (desc.kind == V_ParticleKind_Smoke) if (desc.kind == V_ParticleKind_Smoke)
@ -49,10 +34,10 @@ Vec4 V_ColorFromParticle(V_ParticleDesc desc, u32 particle_idx, u32 density)
// t = saturate(t); // t = saturate(t);
// result.rgb *= 1.0 - (t * 0.9); // result.rgb *= 1.0 - (t * 0.9);
f32 t = (f32)density / 5; f32 t = (f32)density / 10;
// t = smoothstep(-10, 10, t); // t = smoothstep(-10, 10, t);
// t = smoothstep(-5, 5, t); // t = smoothstep(-5, 5, t);
t = smoothstep(0, 50, t); t = smoothstep(-50, 50, t);
// result.rgb *= 1.0 - (t * 0.9); // result.rgb *= 1.0 - (t * 0.9);
// result.a = t; // result.a = t;
@ -296,9 +281,6 @@ ComputeShader(V_SimParticlesCS, 64)
f32 rand_speed = Norm16(seed0 >> 32); f32 rand_speed = Norm16(seed0 >> 32);
f32 rand_falloff = Norm16(seed0 >> 48); f32 rand_falloff = Norm16(seed0 >> 48);
u64 seed1 = MixU64(seed0);
f32 rand_density = Norm16(seed1 >> 0);
////////////////////////////// //////////////////////////////
//- Init //- Init
@ -335,11 +317,6 @@ ComputeShader(V_SimParticlesCS, 64)
StaticAssert(V_ParticlesCap <= (1 << 24)); // particle idx must fit in 24 bits StaticAssert(V_ParticlesCap <= (1 << 24)); // particle idx must fit in 24 bits
StaticAssert(V_ParticleKind_COUNT <= 0x7F); // particle kind must fit in 7 bits StaticAssert(V_ParticleKind_COUNT <= 0x7F); // particle kind must fit in 7 bits
if (AnyBit(desc.flags, V_ParticleFlag_StainTrail))
{
packed |= 1 << 31;
}
////////////////////////////// //////////////////////////////
//- Move //- Move
@ -414,8 +391,8 @@ ComputeShader(V_SimParticlesCS, 64)
if (is_in_world) if (is_in_world)
{ {
f32 commit_delta = abs(t_diff) * desc.commit_rate * frame.dt; f32 stain_delta = abs(t_diff) * desc.stain_rate * frame.dt;
particle.commit_accum += commit_delta; particle.stain_accum += stain_delta;
//- Handle collision //- Handle collision
V_OccluderKind occluder = (V_OccluderKind)occluders[cell_pos]; V_OccluderKind occluder = (V_OccluderKind)occluders[cell_pos];
@ -449,9 +426,10 @@ ComputeShader(V_SimParticlesCS, 64)
{ {
f32 collision_angle = lerp(-0.05 * Tau, 0.05 * Tau, rand_collision_angle); f32 collision_angle = lerp(-0.05 * Tau, 0.05 * Tau, rand_collision_angle);
f32 collision_velocity_falloff = lerp(50, 100, rand_collision_velocity); // f32 collision_velocity_falloff = lerp(50, 100, rand_collision_velocity);
// f32 collision_velocity_falloff = lerp(5000, 10000, rand_collision_velocity); // f32 collision_velocity_falloff = lerp(5000, 10000, rand_collision_velocity);
// f32 collision_velocity_falloff = lerp(500, 10000, rand_collision_velocity); // f32 collision_velocity_falloff = lerp(500, 10000, rand_collision_velocity);
f32 collision_velocity_falloff = lerp(50, 100, rand_collision_velocity);
// f32 collision_velocity_falloff = 0; // f32 collision_velocity_falloff = 0;
particle.velocity = RotateVec2Angle(particle.velocity, collision_angle); particle.velocity = RotateVec2Angle(particle.velocity, collision_angle);
@ -472,22 +450,26 @@ ComputeShader(V_SimParticlesCS, 64)
done = 1; done = 1;
if (AnyBit(desc.flags, V_ParticleFlag_StainWhenPruned)) if (AnyBit(desc.flags, V_ParticleFlag_StainWhenPruned))
{ {
// particle.commit_accum = max(particle.commit_accum, 1); // particle.stain_accum = max(particle.stain_accum, 1);
particle.commit_accum += 1; particle.stain_accum += 1;
packed |= 1 << 31; packed |= 1 << 31;
} }
} }
if (!collision) if (!collision)
{ {
u32 commit_count = floor(particle.commit_accum); u32 stain_count = floor(particle.stain_accum);
u32 density = commit_count; u32 density = 1;
u32 commit = packed;
if (stain_count > 0)
{ {
InterlockedMax(cells[cell_pos], packed); commit |= (1 << 31);
InterlockedAdd(densities[cell_pos], density);
particle.commit_accum -= commit_count;
} }
InterlockedMax(cells[cell_pos], commit);
InterlockedAdd(densities[cell_pos], density);
particle.stain_accum -= stain_count;
} }
} }
else else
@ -609,8 +591,8 @@ ComputeShader(V_SimParticlesCS, 64)
// if (is_in_world) // if (is_in_world)
// { // {
// f32 commit_delta = abs(t_diff) * desc.commit_rate * frame.dt; // f32 stain_delta = abs(t_diff) * desc.stain_rate * frame.dt;
// particle.commit_accum += commit_delta; // particle.stain_accum += stain_delta;
// //- Handle collision // //- Handle collision
// V_OccluderKind occluder = (V_OccluderKind)occluders[cell_pos]; // V_OccluderKind occluder = (V_OccluderKind)occluders[cell_pos];
@ -667,13 +649,13 @@ ComputeShader(V_SimParticlesCS, 64)
// if (prune && AnyBit(desc.flags, V_ParticleFlag_StainWhenPruned)) // if (prune && AnyBit(desc.flags, V_ParticleFlag_StainWhenPruned))
// { // {
// particle.commit_accum += 1; // particle.stain_accum += 1;
// } // }
// if (!collision) // if (!collision)
// { // {
// //- Stain // //- Stain
// u32 stains_count = floor(particle.commit_accum); // u32 stains_count = floor(particle.stain_accum);
// if (stains_count > 0) // if (stains_count > 0)
// { // {
// // TODO: Fixed point // // TODO: Fixed point
@ -681,7 +663,7 @@ ComputeShader(V_SimParticlesCS, 64)
// InterlockedMax(stain_cells[cell_pos], packed); // InterlockedMax(stain_cells[cell_pos], packed);
// InterlockedAdd(stain_densities[cell_pos], density); // InterlockedAdd(stain_densities[cell_pos], density);
// drynesses[cell_pos] = 0; // drynesses[cell_pos] = 0;
// particle.commit_accum -= stains_count; // particle.stain_accum -= stains_count;
// } // }
// //- Draw // //- Draw

View File

@ -11,49 +11,49 @@ V_ParticleDesc V_DescFromParticleKind(V_ParticleKind kind)
V_ParticleDesc result; V_ParticleDesc result;
{ {
PERSIST Readonly V_ParticleFlag flags[V_ParticleKind_COUNT] = { PERSIST Readonly V_ParticleFlag flags[V_ParticleKind_COUNT] = {
#define X(name, flags, layer, commit_rate, pen_rate, r, g, b, a) flags, #define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) flags,
V_ParticlesXList(X) V_ParticlesXList(X)
#undef X #undef X
}; };
PERSIST Readonly V_ParticleLayer layers[V_ParticleKind_COUNT] = { PERSIST Readonly V_ParticleLayer layers[V_ParticleKind_COUNT] = {
#define X(name, flags, layer, commit_rate, pen_rate, r, g, b, a) layer, #define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) layer,
V_ParticlesXList(X) V_ParticlesXList(X)
#undef X #undef X
}; };
PERSIST Readonly f32 commit_rates[V_ParticleKind_COUNT] = { PERSIST Readonly f32 stain_rates[V_ParticleKind_COUNT] = {
#define X(name, flags, layer, commit_rate, pen_rate, r, g, b, a) commit_rate, #define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) stain_rate,
V_ParticlesXList(X) V_ParticlesXList(X)
#undef X #undef X
}; };
PERSIST Readonly f32 pen_rates[V_ParticleKind_COUNT] = { PERSIST Readonly f32 pen_rates[V_ParticleKind_COUNT] = {
#define X(name, flags, layer, commit_rate, pen_rate, r, g, b, a) pen_rate, #define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) pen_rate,
V_ParticlesXList(X) V_ParticlesXList(X)
#undef X #undef X
}; };
PERSIST Readonly f32 r[V_ParticleKind_COUNT] = { PERSIST Readonly f32 r[V_ParticleKind_COUNT] = {
#define X(name, flags, layer, commit_rate, pen_rate, r, g, b, a) r, #define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) r,
V_ParticlesXList(X) V_ParticlesXList(X)
#undef X #undef X
}; };
PERSIST Readonly f32 g[V_ParticleKind_COUNT] = { PERSIST Readonly f32 g[V_ParticleKind_COUNT] = {
#define X(name, flags, layer, commit_rate, pen_rate, r, g, b, a) g, #define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) g,
V_ParticlesXList(X) V_ParticlesXList(X)
#undef X #undef X
}; };
PERSIST Readonly f32 b[V_ParticleKind_COUNT] = { PERSIST Readonly f32 b[V_ParticleKind_COUNT] = {
#define X(name, flags, layer, commit_rate, pen_rate, r, g, b, a) b, #define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) b,
V_ParticlesXList(X) V_ParticlesXList(X)
#undef X #undef X
}; };
PERSIST Readonly f32 a[V_ParticleKind_COUNT] = { PERSIST Readonly f32 a[V_ParticleKind_COUNT] = {
#define X(name, flags, layer, commit_rate, pen_rate, r, g, b, a) a, #define X(name, flags, layer, stain_rate, pen_rate, r, g, b, a) a,
V_ParticlesXList(X) V_ParticlesXList(X)
#undef X #undef X
}; };
result.kind = kind; result.kind = kind;
result.flags = flags[kind]; result.flags = flags[kind];
result.layer = layers[kind]; result.layer = layers[kind];
result.commit_rate = commit_rates[kind]; result.stain_rate = stain_rates[kind];
result.pen_rate = pen_rates[kind]; result.pen_rate = pen_rates[kind];
result.color = LinearFromSrgb(VEC4(r[kind], g[kind], b[kind], a[kind])); result.color = LinearFromSrgb(VEC4(r[kind], g[kind], b[kind], a[kind]));
} }

View File

@ -42,7 +42,6 @@ Enum(V_ParticleFlag)
V_ParticleFlag_NoPruneWhenStill = (1 << 0), V_ParticleFlag_NoPruneWhenStill = (1 << 0),
V_ParticleFlag_StainWhenPruned = (1 << 1), V_ParticleFlag_StainWhenPruned = (1 << 1),
V_ParticleFlag_NoReflect = (1 << 2), V_ParticleFlag_NoReflect = (1 << 2),
V_ParticleFlag_StainTrail = (1 << 3),
}; };
Enum(V_ParticleLayer) Enum(V_ParticleLayer)
@ -60,30 +59,30 @@ Enum(V_ParticleLayer)
/* Name */ None, \ /* Name */ None, \
/* Flags */ V_ParticleFlag_None, \ /* Flags */ V_ParticleFlag_None, \
/* Layer */ V_ParticleLayer_Ground, \ /* Layer */ V_ParticleLayer_Ground, \
/* Commit rate, pen chance */ 30, 0, \ /* Stain rate, pen chance */ 30, 0, \
/* Base color */ 0, 0, 0, 0 \ /* Base color */ 0, 0, 0, 0 \
) \ ) \
\ \
/* Ground particles */ \ /* Ground particles */ \
X( \ X( \
/* Name */ BloodTrail, \ /* Name */ BloodTrail, \
/* Flags */ V_ParticleFlag_NoReflect | V_ParticleFlag_StainTrail, \ /* Flags */ V_ParticleFlag_NoReflect | V_ParticleFlag_StainWhenPruned, \
/* Layer */ V_ParticleLayer_Ground, \ /* Layer */ V_ParticleLayer_Ground, \
/* Commit rate, pen chance */ 500, 0.25, \ /* Stain rate, pen chance */ 30, 0.25, \
/* Base color */ 0.5, 0.1, 0.1, 0.1 \ /* Base color */ 0.5, 0.1, 0.1, 0.05 \
) \ ) \
X( \ X( \
/* Name */ BloodDebris, \ /* Name */ BloodDebris, \
/* Flags */ V_ParticleFlag_StainWhenPruned, \ /* Flags */ V_ParticleFlag_StainWhenPruned, \
/* Layer */ V_ParticleLayer_Mid, \ /* Layer */ V_ParticleLayer_Mid, \
/* Commit rate, pen chance */ 30, 0, \ /* Stain rate, pen chance */ 30, 0, \
/* Base color */ 0.5, 0.1, 0.1, 0.8 \ /* Base color */ 0.5, 0.1, 0.1, 0.8 \
) \ ) \
X( \ X( \
/* Name */ Debris, \ /* Name */ Debris, \
/* Flags */ V_ParticleFlag_StainWhenPruned, \ /* Flags */ V_ParticleFlag_StainWhenPruned, \
/* Layer */ V_ParticleLayer_Mid, \ /* Layer */ V_ParticleLayer_Mid, \
/* Commit rate, pen chance */ 10000, 0, \ /* Stain rate, pen chance */ 0, 0, \
/* Base color */ 2, 0.5, 0, 1 \ /* Base color */ 2, 0.5, 0, 1 \
) \ ) \
\ \
@ -92,14 +91,14 @@ Enum(V_ParticleLayer)
/* Name */ Smoke, \ /* Name */ Smoke, \
/* Flags */ V_ParticleFlag_None, \ /* Flags */ V_ParticleFlag_None, \
/* Layer */ V_ParticleLayer_Air, \ /* Layer */ V_ParticleLayer_Air, \
/* Commit rate, pen chance */ 30, 0, \ /* Stain rate, pen chance */ 0, 0, \
/* Base color */ 0.15, 0.15, 0.15, 0.5 \ /* Base color */ 0.15, 0.15, 0.15, 0.5 \
) \ ) \
X( \ X( \
/* Name */ BulletTrail, \ /* Name */ BulletTrail, \
/* Flags */ V_ParticleFlag_None, \ /* Flags */ V_ParticleFlag_None, \
/* Layer */ V_ParticleLayer_Air, \ /* Layer */ V_ParticleLayer_Air, \
/* Commit rate, pen chance */ 30, 0, \ /* Stain rate, pen chance */ 0, 0, \
/* Base color */ 1, 0, 1, 1 \ /* Base color */ 1, 0, 1, 1 \
) \ ) \
\ \
@ -108,7 +107,7 @@ Enum(V_ParticleLayer)
/* Name */ Test, \ /* Name */ Test, \
/* Flags */ V_ParticleFlag_None, \ /* Flags */ V_ParticleFlag_None, \
/* Layer */ V_ParticleLayer_Mid, \ /* Layer */ V_ParticleLayer_Mid, \
/* Commit rate, pen chance */ 30, 0, \ /* Stain rate, pen chance */ 0, 0, \
/* Base color */ 1, 1, 0, 1 \ /* Base color */ 1, 1, 0, 1 \
) \ ) \
/* ----------------------------------------------------------------------------------------------------------------------------------- */ /* ----------------------------------------------------------------------------------------------------------------------------------- */
@ -138,7 +137,7 @@ Struct(V_Particle)
{ {
i32 kind; // If >= 0, then map to V_ParticleKind. Otherwize initialize particle using emitter at index [abs(kind) - 1] i32 kind; // If >= 0, then map to V_ParticleKind. Otherwize initialize particle using emitter at index [abs(kind) - 1]
f32 life; f32 life;
f32 commit_accum; f32 stain_accum;
u32 cells_count; u32 cells_count;
Vec2 pos; Vec2 pos;
Vec2 velocity; Vec2 velocity;
@ -149,7 +148,7 @@ Struct(V_ParticleDesc)
V_ParticleKind kind; V_ParticleKind kind;
V_ParticleFlag flags; V_ParticleFlag flags;
V_ParticleLayer layer; V_ParticleLayer layer;
f32 commit_rate; f32 stain_rate;
f32 pen_rate; f32 pen_rate;
Vec4 color; Vec4 color;
}; };