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; }