Files
2026-10-19 20:59:15 +02:00

100 lines
7.1 KiB
Plaintext

#version 450
#extension GL_GOOGLE_include_directive : require
layout(local_size_x = 8, local_size_y = 8) in;
#include "common.glsl"
// The glow of the materials (gfx_glow of materials.xml), as the game's data/shaders/post_glow1.frag and post_glow2.frag do it: the glow builds up over frames.
// pass 1 (post_glow1): H = 0.1 x the sum of 11 taps of the previous frame's glow, 1.5 texels apart, horizontally
// pass 2 (post_glow2): D = the sum of 11 taps of H vertically / 12.2; N = the new source x 4 ("HDR": gfx_glow 255 is the old default brightness, up to 1000);
// glow = N x 0.125 + mix(D, N, 0.05)
// In a steady area the glow settles near 3 x the source and spreads into a wide soft halo (the white core of a dense fire). Half resolution (a texel = 2 x 2 cells);
// the previous frame's glow follows the camera (its view is kept after the texels). render.comp adds it to the lights and blends it like post_final.frag.
layout(std430, set = 0, binding = 46) readonly buffer GlowTab { uint gtab[]; };
layout(std430, set = 0, binding = 47) buffer GlowA { vec4 ga[]; }; // the glow (persistent), then one vec4: the view of the frame that wrote it
layout(std430, set = 0, binding = 48) buffer GlowB { vec4 gb[]; }; // the horizontal pass
layout(std430, set = 0, binding = 8) readonly buffer Tables { uint tbl[]; };
layout(std430, set = 0, binding = 21) readonly buffer EntLayer { uint entlayer[]; };
layout(std430, set = 0, binding = 56) readonly buffer PartGlow { uint partglow[]; }; // the glowing particles of the frame added up per texel (fx_particles.comp) // the sprites and particles of the frame: the additive particles (alpha 1) are the particle glow
#define FPAL_TABLE tbl
// ---- the fire palettes of the cell blitting (main.cpp loads them after the tables; 00722490 the lookup, 00724580 the use) ----
uint fpal_word(uint k) { return FPAL_TABLE[1024u + 9u * mats[mats.length() - 1u].w + k]; }
vec3 fpal_rgb(uint w) { return vec3(float((w >> 16) & 255u), float((w >> 8) & 255u), float(w & 255u)) / 255.0; }
vec3 fire_lookup(uint at, uint row, uint w, float t) { // the row of the image at t x width: the first / last pixel outside, else the two neighbours mixed
const float x = float(w) * t;
const uint r0 = at + row * w;
if (x >= float(w)) return fpal_rgb(fpal_word(r0 + w - 1u));
if (x <= 0.0) return fpal_rgb(fpal_word(r0));
const uint i0 = uint(floor(x));
if (float(i0) == x || i0 + 1u >= w) return fpal_rgb(fpal_word(r0 + i0));
return mix(fpal_rgb(fpal_word(r0 + i0)), fpal_rgb(fpal_word(r0 + i0 + 1u)), x - float(i0));
}
uint fire_row(uint m) { return min(fpal_word(132u + m), 5u); } // CellData +0x68 fire_colors_index
float fire_age(uint b) { return float(birth_age(b)) + float(hash3(b & BIRTH_MASK, 77u, 5u) % 21u); } // 00709ef0: the flame's frame is the creation frame - uniform(0, 20)
vec3 fire_colour(uint m, uint b) { const float a = fire_age(b) / 40.0; return fire_lookup(0u, fire_row(m), 8u, a * a); } // colors_fire.png at (age / 40)^2
vec3 fire_glow_colour(uint m, uint b) { const float a = fire_age(b) / 40.0; return fire_lookup(48u, fire_row(m), 8u, fire_age(b) < 15.0 ? a : a * a); } // colors_fire_glow.png (young: age / 40)
vec3 burning_colour(uint m, uint h) { return fpal_rgb(fpal_word(96u + fire_row(m) * 6u + h % 6u)); } // colors_lava.bmp: a random pixel of the row
const float TAP = 1.5; // texels between the taps (post_glow*.frag: one_per_glow_texture_size x 1.5)
vec3 fetch(bool fromA, vec2 t, int hw, int hh) { // bilinear, zero outside
const vec2 q = t - 0.5;
const ivec2 q0 = ivec2(floor(q));
const vec2 f = q - vec2(q0);
vec3 r = vec3(0.0);
for (int k = 0; k < 4; ++k) {
const ivec2 s = q0 + ivec2(k & 1, k >> 1);
if (s.x < 0 || s.y < 0 || s.x >= hw || s.y >= hh) continue;
const float w = ((k & 1) != 0 ? f.x : 1.0 - f.x) * ((k >> 1) != 0 ? f.y : 1.0 - f.y);
r += (fromA ? ga[s.y * hw + s.x].rgb : gb[s.y * hw + s.x].rgb) * w;
}
return r;
}
void main() {
const int hw = (pc.gw + 1) / 2, hh = (pc.gh + 1) / 2;
const ivec2 q = ivec2(gl_GlobalInvocationID.xy);
if (pc.pass_ == 0) return; // (the source is read in pass 2)
if (q.x >= hw || q.y >= hh) return;
const vec2 t = vec2(q) + 0.5;
if (pc.pass_ == 1) {
// the previous frame's glow, moved with the camera (the view it was written in; half a texel per cell)
const vec4 pv = ga[hw * hh];
const vec2 sh = pv.w > 0.5 ? (vec2(float(pc.view_x), float(pc.view_y)) - pv.xy) * 0.5 : vec2(1.0e6);
vec3 acc = vec3(0.0);
for (int x = -5; x <= 5; ++x) acc += fetch(true, t + sh + vec2(float(x) * TAP, 0.0), hw, hh);
gb[q.y * hw + q.x] = vec4(acc * 0.1, 1.0);
return;
}
// pass 2
vec3 dec = vec3(0.0);
for (int y = -5; y <= 5; ++y) dec += fetch(false, t + vec2(0.0, float(y) * TAP), hw, hh);
dec *= 1.0 / 12.2;
vec3 src = vec3(0.0); // the cells of the texel: colour x gfx_glow / 1000, averaged (the cell blitting's HDR range), x 4
for (int k = 0; k < 4; ++k) {
const ivec2 c = min(q * 2 + ivec2(k & 1, k >> 1), ivec2(pc.gw - 1, pc.gh - 1));
const uint cw = cells[c.y * pc.gw + c.x];
const uint m = cell_mat(cw);
if (m == 0u) continue;
const uint g = gtab[m] & 0xFFFFu;
if ((mflags(m) & F_FIRE) != 0u) { src += fire_glow_colour(m, birth[c.y * pc.gw + c.x]); continue; } // a flame: colors_fire_glow.png by its age, as it is (the blitter writes the palette colour, not scaled by gfx_glow)
if ((cw & BURN_BIT) != 0u) { src += burning_colour(m, hash3(uint(c.x + pc.view_x), uint(c.y + pc.view_y), uint(pc.frame) + 3u)) * 0.25; continue; } // a burning cell: its colour / 4 (00722340 with 0x40)
if (g == 0u) continue;
const uint mc = mats[3u * m].x;
src += vec3(float((mc >> 16) & 0xFFu), float((mc >> 8) & 0xFFu), float(mc & 0xFFu)) / 255.0 * (float(g) / 1000.0);
}
vec3 part = vec3(0.0); // post_glow2.frag: + tex_glow_source_particles x 2.5 (the glowing particles: here the additive ones of the frame, fx_particles.comp)
for (int k = 0; k < 4; ++k) {
const ivec2 c = min(q * 2 + ivec2(k & 1, k >> 1), ivec2(pc.gw - 1, pc.gh - 1));
const uint e = entlayer[c.y * pc.gw + c.x];
if ((e >> 24) == 1u) part += vec3(float((e >> 16) & 255u), float((e >> 8) & 255u), float(e & 255u)) / 255.0; // (additive sprites and projectiles)
else if ((e >> 24) == 253u) src += vec3(float((e >> 16) & 255u), float((e >> 8) & 255u), float(e & 255u)) / 255.0 * 0.25; // a burning pixel of a body (sprites.comp): its colour / 4, as a burning cell
}
{ // the particles: all of them, added up (fx_particles.comp)
const uint gt = uint(q.y * hw + q.x) * 3u;
part += vec3(float(partglow[gt]), float(partglow[gt + 1u]), float(partglow[gt + 2u])) / 256.0;
}
const vec3 N = src * 0.25 * 4.0 + part * 0.25 * 2.5;
ga[q.y * hw + q.x] = vec4(N * 0.125 + mix(dec, N, 0.05), 1.0);
if (q.x == 0 && q.y == 0) ga[hw * hh] = vec4(float(pc.view_x), float(pc.view_y), 0.0, 1.0); // (the next frame's pass 1 reads all texels before this pass runs again)
}