Files
Noita-GPU-Vulkan/shaders/fx_common.glsl
T
2026-10-19 20:59:15 +02:00

209 lines
13 KiB
GLSL

// Shared declarations of the GPU wand/projectile/effect system (proj_spawn, proj_step, fx_particles, fx_anims, rigid_events).
// The CPU only runs the spell script (gun.lua) and uploads its result: tables of projectile types, effect definitions, and the shot tree
// (nodes and specs) of every cast. Flight, collisions, triggers, explosions, damage, emitters, particles, lights and animations run here.
#include "common.glsl"
#include "far.glsl"
struct Ent {
vec4 a, box, mv, ai;
ivec4 st;
vec4 hp;
ivec4 x, rg, rg2, aux;
vec4 ex; // detection cone (cos of half angle), eye offset y, melee enabled, 0
vec4 ex2; // ranged attack: projectile speed, gravity
ivec4 ex3; // blood material + 1, blood colour, damage types taken since the last check (bit 1 projectile, 2 explosion, 4 fire, 8 melee), 0
vec4 ex4; // air in the lungs (frames), air maximum (0 = does not need air), damage per frame without air, frames left on fire
vec4 ex5; // swimming: swim_idle_buoyancy_coeff, swim_up_buoyancy_coeff, swim_drag, swim_extra_horizontal_drag
vec4 ex6; // swim_down_buoyancy_coeff, frames not swimming, pixel of the last liquid count (x, y)
vec4 ex7; // blood_multiplier (creatures); liquid inventory of a body: reserve, material + 1, frames of emission left
ivec4 ex8; vec4 ex9; vec4 ex10; vec4 ex11; vec4 ex12, ex13, ex14, ex15, ex16, ex17, ex18, ex19; // damage event of a body: hit point relative to the body x 1000, damage x 1000 pending
};
layout(std430, set = 0, binding = 21) buffer EntLayer { uint entlayer[]; };
layout(std430, set = 0, binding = 22) buffer Ents { Ent ents[]; };
layout(std430, set = 0, binding = 23) readonly buffer Ctrl { ivec4 inp; ivec4 inp2; ivec4 inp3; vec4 outp; vec4 outp2; vec4 view; vec4 hand0; ivec4 hand1; vec4 hand2; } ctrl;
layout(std430, set = 0, binding = 39) readonly buffer EntPrev { vec4 prevpos[]; }; // entity positions before the last tick (for interpolation)
layout(std430, set = 0, binding = 27) buffer Edits { ivec4 ehead; vec4 ee[512]; }; // cell edits, two vec4 each (see edits.comp)
layout(std430, set = 0, binding = 33) readonly buffer FxTables { uvec4 ft[]; }; // projectile types, explosion effects, emitters (words, see gun/gpu_fx.h)
layout(std430, set = 0, binding = 34) readonly buffer FxTex { uint fxtex[]; }; // sprite frames (ARGB) of projectiles and explosions
#include "fx_host.glsl"
#include "fx_state.glsl"
// rigid bodies (read only): projectiles stop at them
layout(std430, set = 0, binding = 30) readonly buffer RShapes { uint rsh[]; };
struct RBody { vec4 p; vec4 v; ivec4 i; vec4 q; vec4 l; vec4 n; vec4 m; ivec4 jn; vec4 ja0; vec4 ja1; vec4 ja2; vec4 ja3; };
layout(std430, set = 0, binding = 31) readonly buffer RBodies { ivec4 rhdr; RBody rbodies[]; };
layout(std430, set = 0, binding = 37) buffer Xev { ivec4 xhead; vec4 xe[512]; }; // explosion events for the rigid bodies: x, y, radius, power
struct FLight { vec4 a; vec4 b; }; // x, y, radius, strength / rgb
layout(std430, set = 0, binding = 38) buffer FxLights { uint lcount; uint lpad[3]; FLight lights[512]; };
const int TYPE_STRIDE = 18, BOOM_BASE = 9216, BOOM_STRIDE = 4, EMIT_BASE = 9472, EMIT_STRIDE = 4, LOAD_BASE = 13568; // (512 types: the scripts' variants of a projectile are types of their own)
float uf(uint u) { return uintBitsToFloat(u); }
vec4 ftf(int i) { return uintBitsToFloat(ft[i]); }
ivec4 fti(int i) { return ivec4(ft[i]); }
float rnd01(uint h) { return float(h & 0xFFFFu) / 65536.0; }
bool solid_at(ivec2 w) {
ivec2 p = w - ivec2(pc.view_x, pc.view_y);
if (p.x < 0 || p.y < 0 || p.x >= pc.gw || p.y >= pc.gh) { // outside the window: a resident far block (far.glsl), else a wall
const int s = far_slot(w);
if (s == 0) return true;
const uint fm = cell_mat(farcells[far_index(s, w)]);
return fm != 0u && (mflags(fm) & (F_STATIC | F_SAND)) != 0u;
}
uint m = cell_mat(cells[p.y * pc.gw + p.x]);
return m != 0u && (mflags(m) & (F_STATIC | F_SAND)) != 0u;
}
void add_light(vec2 pos, float radius, float strength, vec3 rgb) {
uint i = atomicAdd(lcount, 1u);
if (i < 512u) { lights[i].a = vec4(pos, radius, strength); lights[i].b = vec4(rgb, 0.0); }
}
uint add_anim(vec2 pos, int boom, float radius, float strength, int maxage, uint rgb, float angle) { // the record
uint s = atomicAdd(fcnt[2], 1u) % uint(FANIM_MAX);
fanims[s].a = vec4(pos, radius, strength);
fanims[s].b = ivec4(boom, 0, maxage, int(rgb & 0xFFFFFFu));
fanims[s].c = vec4(angle, 0.0, 0.0, 0.0);
return s;
}
void add_edit(vec4 q, vec4 r) {
uint i = uint(atomicAdd(ehead.x, 1));
if (i < 256u) { ee[2u * i] = q; ee[2u * i + 1u] = r; }
}
// A world point inside a rigid body's pixel mask? (the grid is filled by rigid_raster.comp)
layout(std430, set = 0, binding = 40) readonly buffer BodyGrid { uint bodyGrid[]; };
bool body_hit(vec2 w) {
ivec2 p = ivec2(floor(w)) - ivec2(pc.view_x, pc.view_y);
return p.x >= 0 && p.y >= 0 && p.x < pc.gw && p.y < pc.gh && bodyGrid[p.y * pc.gw + p.x] != 0u;
}
// The entity slot of the rigid body that covers a world point, -1 when none.
int body_slot(vec2 w) {
ivec2 p = ivec2(floor(w)) - ivec2(pc.view_x, pc.view_y);
if (p.x < 0 || p.y < 0 || p.x >= pc.gw || p.y >= pc.gh) return -1;
return int(bodyGrid[p.y * pc.gw + p.x] & 0x7FFFFFFFu) - 1; // (bit 31: the characters-pass mark of rigid_raster.comp)
}
// Who damaged an entity last: the slot + 1 of the attacker (0 = unknown) in the low 12 bits of ex8.w, the frame in the rest. The host reads it when the hp drops (script_damage_received).
void note_attacker(int victim, int attacker_plus_1) {
if (attacker_plus_1 > 0 && attacker_plus_1 < 4096) ents[victim].ex8.w = attacker_plus_1 | ((pc.frame & 0xFFFFF) << 12);
}
// A hit that the victim's multipliers took to nothing (a protection): the frame in ex11.w of a creature or the player, for the host's BLOCK message (00b071f0)
void note_block(int victim, float raw, float dealt) {
const int kind = ents[victim].st.x;
if (raw > 0.0 && dealt <= 0.0 && (kind == 1 || kind == 2 || kind == 3)) ents[victim].ex11.w = float(pc.frame);
}
// A damage event on a body with a liquid inventory: the corpse part emits (entities.comp).
void body_damage(int slot, vec2 hit, float dmg) {
if (slot < 0 || ents[slot].ex7.y <= 0.0) return;
atomicAdd(ents[slot].ex8.z, max(1, int(dmg * 1000.0)));
const vec2 r = hit - ents[slot].a.xy; const float ca = cos(ents[slot].a.z), sa = sin(ents[slot].a.z);
const vec2 rel = vec2(ca * r.x + sa * r.y, -sa * r.x + ca * r.y); // in the body's frame (entities.comp turns the leak back with the rotation)
atomicExchange(ents[slot].ex8.x, int(rel.x * 1000.0)); atomicExchange(ents[slot].ex8.y, int(rel.y * 1000.0));
}
// The damage multiplier the perks give a shooter; applied ONCE when the shot is made (Message_Shot, 00b06870: projectile damage, and the explosion damage unless not_scaled_by_gamefx)
float dealt_of(int shooter) {
if (shooter <= 0) return 1.0;
const Ent s = ents[shooter - 1];
return (s.st.x == 1 || s.st.x == 2 || s.st.x == 3) && s.ex7.y > 0.0 ? s.ex7.y : 1.0;
}
// The liquid material at a position (0 when there is none).
uint liquid_at_pos(vec2 pos) {
ivec2 p = ivec2(floor(pos)) - ivec2(pc.view_x, pc.view_y);
if (p.x < 0 || p.y < 0 || p.x >= pc.gw || p.y >= pc.gh) return 0u;
uint m = cell_mat(cells[p.y * pc.gw + p.x]);
if (m == 0u) return 0u;
uint f = mflags(m);
return ((f & F_LIQUID) != 0u && (f & (F_SAND | F_STATIC)) == 0u) ? m : 0u;
}
// A projectile pushes the liquid cells around its position aside (to the nearest free cell away from it).
void displace_liquid(vec2 pos) {
{ ivec2 p0 = ivec2(floor(pos)) - ivec2(pc.view_x, pc.view_y); if (p0.x < 1 || p0.y < 1 || p0.x >= pc.gw - 1 || p0.y >= pc.gh - 1) return;
uint m0 = cell_mat(cells[p0.y * pc.gw + p0.x]); if (m0 == 0u || (mflags(m0) & F_LIQUID) == 0u) return; } // only when the projectile is inside a liquid
for (int dy = -1; dy <= 1; ++dy)
for (int dx = -1; dx <= 1; ++dx) {
ivec2 w = ivec2(floor(pos)) + ivec2(dx, dy);
ivec2 p = w - ivec2(pc.view_x, pc.view_y);
if (p.x < 0 || p.y < 0 || p.x >= pc.gw || p.y >= pc.gh) continue;
int i = p.y * pc.gw + p.x;
uint c = cells[i];
uint m = cell_mat(c);
if (m == 0u) continue;
uint f = mflags(m);
if ((f & F_LIQUID) == 0u || (f & (F_SAND | F_STATIC)) != 0u) continue;
vec2 d = vec2(w) + 0.5 - pos;
d = length(d) < 0.01 ? vec2(0.0, -1.0) : normalize(d);
for (int r = 1; r <= 6; ++r) {
ivec2 t = w + ivec2(round(d * float(r)));
ivec2 q = t - ivec2(pc.view_x, pc.view_y);
if (q.x < 0 || q.y < 0 || q.x >= pc.gw || q.y >= pc.gh) break;
int ti = q.y * pc.gw + q.x;
if (atomicCompSwap(cells[ti], 0u, c) == 0u) { ext[ti] = ext[i]; cells[i] = 0u; ext[i] = vec2(0.0); break; }
}
}
}
vec3 rgb_of(uint c) { return vec3(float((c >> 16) & 255u), float((c >> 8) & 255u), float(c & 255u)) / 255.0; }
// Explosion visuals: animated sprite, light flash, sparks and material sparks.
// radius >= 0: the explosion's own radius (a shot with Explosive Projectile: the file's 2 + 15 per modifier); the light is that x light_radius_coeff (00657960), the table
// holds it for the file's radius
void boom_fx(int boom, vec2 pos, uint seed, float radius) {
int b = BOOM_BASE + boom * BOOM_STRIDE;
ivec4 b0 = fti(b), b1 = fti(b + 1), b2 = fti(b + 2);
vec4 b3 = ftf(b + 3);
float rad = b3.x;
atomicMax(lpad[0], uint(b3.y * 1500.0)); // camera shake (milli-cells), decays in fx_anims
int frames = max(b0.w, 1), wait = max(b1.x, 1);
int maxage = b0.z > 0 ? frames * wait + 2 : 9;
// the light (ConfigExplosion light_*): radius explosion_radius x light_radius_coeff, colour light_r/g/b, fading over light_fade_time (b1.w bits 4-15 / 16-27)
const bool lightOn = (b1.w & 4) != 0;
float lrad = float((uint(b1.w) >> 4) & 0xFFFu);
if (radius >= 0.0) lrad = rad > 0.0 ? lrad * radius / rad : radius * 8.0; // (the coefficient default 8: 004b2780)
lrad = min(lrad, 4095.0);
const int lframes = max(1, int((uint(b1.w) >> 16) & 0xFFFu));
const uint an = add_anim(pos, boom, lightOn ? max(lrad, 9.0) : 0.0, lightOn ? 0.6 : 0.0, max(max(maxage, 9), lightOn ? lframes : 0), uint(b1.y), 0.0);
if (lightOn) fanims[an].c.y = float(lframes); // (the light fades over its own time, the sprite plays over its own)
const int sp0 = int(uint(b2.x) & 0xFFu);
const uint sparkRgb = uint(b2.x) >> 8;
int ns = (b1.w & 1) != 0 ? sp0 + int(rnd01(hash3(seed, 1u, 7u)) * float(b2.y - sp0 + 1)) : 0;
for (int i = 0; i < ns && i < 64; ++i) {
float a = rnd01(hash3(seed, uint(i), 11u)) * 6.2832, sp = 0.8 + rnd01(hash3(seed, uint(i), 12u)) * 2.4;
emit_particle(pos, vec2(cos(a), sin(a)) * sp - vec2(0.0, 0.6), 18.0 + rnd01(hash3(seed, uint(i), 13u)) * 27.0, 0.1, sparkRgb, true, true);
}
int nm = (b1.w & 2) != 0 ? b2.z + int(rnd01(hash3(seed, 2u, 8u)) * float(b2.w - b2.z + 1)) : 0;
if ((b1.w & 10) == 10 && b0.z == 0) { // an ambient emitter (a portal): material sparks spread over a disc of the radius, drifting away from its centre, no gravity, long-lived, additive
for (int i = 0; i < nm && i < 160; ++i) {
float a = rnd01(hash3(seed, uint(i), 31u)) * 6.2832, rr = sqrt(rnd01(hash3(seed, uint(i), 32u))) * rad, sp = 0.05 + rnd01(hash3(seed, uint(i), 33u)) * 0.25;
vec2 d = vec2(cos(a), sin(a));
emit_particle(pos + d * rr, d * sp, 150.0 + rnd01(hash3(seed, uint(i), 34u)) * 60.0, 0.0, uint(b1.z), true, true);
}
nm = 0;
}
for (int i = 0; i < nm && i < 64; ++i) {
float a = rnd01(hash3(seed, uint(i), 21u)) * 6.2832, sp = 0.5 + rnd01(hash3(seed, uint(i), 22u)) * 1.5;
emit_particle(pos, vec2(cos(a), sin(a)) * sp - vec2(0.0, 0.4), 20.0 + rnd01(hash3(seed, uint(i), 23u)) * 30.0, 0.12, uint(b1.z), true, true);
}
}
// The damage of a projectile hit on entity k (TakeDamage_Impl 00ab9a90: every damage type goes through the victim's multiplier of that type): the projectile channel, the
// damage_by_type channels of the type (table words 12-14: melee, electricity, fire, ice / slice, drill, radioactive, poison / healing, curse, holy, explosion) and the wand's adds (f.x electricity, f.y ice)
float hit_damage(int k, float proj, int tb, vec4 f) {
const Ent v = ents[k];
const vec4 c0 = ftf(tb + 12), c1 = ftf(tb + 13), c2 = ftf(tb + 14);
return proj * v.ex9.x
+ c0.x * v.ex9.w + (c0.y + f.x) * v.ex12.x + c0.z * v.ex9.z + (c0.w + f.y) * v.ex12.w
+ c1.x * v.ex12.z + c1.y * v.ex12.y + c1.z * v.ex13.z + c1.w * v.ex13.w
+ c2.x * v.ex13.x + c2.y * v.ex14.y + c2.z * v.ex14.z + c2.w * v.ex9.y;
}