Files
Gaël 9bb9797271 Adding patch notes from git, UI, and loading files from the main install path properly.
Also fixed:
* flying wands not pathing properly.
* Lantern particles offset
* Wand fire offset
* Water spray from flask
2026-10-22 14:18:49 +02:00

464 lines
34 KiB
Plaintext

#version 450
#extension GL_GOOGLE_include_directive : require
layout(local_size_x = 8, local_size_y = 8) in;
#include "common.glsl"
#include "areas.glsl"
vec4 unpack_argb(uint c) {
return vec4(float((c >> 16) & 0xFFu), float((c >> 8) & 0xFFu), float(c & 0xFFu), float(c >> 24)) / 255.0;
}
layout(std430, set = 0, binding = 13) readonly buffer TexData { uint texdata[]; }; // ARGB texels of all material textures
layout(std430, set = 0, binding = 14) readonly buffer TexInfo { uvec4 texinfo[]; }; // per material: offset, width, height, 0 = none
layout(std430, set = 0, binding = 12) readonly buffer SceneMat { uint scene_mat[]; }; // pixel scenes: material + 1
layout(std430, set = 0, binding = 15) readonly buffer BgTile { uint bgtile[]; }; // biome background tile (ARGB)
layout(std430, set = 0, binding = 16) readonly buffer SceneVis { uint scene_vis[]; }; // scene visual colours (ARGB)
layout(std430, set = 0, binding = 17) readonly buffer SceneBg { uint scene_bg[]; }; // scene backgrounds (ARGB)
layout(std430, set = 0, binding = 8) readonly buffer Tables { uint perm[]; }; // [0,512) simplex: the gradient of the procedural biomes decides where the background wall is
int ffloor(float v) { return int(floor(v)); }
uint ps(uint i) { return perm[i]; }
#include "procgrad.glsl"
layout(std430, set = 0, binding = 21) readonly buffer EntLayer { uint entlayer[]; };
struct Ent { vec4 a, box, mv, ai; ivec4 st; vec4 hp; ivec4 x; ivec4 rg; ivec4 rg2; ivec4 aux; vec4 ex; vec4 ex2; ivec4 ex3; vec4 ex4; vec4 ex5; vec4 ex6; vec4 ex7; ivec4 ex8; vec4 ex9; vec4 ex10; vec4 ex11; vec4 ex12, ex13, ex14, ex15, ex16, ex17, ex18, ex19; };
layout(std430, set = 0, binding = 22) readonly buffer Ents { Ent ents[]; };
layout(std430, set = 0, binding = 39) readonly buffer EntPrev { vec4 prevpos[]; }; // positions before the last tick // entity sprites drawn this frame (ARGB per window cell)
layout(std430, set = 0, binding = 26) readonly buffer Overlay { uint overlay[]; }; // CPU-drawn HUD (ARGB, output pixels)
struct FLight { vec4 a; vec4 b; }; // x, y, radius (world cells), strength / rgb
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; ivec4 burnN; ivec4 burnEv[256]; ivec4 reactN; ivec4 reactEv[64]; ivec4 extra; } ctrl; // extra.yzw: the Options' brightness, contrast, gamma x 10000 (main.cpp)
layout(std430, set = 0, binding = 38) readonly buffer FxLights { uint lcount; uint lpad[3]; FLight lights[512]; }; // lights of projectiles and explosions this frame
layout(std430, set = 0, binding = 44) readonly buffer Sky { uint sky[]; }; // src/world/sky.h
layout(std430, set = 0, binding = 47) readonly buffer GlowA { vec4 glowA[]; }; // the blurred glow of the materials at half resolution (glow.comp)
layout(std430, set = 0, binding = 49) readonly buffer Fog { uint fog[]; };
layout(std430, set = 0, binding = 50) readonly buffer Vis { vec4 fogvis[]; }; // line-of-sight visibility per 4x4 block of the window (fog.comp) // the explored map, one byte per 8x8 world cells (fog.comp)
layout(std430, set = 0, binding = 57) readonly buffer LitCells { uvec2 litc[]; }; // the light of the entities per cell (light_cells.comp sums lent once per cell)
layout(std430, set = 0, binding = 45) readonly buffer Lit { uvec4 lhead; vec4 lent[]; }; // the lights near the window: count, then (x, y, radius, strength), (r, g, b, 0) per light
uvec4 sky_img(int i) { return uvec4(sky[4 + 3 * i], sky[5 + 3 * i], sky[6 + 3 * i], 0u); }
vec4 sky_texel(int i, int x, int y) { // ARGB texel of image i (wrapping in x, clamped in y; transparent outside)
uvec4 h = sky_img(i);
if (y < 0 || y >= int(h.z)) return vec4(0.0);
x = ((x % int(h.y)) + int(h.y)) % int(h.y);
return unpack_argb(sky[h.x + uint(y) * h.y + uint(x)]);
}
vec3 sky_table(float tod, int row) { // parallax_colors.bmp: the colour of a row over the day (x = time of day)
return sky_texel(0, int(fract(tod) * 511.0), row).rgb;
}
// The sky behind the terrain (the parallax of 00888c80 / 008897e0 / 00889cb0, docs/BACKLOG.md 22): the colour of layer k is row 20 k of parallax_colors.bmp at
// x = time of day (lerped); the images are masks tinted by it. Layer 0 (weather_background, white) fills the screen with row 0; 1 (parallax_background x row
// 20, alpha gradient_sky_alpha) and 2 (parallax_sunset_line x row 40, alpha the sunset alpha) are repeated from the top of the screen; then in the order 3, 10, 7,
// 4, 9, 6, 5, 8 the image of layer k repeats horizontally from x = -(camera x / depth) + rec.x, its top at y = camera y / depth - rec.y - parallax_offset_y
// (virtual pixels from the top of the screen, at least screen height - image height), tinted by its row and its alpha. Depths 50, 30, 25, 25, 40, 20, 35, 45
// (layers 3..10); rec.x / rec.y / alphas are the host's (sky words 48..: cloud 1 x, cloud 1 y, cloud 2 y, mountains 1 y, mountains 2 y, storm y, storm alpha,
// gradient alpha, sunset alpha, parallax_offset_y). (The rain particles, layer 11, are the GameEmitRainParticles of init.lua.)
vec4 sky_tex(int i, vec2 t) { // image i at texel t, repeating in x, transparent outside in y
const uvec4 h = sky_img(i);
if (h.y == 0u || t.y < 0.0 || t.y >= float(h.z)) return vec4(0.0);
const int x = ((int(floor(t.x)) % int(h.y)) + int(h.y)) % int(h.y);
return unpack_argb(sky[h.x + uint(int(t.y)) * h.y + uint(x)]);
}
vec4 sky_tex_wrap(int i, vec2 t) { // repeating in both directions
const uvec4 h = sky_img(i);
if (h.y == 0u || h.z == 0u) return vec4(0.0);
return sky_tex(i, vec2(t.x, float(((int(floor(t.y)) % int(h.z)) + int(h.z)) % int(h.z))));
}
vec3 sky_row(float tod, int row) { // 008897e0: index (w - 1) x time, lerped to the next column
const uvec4 h = sky_img(0);
const float f = (float(h.y) - 1.0) * fract(tod);
const int i0 = int(floor(f));
const vec3 a = sky_texel(0, i0, row).rgb, b = sky_texel(0, min(i0 + 1, int(h.y) - 1), row).rgb;
return mix(a, b, f - float(i0));
}
float skp(int k) { return uintBitsToFloat(sky[48u + uint(k)]); }
// TODO(to-do #18): layer 0 shader variant, the biome source of gradient / sunset alpha, the camera origin
vec3 sky_colour(vec2 w, vec2 cam, float tod, float tsec, vec2 scr) {
const vec2 camTL = cam - vec2(213.5, 121.0); // (the virtual screen: 427 x 242 virtual pixels, 1 per world pixel)
const vec2 sv = w - camTL;
vec3 col = sky_row(tod, 0);
{ const float ga = skp(7);
if (ga > 0.0) { const vec4 c = sky_tex_wrap(1, sv); col = mix(col, c.rgb * sky_row(tod, 20), c.a * ga); } }
{ const float sa = skp(8);
if (sa > 0.0) { const vec4 c = sky_tex_wrap(2, sv); col = mix(col, c.rgb * sky_row(tod, 40), c.a * sa); } }
const int order[8] = int[8](3, 10, 7, 4, 9, 6, 5, 8);
const float depth[11] = float[11](1.0, 1.0, 1.0, 50.0, 30.0, 25.0, 25.0, 40.0, 20.0, 35.0, 45.0);
for (int o = 0; o < 8; ++o) {
const int k = order[o];
float rx = 0.0, ry = 0.0, a = 1.0;
if (k == 3) { rx = skp(0); ry = skp(1); }
else if (k == 4) ry = skp(2);
else if (k == 5 || k == 6) ry = skp(3);
else if (k == 7) ry = skp(4);
else { ry = skp(5); a = skp(6); }
if (a <= 0.0) continue;
const uvec4 h = sky_img(k);
const float top = max(camTL.y / depth[k] - ry - skp(9), 242.0 - float(h.z));
const vec4 c = sky_tex(k, vec2(sv.x - camTL.x / depth[k] + rx, sv.y - top));
if (c.a > 0.0) col = mix(col, c.rgb * sky_row(tod, 20 * k), c.a * a);
}
return col;
}
// Lighting of the foreground (cells and sprites), after post_final.frag: the lights of the entities (LightComponent, cubic falloff), the glow of the materials, the light of
// the sky (the sky exposure of the cell: open sky, the depth below the first solid cell, the sides of an opening; times the colour of the time of day), gamma, then the
// fog of war (unexplored cells are black unless the sky lights them) and the baseline 0.35 - sky ambient of the explored map. The background (sky, cave walls) is not lit.
// sky[3]: bit 0 lighting on (F4), bit 2 fog of war off (F6), bit 3 glow off (F7).
const int FOG_TW = 1152, FOG_TH = 896, FOG_OX = 18432, FOG_OY = 14336; // the fog map of fog.comp: 32 x 32 pixel texels from the world origin, the clear amount (255 - fog)
float fog_clear(ivec2 t) {
if (t.x < 0 || t.y < 0 || t.x >= FOG_TW || t.y >= FOG_TH) return 0.0;
return float(min(fog[t.y * FOG_TW + t.x], 255u)) / 255.0;
}
vec2 gCpf = vec2(0.0); // window position of the pixel in cells, at sub-cell precision
float bm_dust_r(vec2 w) { // the biome modifier dust_amount of the map tile under w (biome_mods.glsl's table, read through perm)
const uint n = mats[mats.length() - 1u].w;
const uint fb = 1024u + 9u * n + 132u + n;
const int bw = int(perm[fb]), bh = int(perm[fb + 1u]);
const ivec2 t = ivec2(floor(w / 512.0)) + ivec2(35, 14);
if (bw <= 0 || t.x < 0 || t.y < 0 || t.x >= bw || t.y >= bh) return 0.0;
return uintBitsToFloat(perm[fb + 2u + 8192u + 1u + 64u * 6u + 1u + 16u * 3u + 8u * 3272u + 4u * uint(t.y * bw + t.x) + 3u]);
}
float perlin_tex(vec2 uv) { // tex_perlin_noise (data/shaders/noise_perlin.png, sky image 11): bilinear, repeating; uv in texture units (1 = the image)
const uvec4 h = sky_img(11);
if (h.y == 0u || h.z == 0u) return 0.5;
const vec2 q = uv * vec2(float(h.y), float(h.z)) - 0.5;
const ivec2 e = ivec2(floor(q));
const vec2 g = q - vec2(e);
const int W = int(h.y), H = int(h.z);
float t[4];
for (int k = 0; k < 4; ++k) {
const int x = (((e.x + (k & 1)) % W) + W) % W, y = (((e.y + (k >> 1)) % H) + H) % H;
t[k] = float((sky[h.x + uint(y) * h.y + uint(x)] >> 16) & 255u) / 255.0;
}
return mix(mix(t[0], t[1], g.x), mix(t[2], t[3], g.x), g.y);
}
float sky_cell(ivec2 b) { // one block of the exposure grid (sky words: [7] = its offset, then the first block column / row, then the values)
const uint o = sky[60];
const ivec2 b0 = ivec2(int(sky[o]), int(sky[o + 1u]));
const ivec2 q = clamp(b - b0, ivec2(0), ivec2(1024 / 64 + 1, 576 / 64 + 1));
return uintBitsToFloat(sky[o + 2u + uint(q.y * (1024 / 64 + 2) + q.x)]);
}
float sky_bilinear(vec2 w) { // texel centres at the block centres
const vec2 q = w / 64.0 - 0.5;
const ivec2 e = ivec2(floor(q));
const vec2 g = q - vec2(e);
return mix(mix(sky_cell(e), sky_cell(e + ivec2(1, 0)), g.x), mix(sky_cell(e + ivec2(0, 1)), sky_cell(e + ivec2(1, 1)), g.x), g.y);
}
float sky_amount(vec2 w) { // post_final.frag HIQ: (centre + 0.25 x the four neighbours) x 0.5
return (sky_bilinear(w) + 0.25 * (sky_bilinear(w - vec2(64.0, 0.0)) + sky_bilinear(w + vec2(64.0, 0.0)) + sky_bilinear(w - vec2(0.0, 64.0)) + sky_bilinear(w + vec2(0.0, 64.0)))) * 0.5;
}
float fog_bilinear(vec2 w) { // the fog map between the texel centres (32 n + 16), as a linear-filtered tex_fog
const vec2 q = w / 32.0 - 0.5;
const ivec2 e = ivec2(floor(q));
const vec2 g = q - vec2(e);
return mix(mix(fog_clear(e), fog_clear(e + ivec2(1, 0)), g.x), mix(fog_clear(e + ivec2(0, 1)), fog_clear(e + ivec2(1, 1)), g.x), g.y);
}
float explored(ivec2 cp, ivec2 wp) { // 0 fog .. 1 clear: the fog map read bilinearly between the texel centres (32 n + 16), as post_final.frag samples tex_fog
if ((sky[3] & 4u) != 0u) return 1.0;
const vec2 ww = gCpf + vec2(float(pc.view_x), float(pc.view_y));
const vec2 w = ww + vec2(FOG_OX, FOG_OY);
// post_final.frag HIQ: the centre plus four samples at +-0.25 texel diagonally and four at 0.75 texel on the axes, / 9 (tex_fog: FOG_TEX_SIZE 64 at zoom 1, 32 px texels)
const float clr = (fog_bilinear(w) + fog_bilinear(w + vec2(8.0, -8.0)) + fog_bilinear(w + vec2(-8.0, -8.0)) + fog_bilinear(w + vec2(-8.0, 8.0)) + fog_bilinear(w + vec2(8.0, 8.0))
+ fog_bilinear(w - vec2(24.0, 0.0)) + fog_bilinear(w + vec2(24.0, 0.0)) + fog_bilinear(w - vec2(0.0, 24.0)) + fog_bilinear(w + vec2(0.0, 24.0))) * 0.1111111;
// the holes of the frame (main.cpp: fog_of_war_hole sprites): post_final.frag fog x (1 - the hole alpha); the disc of explosion_flare_fog_of_war_hole.png ~ exp(-4.5 t^2)
const uint n = mats[mats.length() - 1u].w;
const uint hb = 1024u + 9u * n + 132u + n + 2u + 8192u;
const int nh = min(int(uintBitsToFloat(perm[hb])), 64);
const uint ib = hb + 1u + 64u * 6u + 1u + 16u * 3u;
float hole = 0.0;
for (int k = 0; k < nh; ++k) { // the hole sprite's alpha (its animation frame by the age), bilinear (smooth_filtering), scaled about its offset
const uint o = hb + 1u + uint(k) * 6u;
const vec2 hp = vec2(uintBitsToFloat(perm[o]), uintBitsToFloat(perm[o + 1u]));
const float sc = max(uintBitsToFloat(perm[o + 2u]), 0.01);
const int img = int(uintBitsToFloat(perm[o + 4u]));
float a = 0.0;
if (img >= 0 && img < 8) {
const uint ia = ib + uint(img) * 3272u;
const float iw = uintBitsToFloat(perm[ia]), ih = uintBitsToFloat(perm[ia + 1u]);
const vec2 sp = (ww - hp) / sc + vec2(uintBitsToFloat(perm[ia + 2u]), uintBitsToFloat(perm[ia + 3u])) - 0.5;
if (sp.x < -1.0 || sp.y < -1.0 || sp.x > iw || sp.y > ih) continue;
const uint nf = max(perm[ia + 4u] & 255u, 1u), wt = max(perm[ia + 4u] >> 8, 1u);
const uint fr = (uint(uintBitsToFloat(perm[o + 5u])) / wt) % nf;
const ivec2 s0 = ivec2(floor(sp));
const vec2 f = sp - vec2(s0);
for (int q = 0; q < 4; ++q) {
const ivec2 s = s0 + ivec2(q & 1, q >> 1);
if (s.x < 0 || s.y < 0 || s.x >= int(iw) || s.y >= int(ih)) continue;
a += float(perm[ia + 5u + fr * 1089u + uint(s.y * int(iw) + s.x)]) / 255.0 * ((q & 1) != 0 ? f.x : 1.0 - f.x) * ((q >> 1) != 0 ? f.y : 1.0 - f.y);
}
} else { // (no image: a soft disc of 16 px x the scale)
const float t = distance(ww, hp) / (16.0 * sc);
a = t < 1.0 ? exp(-4.5 * t * t) : 0.0;
}
hole = max(hole, uintBitsToFloat(perm[o + 3u]) * a);
}
return 1.0 - (1.0 - clr) * (1.0 - min(hole, 1.0));
}
#define FPAL_TABLE perm
// ---- 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
vec3 glow_at(ivec2 cp) { // bilinear in the half-resolution glow
if ((sky[3] & 8u) != 0u) return vec3(0.0);
const int hw = (pc.gw + 1) / 2, hh = (pc.gh + 1) / 2;
const vec2 q = (vec2(cp) + 0.5) * 0.5 - 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 = clamp(q0 + ivec2(k & 1, k >> 1), ivec2(0), ivec2(hw - 1, hh - 1));
r += glowA[s.y * hw + s.x].rgb * (((k & 1) != 0 ? f.x : 1.0 - f.x) * ((k >> 1) != 0 ? f.y : 1.0 - f.y));
}
return max(vec3(0.0), r - 0.008); // (post_final.frag)
}
float gFog = 1.0;
float gSky = 0.0, gLum = 0.0; // (the squared sky amount and the luminosity of the lights of the last lighting() call: the dust)
vec3 gGlow = vec3(0.0);
vec3 lighting(ivec2 cp, ivec2 wp, uint emissive) {
gFog = 1.0; gGlow = vec3(0.0); gSky = 1.0; gLum = 1.0;
if ((sky[3] & 1u) == 0u) return vec3(1.0);
gGlow = glow_at(cp);
if (emissive != 0u) { gFog = explored(cp, wp); return vec3(1.0); }
const uvec2 lc = litc[cp.y * pc.gw + cp.x]; // (the sum over the lights near the window, light_cells.comp: a cell's light is the same for all its pixels)
vec3 L = vec3(unpackHalf2x16(lc.x), unpackHalf2x16(lc.y).x);
// post_final.frag: lights = pow(tex_lights x 0.8, 1.5) + glow; the sky: sky_light_color x sky_ambient_amount, the exposure grid (tex_skylight, 64-pixel blocks of the
// coarse map, main.cpp) read bilinearly with the HIQ blur (0.5 centre + 0.125 x the four neighbours one block away); lights = max(lights, sky), at most 1, gamma 1 / 2.2;
// then the fog of war: s = the sky amount faded out between y 250 and 350, fog = min(1, max(2 x 1.4 (0.6, 0.5, 0.45) max(0, 1 - fog - sqrt(s)), s)),
// lights x fog + max(0.35 - s, 0) x fog
vec3 lights = pow(max(L * 0.8, vec3(0.0)), vec3(1.5)) + gGlow;
float amount = sky_amount(vec2(wp) + 0.5);
amount *= amount; // (post_final.frag: sky_ambient_amount *= sky_ambient_amount after the sample)
const float tod = uintBitsToFloat(sky[0]);
const vec3 topc = sky_table(tod, 4);
const float f = smoothstep(0.12, 0.5, dot(topc, vec3(0.299, 0.587, 0.114)));
// TODO(to-do #17): sky_light_color from the PostFx keyframes (00890330)
const vec3 skyc = mix(vec3(0.12, 0.16, 0.30), vec3(1.0), f); // (sky_light_color: the engine's day keyframes are not traced yet; this follows the sky's brightness)
lights = min(max(lights, skyc * amount), vec3(1.0));
lights = pow(lights, vec3(1.0 / 2.2));
const float vis = explored(cp, wp);
const float s = amount * (1.0 - clamp((float(wp.y) - 250.0) / 100.0, 0.0, 1.0));
vec3 fog = 1.4 * vec3(0.6, 0.5, 0.45) * max(0.0, 1.0 - (1.0 - vis) - sqrt(s));
fog = min(vec3(1.0), max(2.0 * fog, vec3(s)));
L = lights * fog + max(0.35 - s, 0.0) * fog;
gSky = amount; gLum = sqrt(min(1.0, dot(L, vec3(0.299, 0.587, 0.114))));
gFog = max(fog.r, max(fog.g, fog.b));
return min(L, vec3(1.35));
}
// The picture is made at the game's own resolution, one pixel per world cell, then scaled up to the window (Noita draws its world at a fixed internal
// resolution: the window size changes only the final scaling). pass_ 1, the world pass: one thread per cell of the view, the colour of the cell (background,
// material, lights, fog of war, entity layer, projectile lights, grading, glow, blindness) into wcell. pass_ 0, the output pass: one thread per window pixel,
// the cell under it (camera shake and the camera between two ticks at sub-cell precision), the player at its sub-cell position, the low-health vignette, the HUD.
layout(std430, set = 0, binding = 60) writeonly buffer ClipRaw { uint clipraw[]; }; // the window picture without the HUD (the replay recorder)
layout(std430, set = 0, binding = 59) buffer WorldCells { uvec4 wcell[]; }; // per cell: the colour (rgba8), the light Lv (half r g, half b + the fog amount)
float gScreenY = 0.0;
vec3 world_cell(ivec2 cp, out vec3 Lv) {
uint c = cells[cp.y * pc.gw + cp.x];
float t = gScreenY; // (the screen row of the cell centre, for the background gradient)
vec3 bg = mix(vec3(0.05, 0.05, 0.09), vec3(0.02, 0.02, 0.04), t);
ivec2 wp0 = cp + ivec2(pc.view_x, pc.view_y); // world cell
const int bai = area_of_cell(wp0);
bool bgOk = true, bgDrawn = false;
{ // a procedural (surface) biome has no background wall: the sky and the surface caves of the hills show none
const uint trec = topo_rec(wp0);
if (trec != 0xFFFFFFFFu && (atab[trec] & 1u) != 0u) bgOk = false;
}
if (bgOk && bai >= 0 && arec(bai, 12) > 0) {
// the biome's background wall, tiled in world space
const int bw = arec(bai, 12), bh = arec(bai, 13);
uint tx = uint(((wp0.x % bw) + bw) % bw), ty = uint(((wp0.y % bh) + bh) % bh);
vec4 bc = unpack_argb(bgtile[uint(arec(bai, 11)) + ty * uint(bw) + tx]);
bg = mix(bg, bc.rgb, bc.a);
}
uint sidx = 0xFFFFFFFFu;
for (int a = 0; a < int(atab[0]); ++a) { // the first area whose scene has a material here; the first scene background
int si = scene_index(a, wp0);
if (si < 0) continue;
if (sidx == 0xFFFFFFFFu && scene_mat[si] != 0u) sidx = uint(si);
uint sb = scene_bg[si];
if (sb != 0u) { vec4 bc = unpack_argb(sb); bg = mix(bg, bc.rgb, bc.a); bgDrawn = bgDrawn || bc.a > 0.01; }
}
const uint trecSky = topo_rec(wp0);
bool skyPix = false;
if (sky[2] != 0u && trecSky != 0xFFFFFFFFu && (atab[trecSky] & 0x1000000u) != 0u && !bgDrawn && ((atab[trecSky] & 0x2000000u) == 0u || cp.y < int(sky[64 + cp.x])) && cell_mat(c) == 0u) { // (the scene biomes show the sky wherever no scene background is drawn; the procedural ones (hills) have caves with no wall behind: there the column must be open) // open sky: no wall behind, nothing solid above in the window
const vec2 cam = vec2(float(pc.view_x) + float(pc.gw) * 0.5, float(pc.view_y) + float(pc.gh) * 0.5);
skyPix = true;
bg = sky_colour(vec2(wp0), cam, uintBitsToFloat(sky[0]), uintBitsToFloat(sky[1]), vec2(0.0, float(wp0.y - pc.view_y) - float(pc.gh) * 0.5));
}
if ((sky[3] & 2u) != 0u && cell_mat(c) == 0u) { // F5: the sky mask: green = the sky is drawn, red = a solid cell above in the window column (the row of that cell is yellow), blue = not an outdoor biome, magenta = a scene background is drawn
const int top = int(sky[64 + cp.x]);
vec3 dbg = vec3(0.0);
if (!(trecSky != 0xFFFFFFFFu && (atab[trecSky] & 0x1000000u) != 0u)) dbg = vec3(0.0, 0.0, 0.8);
else if (bgDrawn) dbg = vec3(0.8, 0.0, 0.8);
else if (wp0.y >= 150) dbg = vec3(0.8, 0.8, 0.0);
else if ((atab[trecSky] & 0x2000000u) != 0u && cp.y >= top) dbg = vec3(0.8, 0.0, 0.0);
else dbg = vec3(0.0, 0.7, 0.0);
bg = mix(bg, dbg, 0.5);
}
{ // the weather fog (post_final.frag): the background mixes toward FOG_BACKGROUND 0.7 (FOG_BACKGROUND_NIGHT 0.2 by night_amount) by fog_amount_background =
// WorldStateComponent.fog (00c42d30 eases it toward max(fog_target, fog_target_extra); init.lua's weather sets fog_target_extra 0.3 .. 0.85 when it rains or snows)
const float wfog = uintBitsToFloat(sky[61]);
if (wfog > 0.0) {
const float tod = uintBitsToFloat(sky[0]);
const float night = clamp((0.55 - abs(fract(tod) - 0.57) - 0.4) * 10.0, 0.0, 1.0);
bg = mix(bg, vec3(mix(0.7, 0.2, night)), wfog);
}
}
if (sky[63] != 0u && !skyPix) bg = vec3(0.022, 0.02, 0.028); // the spell test room: a dark background
vec3 rgb = bg;
uint m = cell_mat(c);
gCpf = vec2(cp) + 0.5;
Lv = lighting(cp, wp0, m != 0u && ((mats[3u * m + 2u].z >> 17) & 1u) != 0u ? 1u : 0u); // fire and lava light themselves
rgb = skyPix ? bg : bg * gFog; // (the fog of war darkens the background too; the sky lights it)
if (m != 0u) {
vec4 col = unpack_argb(mats[3u * m].x);
float shade = 1.0 + (float((c >> 16) & 0xFFu) / 255.0 - 0.5) * 0.18; // per-cell variation
uvec4 ti = texinfo[m];
if (ti.y != 0u) { // the game's material texture, tiled in world space (one texel per cell)
ivec2 wp = cp + ivec2(pc.view_x, pc.view_y);
uint tx = uint(((wp.x % int(ti.y)) + int(ti.y)) % int(ti.y));
uint ty = uint(((wp.y % int(ti.z)) + int(ti.z)) % int(ti.z));
vec4 tc = unpack_argb(texdata[ti.x + ty * ti.y + tx]);
col = vec4(tc.rgb, col.a);
shade = 1.0;
}
if ((mflags(m) & F_FIRE) != 0u) { col = vec4(fire_colour(m, birth[cp.y * pc.gw + cp.x]), 1.0); shade = 1.0; } // a flame by its age (00724580: colors_fire.png, opaque)
if (sidx != 0xFFFFFFFFu && scene_mat[sidx] == m + 1u && scene_vis[sidx] != 0u) { // pixel-scene visual image // pixel-scene visual image
vec4 vc = unpack_argb(scene_vis[sidx]);
col = vec4(vc.rgb, col.a);
shade = 1.0;
}
if ((c & 0x80000000u) != 0u && (mflags(m) & F_STATIC) != 0u) col.rgb = mix(col.rgb, vec3(0.42, 0.03, 0.03), 0.6); // stained with blood (fx_particles.comp)
if ((c & (FROZEN_BIT | BURN_BIT)) == FROZEN_BIT && (mflags(m) & F_STATIC) != 0u && (mflags(m) & F_FIRE) == 0u) col.rgb = mix(col.rgb, vec3(0.72, 0.88, 1.0), 0.55); // the frozen stain (edits.comp mode 12; the icy look is an approximation of the engine's stain colour)
if ((mflags(m) & (F_STATIC | F_LIQCELL)) == F_STATIC && ext[cp.y * pc.gw + cp.x].x >= 8.0) // a solid that burnt: 0070e060 takes one off every channel per eight of its burn age (burn.comp, ext.x), down to 0
col.rgb = max(col.rgb - vec3(floor(ext[cp.y * pc.gw + cp.x].x / 8.0) / 255.0), vec3(0.0));
if ((mflags(m) & F_STATIC) != 0u && (c & BURN_BIT) == 0u && ((c >> 28) & 7u) != 0u && max(col.r, max(col.g, col.b)) > 18.5 / 255.0) col.rgb = max(col.rgb * (1.0 - float((c >> 28) & 7u) / 7.0), vec3(19.0 / 255.0)); // burnt by explosions (0070e760: every channel x (1 - s), at least 0x13; the level in bits 28-30, explode_rays.comp)
if ((c & BURN_BIT) != 0u) { // a burning cell: a random pixel of colors_lava.bmp every frame (00724580), opaque
col = vec4(burning_colour(m, hash3(uint(cp.x + pc.view_x), uint(cp.y + pc.view_y), uint(pc.frame))), 1.0);
shade = 1.0;
}
vec3 fgc = col.rgb * shade * Lv;
{ // the dust (post_final.frag): the biome modifier dust_amount (tex_fog .g) hazes the lit foreground toward FOG_FOREGROUND 0.6 (night or underground 0.2):
// amount = mix(dust, fog_amount_foreground (the weather fog, sky word 61), sky) x max(sky, dust x luminosity x min(1, 2 perlin^2)); night_amount = clamp((0.55 - |tod - 0.57| - 0.4) x 10, 0, 1)
const vec2 wq = vec2(cp + ivec2(pc.view_x, pc.view_y));
const float dust = bm_dust_r(wq), wfog = uintBitsToFloat(sky[61]);
if (dust > 0.0 || (wfog > 0.0 && gSky > 0.0)) {
const float tod = uintBitsToFloat(sky[0]);
const float night = clamp((0.55 - abs(fract(tod) - 0.57) - 0.4) * 10.0, 0.0, 1.0);
const float pn = perlin_tex(wq * 0.0004 + vec2(0.0, uintBitsToFloat(sky[1]) * 0.005)); // noise_perlin2 = texture2D(tex_perlin_noise, world_pos * 0.0004 + (0, noise_time * 0.005))
const float amt = mix(dust, wfog, gSky) * max(gSky, dust * gLum * min(1.0, pn * pn * 2.0));
fgc = mix(fgc, vec3(mix(0.6, 0.2, max(night, 1.0 - gSky))), amt);
}
}
rgb = mix(bg, fgc, col.a);
}
uint ec = entlayer[cp.y * pc.gw + cp.x];
const bool playerPix = (ec >> 24) == 254u; // the player is drawn below, at its sub-cell position (the sprite layer itself has whole-cell resolution)
if (playerPix) ec = 0u;
if ((ec >> 24) >= 1u && (ec >> 24) <= 3u) rgb += unpack_argb(ec | 0xFF000000u).rgb * gFog; // (the fog of war hides them: post_final multiplies the light buffer they go to by fog_of_war) // additive light (alpha 1: additive projectiles and animations, 2: particles, 3: additive sprites such as glowing eyes)
else if ((ec >> 24) != 0u) { vec4 e4 = unpack_argb(ec); rgb = mix(rgb, e4.rgb * Lv, e4.a); }
return rgb;
}
vec3 post_cell(vec3 rgb, ivec2 cp, vec3 Lv) {
{ // light of projectiles, flashes and explosions: additive glow
vec2 wpos = vec2(cp + ivec2(pc.view_x, pc.view_y)) + 0.5; // (the cell centre: the world is drawn at one pixel per cell)
for (uint i = 0u; i < min(lcount, 512u); ++i) {
float d = distance(wpos, lights[i].a.xy) / lights[i].a.z;
if (d < 1.0) rgb += lights[i].b.rgb * ((1.0 - d) * (1.0 - d) * lights[i].a.w) * gFog;
}
}
{ // the color grading of the biome modifiers (post_final.frag: mix toward the grey by .a, then times .rgb; main.cpp moves it toward the camera's biome; the last 4 words of Tables)
const uint n = mats[mats.length() - 1u].w;
const uint gi = 1024u + 9u * n + 132u + n + 2u + 8192u + 1u + 64u * 6u + 1u + 16u * 3u + 8u * 3272u + 8192u * 4u + 2u * n;
const vec4 cg = vec4(uintBitsToFloat(perm[gi]), uintBitsToFloat(perm[gi + 1u]), uintBitsToFloat(perm[gi + 2u]), uintBitsToFloat(perm[gi + 3u]));
if (cg != vec4(1.0, 1.0, 1.0, 0.0) && cg != vec4(0.0)) { rgb = mix(rgb, vec3((rgb.r + rgb.g + rgb.b) * 0.3333), cg.a); rgb *= cg.rgb; }
}
if ((sky[3] & 1u) != 0u && (gGlow.r + gGlow.g + gGlow.b) > 0.002) { // the glow, blended like post_final.frag: max(color + glow * 0.6 - 0.6 * lights, screen(color, glow))
const vec3 g = gGlow * min(vec3(1.0), 2.8 * vec3(0.6, 0.5, 0.45) * gFog); // (post_final.frag: fog_of_war = min(1, 2 x 1.4 x (0.6, 0.5, 0.45) x (1 - fog)): the glow keeps its strength until the fog is thick)
rgb = max(rgb + g * 0.6 - 0.6 * Lv, clamp(rgb + g - rgb * g, 0.0, 1.0));
}
rgb *= 1.0 - clamp(ctrl.hand2.w, 0.0, 1.0); // BLINDNESS (00b015d0 case 9): a black screen over the world, below the HUD
return rgb;
}
void main() {
// camera shake of explosions: the whole picture (not the HUD) is offset by a random amount
const float scl = float(pc.scale) / 16.0; // output pixels per cell (fixed point, 1/16)
float amp = float(lpad[0]) * 0.001 * scl;
ivec2 so = amp > 0.5 ? ivec2((vec2(float(hash3(uint(pc.frame), 1u, 77u) & 255u), float(hash3(uint(pc.frame), 2u, 77u) & 255u)) / 255.0 - 0.5) * 2.0 * amp) : ivec2(0);
so += ivec2(round(ctrl.view.xy * scl)); // the camera is drawn between its last two ticks, at sub-cell precision (host: view.xy = displayed camera - centre of the window, in cells)
const ivec2 vis = ivec2((vec2(pc.gw, pc.gh) * scl - vec2(pc.ow, pc.oh)) * 0.5); // the window is the centre of the simulated grid (zoomed)
if (pc.pass_ == 1) { // the world pass: the cells from one before the window's first to past its last (main.cpp dispatches ow / scl + 4 by oh / scl + 4)
const ivec2 cp = ivec2(floor(vec2(so + vis) / scl)) - 1 + ivec2(gl_GlobalInvocationID.xy);
if (cp.x < 0 || cp.y < 0 || cp.x >= pc.gw || cp.y >= pc.gh) return;
gScreenY = ((float(cp.y) + 0.5) * scl - float(so.y + vis.y)) / float(pc.oh);
vec3 Lv;
vec3 rgb = world_cell(cp, Lv);
rgb = post_cell(rgb, cp, Lv);
wcell[cp.y * pc.gw + cp.x] = uvec4(packUnorm4x8(vec4(clamp(rgb, 0.0, 1.0), 1.0)), packHalf2x16(Lv.rg), packHalf2x16(vec2(Lv.b, gFog)), 0u);
return;
}
ivec2 p = ivec2(gl_GlobalInvocationID.xy);
if (p.x >= pc.ow || p.y >= pc.oh) return;
ivec2 cp = clamp(ivec2(floor(vec2(p + so + vis) / scl)), ivec2(0), ivec2(pc.gw - 1, pc.gh - 1));
vec3 rgb = unpackUnorm4x8(wcell[cp.y * pc.gw + cp.x].x).rgb; // (only the colour: the light is read for the player's pixels)
if (ctrl.inp2.y != 0 && (ents[0].st.w & 8) == 0) { // (dead: the ragdoll of 00abb7d0 is the body, main.cpp onCreatureDeath(0, true))
vec2 P = ents[0].a.xy;
const vec2 pv = prevpos[0].xy;
if (distance(pv, P) < 10.0) P = mix(pv, P, ctrl.view.z);
const ivec2 cq = clamp(ivec2(floor(vec2(p + so + vis) / scl - (P - floor(P)))), ivec2(0), ivec2(pc.gw - 1, pc.gh - 1));
const uint pe = entlayer[cq.y * pc.gw + cq.x];
if ((pe >> 24) == 254u) { // the player's pixel: lit like the cell under it, then the cell's post effects
const uvec4 wc = wcell[cp.y * pc.gw + cp.x];
const vec3 Lv = vec3(unpackHalf2x16(wc.y), unpackHalf2x16(wc.z).x);
gFog = unpackHalf2x16(wc.z).y; gGlow = glow_at(cp);
rgb = post_cell(unpack_argb(pe | 0xFF000000u).rgb * Lv, cp, Lv);
}
}
// TODO(to-do #20): damage_flash_interpolation, additive_overlay_color / overlay_color
{ // the low health indicator (post_final.frag): + LOW_HEALTH_INDICATOR_COLOR (0.7, 0.1, 0) x (1.3 x the distance from the centre)^4 x low_health_indicator_alpha (sky word 62)
const float lh = uintBitsToFloat(sky[62]);
if (lh > 0.0) {
float a = length(vec2(p) / vec2(float(pc.ow), float(pc.oh)) - 0.5) * 1.3;
a *= a; a *= a;
rgb += vec3(0.7, 0.1, 0.0) * a * lh;
}
}
if (ctrl.extra.y != 0 || ctrl.extra.z != 0 || ctrl.extra.w != 0) { // the Options' sliders (post_final.frag: + brightness, (c - 0.5) x contrast + 0.5, pow(c, gamma), clamped; 1 + the slider for the last two)
rgb += float(ctrl.extra.y) * 1e-4;
rgb = (rgb - 0.5) * (1.0 + float(ctrl.extra.z) * 1e-4) + 0.5;
rgb = pow(max(rgb, vec3(0.0)), vec3(1.0 + float(ctrl.extra.w) * 1e-4));
rgb = clamp(rgb, 0.0, 1.0);
}
{ // the picture without the HUD for the replay recorder (the game's GIFs have no UI): clip_half.comp / main.cpp copy it
const uvec3 q0 = uvec3(clamp(rgb, 0.0, 1.0) * 255.0 + 0.5);
clipraw[p.y * pc.ow + p.x] = 0xFF000000u | (q0.r << 16) | (q0.g << 8) | q0.b;
}
const uint ovTile = uint((p.y >> 5) * ((pc.ow + 31) >> 5) + (p.x >> 5)); // the HUD overlay: a mask word per 32 x 32 tile, then 1024 words per tile (main.cpp kOvlTiles)
const uint ov = overlay[ovTile] != 0u ? overlay[3600u + ovTile * 1024u + uint((p.y & 31) * 32 + (p.x & 31))] : 0u;
if ((ov >> 24) != 0u) { vec4 o4 = unpack_argb(ov); rgb = mix(rgb, o4.rgb, o4.a); }
uvec3 q = uvec3(clamp(rgb, 0.0, 1.0) * 255.0 + 0.5);
pixels[p.y * pc.ow + p.x] = 0xFF000000u | (q.r << 16) | (q.g << 8) | q.b; // BGRA8 in memory
}