#version 460
#extension GL_EXT_scalar_block_layout : enable
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
layout(set = 0, binding = 0, rgba32f) uniform image2D outputImage;
// ────────────────────────────────────────────────
// Engine PushConstants
// ────────────────────────────────────────────────
layout(push_constant) uniform PushConstants {
float time; // total engine time (seconds)
uint frameSeed; // per-frame RNG seed
vec3 cameraPos; float pad0;
vec4 cameraQuat; // xyzw orientation quaternion
float cameraFovDeg; // vertical FOV in degrees
float aspectRatio; // width / height
float nearPlane;
float farPlane;
float exposure;
float vignetteStrength;
float bloomThreshold;
float bloomIntensity;
uint tonemapMode;
float contrast;
float saturation;
float gamma;
vec3 sunDir; float sunIntensity;
vec3 moonDir; float moonIntensity;
vec3 windDir; float windStrength;
float fogDensity;
float dayNightFactor;
float cloudCoverage;
float raymarchMaxDist;
float raymarchEpsilon;
uint raymarchMaxSteps;
uint controllerInput;
float leftStickX; float leftStickY;
float rightStickX; float rightStickY;
float leftTrigger; float rightTrigger;
vec2 mouseDelta;
vec2 mouseNormalized;
float mouseWheelDelta;
float pad1[3];
} pc;
// ────────────────────────────────────────────────
// Audio input (CPU → shader)
// ────────────────────────────────────────────────
struct AudioData {
float amplitude;
float bass;
float mid;
float high;
float fftBins[16];
float reserved[12];
};
layout(std430, set = 0, binding = 1) readonly buffer AudioInputBuffer {
AudioData audio;
} audioIn;
// ────────────────────────────────────────────────
// Audio commands (shader → CPU suggestions)
// ────────────────────────────────────────────────
struct AudioCommandBlock {
float slotCommand[16];
float slotValue[16];
float reserved[16];
};
layout(std430, set = 0, binding = 2) buffer AudioCommandBuffer {
AudioCommandBlock commands;
} audioOut;
// ────────────────────────────────────────────────
// Scene tuning – diamond mandelbulb style
// ────────────────────────────────────────────────
#define POWER 8.0
#define MAX_ITER 180u
#define BAILOUT 16.0
#define EPS 0.0004
#define MARCH_STEPS 220
#define FAR 35.0
#define GLOW_STRENGTH 2.2
#define IRIDESCENCE_SHIFT 0.45
#define FACET_SHARPNESS 0.92 // higher = sharper diamond edges
// Sound slots (customize to your mixer)
#define SLOT_CRYSTAL_PULSE 0
#define SLOT_IRIDESCENT_GLOW 1
#define SLOT_BASS_SURGE 2
#define SLOT_HIGH_SPARKLE 3
// ────────────────────────────────────────────────
// Hashes
// ────────────────────────────────────────────────
float hash11(float p) {
p = fract(p * 0.1031);
p *= p 33.33;
return fract(2.0 * p * (p p));
}
float hash12(vec2 p) {
p = fract(p * vec2(0.1031, 0.1030));
p = dot(p, p.yx 33.33);
return fract((p.x p.y) * p.x);
}
float hash13(vec3 p3) {
p3 = fract(p3 * vec3(0.1031, 0.1030, 0.0973));
p3 = dot(p3, p3.yzx 33.33);
return fract((p3.x p3.y) * p3.z);
}
// ────────────────────────────────────────────────
// Mandelbulb DE (power-8, tuned for diamond facets)
// ────────────────────────────────────────────────
float mandelbulbDE(vec3 pos, out float trap) {
vec3 z = pos;
float dr = 1.0;
float r = 0.0;
trap = 1e10;
for (uint i = 0u; i < MAX_ITER; i) {
r = length(z);
if (r > BAILOUT) break;
// Polar coords
float theta = acos(z.z / max(r, 1e-6));
float phi = atan(z.y, z.x);
dr = pow(r, POWER - 1.0) * POWER * dr 1.0;
float zr = pow(r, POWER);
theta *= POWER;
phi *= POWER;
z = zr * vec3(sin(theta) * cos(phi), sin(phi) * sin(theta), cos(theta)) pos;
// Orbit trap for faceted detail
trap = min(trap, dot(z.xz, z.xz)); // xy plane trap → sharper diamond planes
}
return 0.5 * log(r 1e-6) * r / dr;
}
// ────────────────────────────────────────────────
// Soft min for smooth blending (not used here but handy)
// ────────────────────────────────────────────────
float smin(float a, float b, float k) {
float h = clamp(0.5 0.5 * (b - a) / k, 0.0, 1.0);
return mix(b, a, h) - k * h * (1.0 - h);
}
// ────────────────────────────────────────────────
// Raymarch
// ────────────────────────────────────────────────
vec2 raymarch(vec3 ro, vec3 rd) {
float t = 0.0;
float trap = 0.0;
for (int i = 0; i < MARCH_STEPS; i) {
vec3 p = ro t * rd;
float dist = mandelbulbDE(p, trap);
if (dist < EPS) return vec2(t, trap);
if (t > FAR) break;
t = dist * 0.92;
}
return vec2(-1.0, trap);
}
// ────────────────────────────────────────────────
// Tetrahedron normal
// ────────────────────────────────────────────────
vec3 calcNormal(vec3 p) {
vec2 e = vec2(1.0, -1.0) * 0.57735 * 0.0008;
float trapDummy;
return normalize(
e.xyy * mandelbulbDE(p e.xyy, trapDummy)
e.yyx * mandelbulbDE(p e.yyx, trapDummy)
e.yxy * mandelbulbDE(p e.yxy, trapDummy)
e.xxx * mandelbulbDE(p
e.xxx, trapDummy)
);
}
// ────────────────────────────────────────────────
// Quaternion rotate
// ────────────────────────────────────────────────
vec3 quatRotate(vec4 q, vec3 v) {
vec3 t = 2.0 * cross(
q.xyz, v);
return v q.w * t cross(
q.xyz, t);
}
// ────────────────────────────────────────────────
// Diamond / iridescent shading
// ────────────────────────────────────────────────
vec3 shadeDiamond(vec3 pos, vec3 nor, vec3 view, float trap, float t) {
vec3 base = vec3(0.98, 0.99, 1.00); // near-white diamond
// Iridescence from time audio trap detail
float hue = fract(t * IRIDESCENCE_SHIFT * (1.0
audioIn.audio.mid * 2.5) trap * 0.15
audioIn.audio.high * 4.0);
vec3 irides = 0.5 0.5 * cos(6.28318 * (hue vec3(0.0, 0.333, 0.667)));
vec3 col = base * (0.8 0.4 * irides);
// Emission / glow from bass & amplitude
float emit =
audioIn.audio.amplitude * 2.5
audioIn.audio.bass * 3.8
audioIn.audio.high * 1.2;
col = irides * emit * (0.6 0.4 * sin(t * 6.0 trap * 20.0));
// Sun lighting
vec3 sunDir = normalize(pc.sunDir);
float diff = max(0.0, dot(nor, sunDir));
vec3 reflectDir = reflect(-sunDir, nor);
float spec = pow(max(0.0, dot(reflectDir, -view)), 120.0); // sharp diamond spec
col *= 0.15 diff * 1.4;
col = irides * spec * 1.2;
// Fresnel rim
float fres = pow(1.0 - max(0.0, dot(nor, -view)), 4.5);
col = irides * fres * 1.5;
// Trap-based AO / detail darkening
float ao = 1.0 - smoothstep(0.0, 12.0, sqrt(trap)) * 0.7;
col *= 0.4 0.6 * ao;
// Edge/facets glow
float facet = smoothstep(0.0, 0.15, abs(trap - floor(trap 0.5)));
col = irides * facet * GLOW_STRENGTH * (1.0
audioIn.audio.amplitude * 2.5);
return clamp(col, 0.0, 3.0);
}
void main() {
ivec2 px = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = imageSize(outputImage);
if (any(greaterThanEqual(px, size))) return;
vec2 uv = (vec2(px) 0.5) / vec2(size) - 0.5;
uv.x *= pc.aspectRatio;
float t = pc.time;
// Camera
vec3 ro = pc.cameraPos;
vec3 fwd = quatRotate(pc.cameraQuat, vec3(0,0,-1));
vec3 right = quatRotate(pc.cameraQuat, vec3(1,0,0));
vec3 up = quatRotate(pc.cameraQuat, vec3(0,1,0));
float fovScale = tan(radians(pc.cameraFovDeg) * 0.5);
vec3 rd = normalize(fwd right * uv.x * fovScale up * uv.y * fovScale);
// Raymarch
vec2 res = raymarch(ro, rd);
vec3 color = vec3(0.01, 0.015, 0.03); // dark space bg
if (res.x > 0.0) {
vec3 pos = ro res.x * rd;
vec3 nor = calcNormal(pos);
color = shadeDiamond(pos, nor, rd, res.y, t);
// Transmission / inner glow
float trans = 0.6 0.4 * (1.0 - smoothstep(0.0, 10.0, res.y));
color = vec3(0.9, 0.95, 1.1) * trans *
audioIn.audio.amplitude * 0.8;
} else {
// Background stars / nebula
float stars = pow(hash12(floor(vec2(atan(rd.z, rd.x), acos(rd.y)) * 800.0 t * 0.2)), 20.0) * 0.6;
color = vec3(1.0) * stars * (1.0
audioIn.audio.high * 3.0);
}
// Post
color = pow(color, vec3(1.0 / pc.gamma));
color = (color - 0.5) * pc.contrast 0.5;
color *= pc.saturation * (color / (color vec3(0.08))) (1.0 - pc.saturation);
vec3 bloom = max(color - pc.bloomThreshold, 0.0) * pc.bloomIntensity * 1.5;
color = bloom;
imageStore(outputImage, px, vec4(color, 1.0));
// Audio command feedback (only once per frame)
if (px.x == 0 && px.y == 0) {
float beat = step(0.7,
audioIn.audio.bass
audioIn.audio.amplitude * 0.5);
audioOut.commands.slotCommand[SLOT_CRYSTAL_PULSE] = 2.0; // set volume
audioOut.commands.slotValue[SLOT_CRYSTAL_PULSE] = 0.3
audioIn.audio.mid * 0.7;
audioOut.commands.slotCommand[SLOT_IRIDESCENT_GLOW] = 2.0;
audioOut.commands.slotValue[SLOT_IRIDESCENT_GLOW] = 0.5
audioIn.audio.high * 0.8;
audioOut.commands.slotCommand[SLOT_BASS_SURGE] = beat > 0.5 ? 1.0 : 0.0; // play on bass surge
audioOut.commands.slotValue[SLOT_BASS_SURGE] =
audioIn.audio.bass * 1.2;
audioOut.commands.slotCommand[SLOT_HIGH_SPARKLE] = (
audioIn.audio.high > 0.6) ? 1.0 : 0.0;
audioOut.commands.slotValue[SLOT_HIGH_SPARKLE] =
audioIn.audio.high * 1.5;
}
}