saumwacht-godot/scripts/luneraria/view/materials/lune_rune.gdshader
Martin Stickelbroeck 9f62e30377 G2: Ancients, Türme, Bossgrotte und Superboss; macOS- und Windows-ARM64-Pakete
Mondherz-Ancients (Saumkern-Kristall als Herz, Sichelklingen, Ringe, Krone,
Teamwappen, Energieadern) mit den Stufen stabil/instabil/kritisch, kurzem
Zusammenbruch und bleibender Ruine; der Sieg wartet nie auf die Grafik.
Turmfamilie aus einem Blender-Generator: Grenzwacht (Tower 1), Bastion
(Tower 2), Kernwacht (Basisturm in der Formensprache des Ancients), mit
Schadensstufen, Einsturz, Ruine und sichtbarem Angriff (Zielring, Aufladen,
Bolzen, Einschlag) - der Schaden bleibt vollständig in der Simulation.
Bossgrotte Mondschlund mit Tor, Treppe in die Tiefe, Altar, Säulen, Felsen
und fünf Zuständen; der Generator hält Abstand zur Top-Lane, die Höhle
braucht eine kleine Geländesenke (nur Darstellung). Superboss Schlundkrone
mit 24 Knochen und 11 Clips (inkl. Brüllen und Sieg), Adern in Teamfarbe,
dasselbe Modell für Grotte und Lane. Gemeinsame LN-Materialfamilie (Stein,
Metall, Kristall, Rune, Stoff, Mondwasser, Kreaturenhaut), Occlusion-Fade
ohne dithernde Schatten. Keine Regeländerung.

Geprüft: verify_g2 245/245, verify_g1 443/443, Regeln 91/91 und 73/73,
Stress 17/17, Balance 40 Partien identisch mit G1, verify_m1 38/38,
verify_m2 14/14, Netz 43/43 und Loopback 12/12, Legacy 209 und 463.
21 Ansichten vorher/nachher (1920x1080), Videos Turmkampf und Bossbegegnung,
Leistung G1/G2 auf demselben Mac (+0,5 ms in vollen Kampfansichten).
Pakete 0.17.0-g2 für macOS (Universal, ad-hoc signiert), Windows x64/ARM64
und Linux; macOS nativ und unter Rosetta, beide Windows-Pakete in Windows 11
ARM (Parallels, OpenGL-Fallback) mit --lune-smoke bestanden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 14:12:49 +02:00

105 lines
4.4 KiB
Text

shader_type spatial;
render_mode diffuse_burley, specular_schlick_ggx;
// Luneraria RUNE: carved glyph friezes and panels. One glyph per UV cell (u), drawn from
// strokes chosen by the cell hash; the carrier is dark inlay stone. The glyph family is the
// team's (0 lunar arcs and dots, 1 solar rays and wedges, 2 old grotto spirals), so the two
// teams differ in their runes, not only in colour. `energy` dims or wakes them; a slow wave
// runs along the frieze.
uniform vec4 carrier_color : source_color = vec4(.16, .16, .19, 1.0);
uniform float stroke = .075;
instance uniform vec4 tint : source_color, instance_index(4) = vec4(.5, .78, 1.0, 1.0);
instance uniform float energy : instance_index(5) = 1.0;
instance uniform float pattern : instance_index(10) = 0.0;
instance uniform float flicker : instance_index(6) = 0.0;
instance uniform float damage : instance_index(0) = 0.0;
instance uniform float fade : instance_index(1) = 0.0;
instance uniform float fade_floor : instance_index(2) = 2.2;
instance uniform float seed : instance_index(3) = 0.0;
varying vec3 world_pos;
varying vec3 local_pos;
#include "res://scripts/luneraria/view/materials/lune_common.gdshaderinc"
void vertex() {
world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
local_pos = VERTEX;
}
float seg(vec2 p, vec2 a, vec2 b) {
vec2 pa = p - a, ba = b - a;
float h = clamp(dot(pa, ba) / max(dot(ba, ba), 1e-5), 0.0, 1.0);
return length(pa - ba * h);
}
float arc(vec2 p, vec2 c, float r, float a0, float a1) {
vec2 d = p - c;
float a = atan(d.y, d.x);
float mid = (a0 + a1) * .5;
float half_span = (a1 - a0) * .5;
float off = abs(mod(a - mid + 3.14159, 6.28318) - 3.14159);
if (off > half_span) {
vec2 e0 = c + r * vec2(cos(a0), sin(a0));
vec2 e1 = c + r * vec2(cos(a1), sin(a1));
return min(length(p - e0), length(p - e1));
}
return abs(length(d) - r);
}
// Distance to the strokes of glyph `id` inside the unit cell q in [0,1]^2.
float glyph(vec2 q, float id, float family) {
float d = 1.0;
float h1 = fract(id * 7.13), h2 = fract(id * 3.71), h3 = fract(id * 5.37);
vec2 c = vec2(.5, .5);
d = min(d, seg(q, vec2(.5, .12), vec2(.5, .88)) + step(.35, h3) * 1.0);
if (family < .5) {
// Lunar: crescents, circles and dots.
d = min(d, arc(q, vec2(.5 + (h1 - .5) * .2, .55), .26, -1.2 + h2, 2.2 + h2));
d = min(d, abs(length(q - vec2(.5, .26)) - .1 - h1 * .05) + step(.5, h2) * 1.0);
d = min(d, length(q - vec2(.5 + (h2 - .5) * .5, .82)) - .04);
d = min(d, seg(q, vec2(.28, .4 + h1 * .2), vec2(.72, .4 + h1 * .2)) + step(.6, h1) * 1.0);
} else if (family < 1.5) {
// Solar: rays, wedges and a disc.
d = min(d, abs(length(q - vec2(.5, .6)) - .14));
for (int k = 0; k < 5; k++) {
float a = 3.14159 * (.1 + .2 * float(k)) + (h1 - .5) * .3;
vec2 dir = vec2(cos(a), sin(a));
d = min(d, seg(q, vec2(.5, .6) + dir * .2, vec2(.5, .6) + dir * (.3 + .08 * step(.5, fract(h2 * float(k + 1) * 3.1)))));
}
d = min(d, seg(q, vec2(.3, .15), vec2(.5, .34)) + step(.4, h2) * 1.0);
d = min(d, seg(q, vec2(.7, .15), vec2(.5, .34)) + step(.4, h2) * 1.0);
} else {
// Old grotto spirals and hooks.
d = min(d, arc(q, c, .28, 0.0 + h1 * 3.0, 4.2 + h1 * 3.0));
d = min(d, arc(q, c, .14, 1.0 + h2 * 3.0, 5.0 + h2 * 3.0));
d = min(d, seg(q, vec2(.2, .8 - h3 * .2), vec2(.8, .2 + h3 * .2)) + step(.5, h3) * 1.0);
}
return d;
}
void fragment() {
if (PROJECTION_MATRIX[3][3] < .5 && faded(fade, fade_floor, world_pos.y, FRAGCOORD.xy)) discard;
float occlusion = COLOR.r;
vec2 uv = UV;
float cell = floor(uv.x);
vec2 q = vec2(fract(uv.x), clamp(uv.y, 0.0, 1.0));
q = (q - .5) * vec2(1.0, 1.1) + .5;
float id = hash12(vec2(cell, seed * 13.0 + pattern));
float d = glyph(q, id, pattern);
float line = 1.0 - smoothstep(stroke * .55, stroke, d);
// Border lines along the frieze.
float border = 1.0 - smoothstep(.02, .045, min(abs(uv.y - .04), abs(uv.y - .96)));
float mark = max(line, border * .7);
float wave = .7 + .3 * sin(uv.x * .9 - TIME * 1.6 + seed * 4.0);
float live = energy;
if (flicker > 0.0) live *= mix(1.0, step(.45, noise3(vec3(TIME * 3.0 + cell, seed, 0.0))), flicker);
// Damage breaks glyph segments.
float broken = damage > .01 ? step(damage * .8, fract(id * 11.3 + floor(q.y * 3.0) * .37)) : 1.0;
vec3 carrier = carrier_color.rgb * mix(.8, 1.1, noise3(world_pos * 3.0)) * mix(.6, 1.0, occlusion);
ALBEDO = mix(carrier, tint.rgb * .25, mark * .6);
ROUGHNESS = mix(.8, .35, mark);
METALLIC = 0.0;
EMISSION = tint.rgb * mark * broken * live * wave * 2.2;
}