// =============================================================================
// CYBERPUNK VIKING BERZERKER SHADER
// Obsidian Glassmorphism · Neon Cyan Illumination · Magenta Shatter Physics
// -----------------------------------------------------------------------------
// SwiftUI-inspired architecture: declarative uniform structs, modular fn blocks,
// composable visual layers, and a single themeable ColorTheme binding.
// Compatible with wgpu / Bevy / any WebGPU-based Rust renderer.
// =============================================================================
// -----------------------------------------------------------------------------
// SECTION 1 — THEME UNIFORMS
// Bind group 0, binding 0: update at runtime to retheme everything.
// SwiftUI analogy: @Environment(.colorScheme) / @Binding var theme: Theme
// -----------------------------------------------------------------------------
struct ColorTheme {
// Core palette — vec4<f32> = (R, G, B, intensity_multiplier)
primary_neon: vec4<f32>, // default: cyan (0.0, 1.0, 0.95, 1.2)
shatter_neon: vec4<f32>, // default: magenta (1.0, 0.0, 0.75, 1.5)
glass_base: vec4<f32>, // default: obsidian black (0.04, 0.04, 0.06, 0.82)
glass_edge: vec4<f32>, // default: dark cyan rim (0.0, 0.45, 0.55, 0.6)
rune_glow: vec4<f32>, // default: pale ice (0.75, 0.98, 1.0, 0.9)
ember_core: vec4<f32>, // default: berzerker blood (0.95, 0.12, 0.12, 1.0)
background_deep: vec4<f32>, // default: void black (0.01, 0.01, 0.03, 1.0)
// Scalar controls
glass_blur_strength: f32, // 0.0–1.0, default 0.6
shatter_edge_width: f32, // pixels, default 1.8
neon_bloom_radius: f32, // 0.0–0.05, default 0.022
rune_opacity: f32, // 0.0–1.0, default 0.55
// Padding to ensure 16-byte alignment for GPU uniforms
_pad0: f32,
_pad1: f32,
_pad2: f32,
_pad3: f32,
};
@group(0) @binding(0)
var<uniform> theme: ColorTheme;
// -----------------------------------------------------------------------------
// SECTION 2 — SCENE UNIFORMS
// Bind group 0, binding 1: time, resolution, interaction inputs.
// SwiftUI analogy: @State / @Published scene state driving view updates.
// -----------------------------------------------------------------------------
struct SceneUniforms {
view: mat4x4<f32>,
proj: mat4x4<f32>,
time: f32, // seconds elapsed
delta_time: f32, // frame delta for physics
resolution: vec2<f32>,
mouse: vec2<f32>, // normalized 0..1
mouse_velocity: vec2<f32>, // for shatter impulse
shatter_origin: vec2<f32>, // epicenter of last shatter event
shatter_time: f32, // timestamp of last shatter (for animation)
shatter_force: f32, // 0.0–1.0 impact magnitude
berzerker_rage: f32,
scroll_offset: f32,
_pad: vec2<f32>,
};
@group(0) @binding(1)
var<uniform> scene: SceneUniforms;
// SECTION 3 — VERTEX STAGE
// Receives vertex data from the SurtrRenderer's vertex buffer.
// -----------------------------------------------------------------------------
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(2) uv: vec2<f32>,
@location(3) color: vec4<f32>,
@location(4) mode: u32,
@location(5) radius: f32,
@location(6) slice: vec3<f32>,
@location(7) logical: vec2<f32>,
@location(8) size: vec2<f32>,
@location(9) screen: vec2<f32>,
@location(10) clip: vec4<f32>,
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) color: vec4<f32>,
@location(2) @interpolate(flat) mode: u32,
@location(3) radius: f32,
@location(4) slice: vec3<f32>,
@location(5) logical: vec2<f32>,
@location(6) size: vec2<f32>,
@location(7) screen: vec2<f32>,
@location(8) normal: vec3<f32>,
@location(9) clip: vec4<f32>,
};
@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
// Check if we are in 2D mode (mode < 100) or 3D mode
if in.mode < 100u {
// 2D UI Path: Convert pixel coordinates to NDC
let ndc_x = (in.position.x / scene.resolution.x) * 2.0 - 1.0;
let ndc_y = 1.0 - (in.position.y / scene.resolution.y) * 2.0;
out.clip_position = vec4<f32>(ndc_x, ndc_y, 0.0, 1.0);
} else {
// 3D/Visualization Path: Use MVP matrices
out.clip_position = scene.proj * scene.view * vec4<f32>(in.position, 1.0);
}
out.uv = in.uv;
out.color = in.color;
out.mode = in.mode;
out.radius = in.radius;
out.slice = in.slice;
out.logical = in.logical;
out.size = in.size;
out.screen = in.screen;
out.normal = in.normal;
out.clip = in.clip;
return out;
}
// =============================================================================
// SECTION 4 — MATH & UTILITY LIBRARY
// Pure functions — no side effects, fully composable.
// SwiftUI analogy: ViewModifier / extension on View
// =============================================================================
// --- 2D rotation matrix (standard CCW)
fn rot2(angle: f32) -> mat2x2<f32> {
let s = sin(angle);
let c = cos(angle);
return mat2x2<f32>(c, s, -s, c);
}
// --- Hash / noise primitives (GPU-friendly, no texture dependency)
fn hash11(p: f32) -> f32 {
var x = fract(p * 0.1031);
x *= x + 33.33;
x *= x + x;
return fract(x);
}
// --- Heatmap Palette
fn heatmap_palette(t: f32) -> vec3<f32> {
let low = vec3<f32>(0.0, 0.05, 0.2);
let mid = theme.primary_neon.rgb;
let high = theme.shatter_neon.rgb;
return mix(mix(low, mid, smoothstep(0.0, 0.5, t)), high, smoothstep(0.5, 1.0, t));
}
fn hash21(p: vec2<f32>) -> f32 {
var p3 = fract(vec3<f32>(p.xyx) * 0.1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
fn hash22(p: vec2<f32>) -> vec2<f32> {
var p3 = fract(vec3<f32>(p.xyx) * vec3<f32>(0.1031, 0.1030, 0.0973));
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.xx + p3.yz) * p3.zy);
}
// --- Value noise 2D (smooth)
fn vnoise(p: vec2<f32>) -> f32 {
let i = floor(p);
let f = fract(p);
let u = f * f * (3.0 - 2.0 * f);
return mix(
mix(hash21(i + vec2<f32>(0.0, 0.0)), hash21(i + vec2<f32>(1.0, 0.0)), u.x),
mix(hash21(i + vec2<f32>(0.0, 1.0)), hash21(i + vec2<f32>(1.0, 1.0)), u.x),
u.y
);
}
// --- Fractal Brownian Motion (fBm) — 5 octaves
fn fbm(p: vec2<f32>) -> f32 {
var val = 0.0;
var amp = 0.5;
var freq = 1.0;
var pp = p;
for (var i = 0; i < 5; i++) {
val += amp * vnoise(pp * freq);
freq *= 2.1;
amp *= 0.5;
pp = pp * rot2(0.37);
}
return val;
}
// --- Signed distance: line segment (for rune strokes)
fn sd_segment(p: vec2<f32>, a: vec2<f32>, b: vec2<f32>) -> f32 {
let pa = p - a;
let ba = b - a;
let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h);
}
// --- Signed distance: box
fn sd_box(p: vec2<f32>, b: vec2<f32>) -> f32 {
let d = abs(p) - b;
return length(max(d, vec2<f32>(0.0))) + min(max(d.x, d.y), 0.0);
}
// --- Equilateral Triangle SDF
fn sd_triangle(p: vec2<f32>, r: f32) -> f32 {
let k = sqrt(3.0);
var pp = p;
pp.x = abs(pp.x) - r;
pp.y = pp.y + r / k;
if (pp.x + k * pp.y > 0.0) {
pp = vec2<f32>(pp.x - k * pp.y, -k * pp.x - pp.y) / 2.0;
}
pp.x -= clamp(pp.x, -2.0 * r, 0.0);
return -length(pp) * sign(pp.y);
}
// --- Ellipse SDF (Approximate)
fn sd_ellipse(p: vec2<f32>, r: vec2<f32>) -> f32 {
let k0 = length(p / r);
let k1 = length(p / (r * r));
return k0 * (k0 - 1.0) / k1;
}
// --- Rounded Rectangle SDF
fn sd_round_rect(p: vec2<f32>, b: vec2<f32>, r: f32) -> f32 {
let d = abs(p) - b;
return length(max(d, vec2<f32>(0.0))) + min(max(d.x, d.y), 0.0) - r;
}
// --- Smooth minimum (organic blending)
fn smin(a: f32, b: f32, k: f32) -> f32 {
let h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
return mix(b, a, h) - k * h * (1.0 - h);
}
// --- Neon glow falloff: sharp core + soft bloom
fn neon_glow(dist: f32, width: f32, bloom: f32) -> f32 {
let core = smoothstep(width, 0.0, dist);
let glow = exp(-dist * dist / (bloom * bloom));
return core + glow * 0.6;
}
// --- Chromatic aberration offset
fn chrom_aberr(uv: vec2<f32>, offset: f32) -> vec2<f32> {
return uv + normalize(uv - vec2<f32>(0.5)) * offset;
}
// ── 3D SDF Library ──────────────────────────────────────────────────────────
fn sd_sphere(p: vec3<f32>, s: f32) -> f32 {
return length(p) - s;
}
fn sd_box_3d(p: vec3<f32>, b: vec3<f32>) -> f32 {
let q = abs(p) - b;
return length(max(q, vec3<f32>(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0);
}
fn scene_sdf(p: vec3<f32>) -> f32 {
let s1 = sd_sphere(p - vec3<f32>(0.0, 0.2 * sin(scene.time), 0.0), 0.4);
let b1 = sd_box_3d(p - vec3<f32>(0.6, 0.0, 0.0), vec3<f32>(0.2, 0.2, 0.2));
return smin(s1, b1, 0.1);
}
fn ray_march(ro: vec3<f32>, rd: vec3<f32>) -> f32 {
var t = 0.0;
for (var i = 0; i < 64; i++) {
let d = scene_sdf(ro + rd * t);
if d < 0.001 { return t; }
if t > 20.0 { break; }
t += d;
}
return -1.0;
}
fn calc_normal(p: vec3<f32>) -> vec3<f32> {
let e = vec2<f32>(0.001, 0.0);
return normalize(vec3<f32>(
scene_sdf(p + e.xyy) - scene_sdf(p - e.xyy),
scene_sdf(p + e.yxy) - scene_sdf(p - e.yxy),
scene_sdf(p + e.yyx) - scene_sdf(p - e.yyx),
));
}
// -----------------------------------------------------------------------------
// SECTION 5 — FRAGMENT STAGE
// -----------------------------------------------------------------------------
@group(1) @binding(0) var t_diffuse: texture_2d<f32>;
@group(1) @binding(1) var s_diffuse: sampler;
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
// Mode 0: Primitive / Panel
// Mode 1: Neon Line / Gungnir
// Mode 2: Texture Map
// Mode 3: Rounded Rect Panel
// Mode 4: Ellipse
// Mode 5: Shatter Shard
// Mode 6: Text Atlas
// Mode 7: Bifrost Glassmorphism
// Mode 8: Mjolnir Shatter Grid
// Mode 9: Lightning Bolt
// Mode 10: Runes
// Mode 11: Mjolnir Fluid
// Mode 12: Heatmap
// Mode 13: 3D Surface
// Mode 14: Ray Marched Reflections
// Mode 15: Linear Gradient
// Mode 16: Radial Gradient
// Mode 17: Stroked Rounded Rect
// Mode 18: Drop Shadow
// Mode 19: Dashed Border
var color = in.color;
let fw = length(vec2(dpdx(in.logical.x), dpdy(in.logical.y)));
// ── Global Clipping (SDF-based) ──────────────────────────────────────────
let clip_pos = in.clip.xy;
let clip_size = in.clip.zw;
let clip_d = sd_box(in.position.xy - (clip_pos + clip_size * 0.5), clip_size * 0.5);
let clip_alpha = 1.0 - smoothstep(-fw, fw, clip_d);
color.a *= clip_alpha;
if color.a <= 0.0 { discard; }
if in.mode == 3u {
// Mode 3: Rounded Rect Panel
let half_size = in.size * 0.5;
let d = sd_round_rect(in.logical - half_size, half_size - in.radius, in.radius);
let alpha = 1.0 - smoothstep(-fw, fw, d);
color.a *= alpha;
if color.a <= 0.0 { discard; }
} else if in.mode == 4u {
// Mode 4: Ellipse
let d = length((in.uv - 0.5) * 2.0) - 1.0;
let alpha = 1.0 - smoothstep(-fw, fw, d);
color.a *= alpha;
if color.a <= 0.0 { discard; }
} else if in.mode == 5u {
// Shatter Shard SDF
let p = (in.uv - 0.5) * 2.0;
let d = sd_triangle(p, 0.01);
let alpha = 1.0 - smoothstep(-fw, fw, d);
if alpha <= 0.0 { discard; }
// Add neon edge
let edge = smoothstep(0.01, 0.0, abs(d));
color = mix(color, theme.shatter_neon, edge);
} else if in.mode == 7u {
// Mode 7: Bifrost (Advanced Glassmorphism)
let uv_dist = sd_box(in.uv - 0.5, vec2<f32>(0.495));
// Refraction & Chromatic Aberration
let refraction = 0.005 * (1.0 - smoothstep(0.0, 0.1, abs(uv_dist)));
let r = textureSample(t_diffuse, s_diffuse, in.uv + vec2<f32>(refraction, 0.0)).r;
let g = textureSample(t_diffuse, s_diffuse, in.uv).g;
let b = textureSample(t_diffuse, s_diffuse, in.uv - vec2<f32>(refraction, 0.0)).b;
let blur_color = vec4<f32>(r, g, b, 1.0);
// Frosted Grain Noise
let grain = hash21(in.position.xy + scene.time) * 0.04;
let base = mix(blur_color, theme.glass_base, theme.glass_base.a) + grain;
// Edge Highlight & Specular
let edge_mask = smoothstep(-0.01, 0.0, uv_dist);
var final_color = mix(base, theme.glass_edge, (1.0 - edge_mask) * theme.glass_edge.a);
// Specular Light Catch
let light_dir = normalize(vec2<f32>(1.0, -1.0));
let normal = normalize(vec2<f32>(dpdx(uv_dist), dpdy(uv_dist)));
let spec = pow(max(dot(normal, light_dir), 0.0), 16.0) * 0.2;
final_color += spec * theme.primary_neon;
color = final_color;
} else if in.mode == 8u || in.mode == 11u {
// Mode 8/11: Mjolnir Shatter
var p = (in.uv - 0.5) * 2.0;
if in.mode == 11u {
p += fbm(p * 5.0 + vec2<f32>(scene.time, scene.time * 0.7)) * 0.2;
}
let d = sd_triangle(p, 0.05);
let alpha = 1.0 - smoothstep(-fw, fw, d);
if alpha <= 0.0 { discard; }
color.a *= alpha;
color = mix(color, theme.shatter_neon, smoothstep(0.05, 0.0, abs(d)));
} else if in.mode == 9u {
// Mode 9: Gungnir Bolt (Lightning Glow)
let d = length((in.uv - 0.5) * vec2<f32>(1.0, 4.0));
let glow = neon_glow(d, 0.01, 0.2);
color = theme.primary_neon * glow;
} else if in.mode == 10u {
// Mode 10: Runes (SDF Strokes)
let p = (in.uv - 0.5) * 2.0;
let d1 = sd_segment(p, vec2<f32>(-0.5, -0.8), vec2<f32>(0.5, 0.8));
let d2 = sd_segment(p, vec2<f32>(0.5, -0.8), vec2<f32>(-0.5, 0.8));
let d = min(d1, d2);
let glow = neon_glow(d, 0.02, 0.15);
color = theme.rune_glow * glow * theme.rune_opacity;
} else if in.mode == 12u {
// Mode 12: Heatmap
let val = textureSample(t_diffuse, s_diffuse, in.uv).r;
color = vec4<f32>(heatmap_palette(val), in.color.a);
} else if in.mode == 13u {
// Mode 13: 3D Surface
// Simple diffuse lighting
let light_dir = normalize(vec3<f32>(0.5, 0.5, 1.0));
let diff = max(dot(in.normal, light_dir), 0.2);
color = vec4<f32>(in.color.rgb * diff, in.color.a);
} else if in.mode == 14u {
// Mode 14: Ray Marched Reflections
let ro = vec3<f32>(in.uv.x - 0.5, in.uv.y - 0.5, -2.0);
let rd = normalize(vec3<f32>(in.uv.x - 0.5, in.uv.y - 0.5, 1.0));
let t = ray_march(ro, rd);
if t > 0.0 {
let p = ro + rd * t;
let n = calc_normal(p);
let light_dir = normalize(vec3<f32>(1.0, 1.0, -1.0));
let diff = max(dot(n, light_dir), 0.2);
let ref_rd = reflect(rd, n);
let ref_t = ray_march(p + n * 0.01, ref_rd);
var reflection_color = vec3<f32>(0.05, 0.05, 0.1);
if ref_t > 0.0 {
reflection_color = mix(theme.primary_neon.rgb, theme.shatter_neon.rgb, 0.5);
}
let final_rgb = mix(in.color.rgb * diff, reflection_color, 0.3);
color = vec4<f32>(final_rgb, 1.0);
} else {
discard;
}
} else if in.mode == 15u {
// Mode 15: Linear Gradient (Animated)
let angle = in.slice.x + scene.time * 0.5;
let dir = vec2<f32>(cos(angle), sin(angle));
let t = dot(in.uv - 0.5, dir) + 0.5;
color = mix(in.color, theme.primary_neon, clamp(t, 0.0, 1.0));
} else if in.mode == 16u {
// Mode 16: Radial Gradient
let t = length(in.uv - 0.5) * 2.0;
color = mix(in.color, theme.primary_neon, clamp(t, 0.0, 1.0));
} else if in.mode == 17u {
// Mode 17: Stroked Rounded Rect
let half_size = in.size * 0.5;
let d = sd_round_rect(in.logical - half_size, half_size - in.radius, in.radius);
let thickness = max(in.slice.x, 1.0);
let d_stroke = abs(d + thickness * 0.5) - thickness * 0.5;
let alpha = 1.0 - smoothstep(-fw, fw, d_stroke);
color.a *= alpha;
if color.a <= 0.0 { discard; }
} else if in.mode == 18u {
// Mode 18: Drop Shadow
let half_size = in.size * 0.5;
let d = sd_round_rect(in.logical - half_size, half_size - in.radius, in.radius);
let blur = max(in.slice.x, 1.0);
let shadow = 1.0 - smoothstep(-blur, blur, d - in.slice.y);
color = vec4<f32>(in.color.rgb, in.color.a * shadow);
if color.a <= 0.0 { discard; }
} else if in.mode == 19u {
// Mode 19: Dashed Border
let half_size = in.size * 0.5;
let d = sd_round_rect(in.logical - half_size, half_size - in.radius, in.radius);
let thickness = max(in.slice.x, 1.0);
let dash = max(in.slice.y, 1.0);
let gap = max(in.slice.z, 1.0);
let perimeter = (in.uv.x + in.uv.y) * max(in.size.x, in.size.y);
let pattern = (perimeter + scene.time * 20.0) % (dash + gap);
var alpha = 1.0 - smoothstep(-fw, fw, abs(d + thickness * 0.5) - thickness * 0.5);
if pattern > dash { alpha = 0.0; }
color.a *= alpha;
if color.a <= 0.0 { discard; }
} else if in.mode == 20u {
// Mode 20: 9-Slice / Patch Scaling
// slice = [left, top, right], radius = bottom (normalized 0..1 of texture)
let l = in.slice.x;
let t = in.slice.y;
let r = in.slice.z;
let b = in.radius;
var uv = in.uv;
// X-axis remapping
if uv.x < l {
uv.x = uv.x * (0.33 / l);
} else if uv.x > (1.0 - r) {
uv.x = 0.66 + (uv.x - (1.0 - r)) * (0.34 / r);
} else {
uv.x = 0.33 + (uv.x - l) / (1.0 - l - r) * 0.33;
}
// Y-axis remapping
if uv.y < t {
uv.y = uv.y * (0.33 / t);
} else if uv.y > (1.0 - b) {
uv.y = 0.66 + (uv.y - (1.0 - b)) * (0.34 / b);
} else {
uv.y = 0.33 + (uv.y - t) / (1.0 - t - b) * 0.33;
}
color *= textureSample(t_diffuse, s_diffuse, uv);
} else if in.mode == 21u {
// Mode 21: Stroked Ellipse
let d = sd_ellipse((in.uv - 0.5) * 2.0, vec2<f32>(1.0));
let thickness = max(in.slice.x, 0.01);
let d_stroke = abs(d + thickness * 0.5) - thickness * 0.5;
let alpha = 1.0 - smoothstep(-fw, fw, d_stroke);
color.a *= alpha;
if color.a <= 0.0 { discard; }
} else if in.mode == 2u || in.mode == 6u {
// Texture or Text Atlas
let tex_color = textureSample(t_diffuse, s_diffuse, in.uv);
if in.mode == 6u {
// Text uses alpha from atlas and color from vertex
color.a *= tex_color.r;
} else {
color *= tex_color;
}
}
// Global Berserker Rage influence
let rage = scene.berzerker_rage;
if rage > 0.1 {
let n = fbm(in.logical * 10.0 + vec2<f32>(scene.time, scene.time * 0.7));
color = mix(color, theme.ember_core, n * rage * 0.5);
}
return color;
}