Skip to content

Instantly share code, notes, and snippets.

@kayuksel
Created March 27, 2025 22:34
Show Gist options
  • Save kayuksel/ea218cd57d88fae234c99e121e5e5153 to your computer and use it in GitHub Desktop.
Save kayuksel/ea218cd57d88fae234c99e121e5e5153 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS Shader Evolution with Audio-Driven Animations</title>
<style>
/* Global and background styling */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
margin: 0;
padding: 0;
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
color: #fff;
text-align: center;
}
/* When a canvas is fullscreen, hide other interface elements */
.fullscreen-active header,
.fullscreen-active #controls,
.fullscreen-active #audioControls,
.fullscreen-active #progressFeedback {
display: none !important;
}
header {
padding: 20px;
background: rgba(0, 0, 0, 0.6);
box-shadow: 0 2px 8px rgba(0,0,0,0.4);
}
header h1 {
margin: 0;
font-size: 2rem;
font-weight: 600;
}
/* Container for canvases */
#container {
display: flex;
flex-wrap: wrap;
justify-content: center;
padding: 10px;
}
.variant {
margin: 10px;
border: 3px solid transparent;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
border-radius: 8px;
transition: transform 0.2s, border-color 0.2s;
position: relative;
}
.variant:hover {
transform: scale(1.03);
}
.variant.selected {
border-color: #fff;
border: 8px solid #fff;
}
canvas {
width: 500;
height: 500px;
border-radius: 8px;
transition: all 0.3s ease;
}
/* Fullscreen styles for canvas */
.fullscreen {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 100vh !important;
z-index: 9999 !important;
border-radius: 0 !important;
}
/* Controls styling */
#controls, #audioControls, #progressFeedback {
margin: 10px;
}
button {
margin-right: 10px;
padding: 10px 16px;
border: none;
border-radius: 4px;
background-color: #3498db;
color: #fff;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s;
}
button:disabled {
background-color: #7f8c8d;
cursor: not-allowed;
}
button:hover:not(:disabled) {
background-color: #2980b9;
}
input[type="file"] {
padding: 6px;
border-radius: 4px;
border: 1px solid #ccc;
}
#progressFeedback {
font-size: 1rem;
padding: 10px 16px;
background: rgba(0, 0, 0, 0.6);
border-radius: 4px;
display: none;
margin: 0 auto;
max-width: 90%;
}
/* Responsive adjustments */
@media (max-width: 768px) {
canvas {
width: 90vw;
height: 90vw;
}
button {
font-size: 0.9rem;
padding: 8px 12px;
}
}
@media (max-width: 480px) {
body {
padding: 5px;
}
#controls, #audioControls, #progressFeedback {
margin: 5px;
}
header h1 {
font-size: 1.6rem;
}
}
</style>
<!-- Load Tone.js from jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/Tone.js"></script>
</head>
<body>
<header>
<h1>AI Co-Artist: Interactive Shader Evolution</h1>
</header>
<div id="audioControls">
<label for="mp3Upload">Upload MP3:</label>
<input type="file" id="mp3Upload" accept="audio/mp3">
</div>
<div id="container"></div>
<div id="controls">
<button id="evolveButton" disabled>Evolve</button>
<button id="downloadButton" disabled>Download</button>
</div>
<div id="progressFeedback"></div>
<script>
// ================== CONFIGURATION ==================
// WARNING: Embedding your API key in client-side code is insecure!
const API_KEY = ""; // Replace with your OpenAI API key
const POPULATION_SIZE = 13;
const MAX_ATTEMPTS = 5;
// Global Tone.js analyser (FFT) for audio features
let audioAnalyser = null;
let audioPlayer = null;
// ================== PROGRESS FEEDBACK FUNCTIONS ==================
function showProgress(message) {
const progressDiv = document.getElementById("progressFeedback");
progressDiv.textContent = message;
progressDiv.style.display = "block";
}
function hideProgress() {
const progressDiv = document.getElementById("progressFeedback");
progressDiv.style.display = "none";
}
// ================== MP3 UPLOAD & AUDIO ANALYSIS ==================
const mp3Upload = document.getElementById("mp3Upload");
mp3Upload.addEventListener("change", async (event) => {
const file = event.target.files[0];
if (!file) return;
// If there's already an audio player, stop and dispose of it.
if (audioPlayer) {
audioPlayer.stop();
audioPlayer.dispose();
audioPlayer = null;
}
// Also reset the analyser if needed.
if (!audioAnalyser) {
audioAnalyser = new Tone.Analyser("fft", 32);
}
const fileURL = URL.createObjectURL(file);
try {
// Create a new player and chain it to the analyser then destination
audioPlayer = new Tone.Player(fileURL, () => {
Tone.start().then(() => {
audioPlayer.start();
});
}).chain(audioAnalyser, Tone.Destination);
} catch (err) {
console.error("Error loading MP3 file:", err);
}
});
// ================== SHADER TEMPLATES ==================
const vertexShaderSource = `
attribute vec2 a_position;
void main(){
gl_Position = vec4(a_position, 0.0, 1.0);
}
`;
// Base shaders with new uniform u_audio to drive visuals.
const baseShaders = [
`
float gTime = 0.0;
const float REPEAT = 5.0;
mat2 rot(float a) {
float c = cos(a), s = sin(a);
return mat2(c, s, -s, c);
}
float sdBox(vec3 p, vec3 b) {
vec3 q = abs(p) - b;
return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
}
float box(vec3 pos, float scale) {
pos *= scale;
float base = sdBox(pos, vec3(0.4, 0.4, 0.1)) / 1.5;
pos.xy *= rot(0.75 + u_audio * 0.5);
pos.y -= 3.5;
return -base;
}
float box_set(vec3 pos, float iTime) {
vec3 pos_origin = pos;
pos.y += sin(gTime * (0.4 + u_audio * 0.2)) * 2.5;
pos.xy *= rot(0.8);
float box1 = box(pos, 2.0 - abs(sin(gTime * 0.4)) * 1.5);
pos = pos_origin;
pos.y -= sin(gTime * 0.4) * 2.5;
pos.xy *= rot(0.8);
float box2 = box(pos, 2.0 - abs(sin(gTime * 0.4)) * 1.5);
pos = pos_origin;
pos.x += sin(gTime * 0.4) * 2.5;
pos.xy *= rot(0.8);
float box3 = box(pos, 2.0 - abs(sin(gTime * 0.4)) * 1.5);
pos = pos_origin;
pos.x -= sin(gTime * 0.4) * 2.5;
pos.xy *= rot(0.8);
float box4 = box(pos, 2.0 - abs(sin(gTime * 0.4)) * 1.5);
pos = pos_origin;
pos.xy *= rot(0.8);
float box5 = box(pos, 0.5) * 6.0;
pos = pos_origin;
float box6 = box(pos, 0.5) * 6.0;
return max(max(max(max(max(box1, box2), box3), box4), box5), box6);
}
float map(vec3 pos, float iTime) {
return box_set(pos, iTime);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 p = (fragCoord.xy * 2.0 - iResolution.xy) / min(iResolution.x, iResolution.y);
vec3 ro = vec3(0.0, -0.2, iTime * 4.0);
vec3 ray = normalize(vec3(p, 1.5));
ray.xy *= rot(sin(iTime * 0.03 + u_audio) * 5.0);
ray.yz *= rot(sin(iTime * 0.05 + u_audio) * 0.2);
float t = 0.1;
vec3 col = vec3(0.0);
float ac = 0.0;
for (int i = 0; i < 99; i++){
vec3 pos = ro + ray * t;
pos = mod(pos - 2.0, 4.0) - 2.0;
gTime = iTime - float(i) * 0.01;
float d = map(pos, iTime);
d = max(abs(d), 0.01);
ac += exp(-d * (23.0 + u_audio * 5.0));
t += d * 0.55;
}
col = vec3(ac * 0.02);
col += vec3(0.0, 0.2 * abs(sin(iTime + u_audio)), 0.5 + sin(iTime) * 0.2);
fragColor = vec4(col, 1.0 - t * (0.02 + 0.02 * sin(iTime)));
}
`,
`
mat2 rot(float a) {
float c = cos(a), s = sin(a);
return mat2(c, s, -s, c);
}
const float pi = acos(-1.0);
const float pi2 = pi * 2.0;
vec2 pmod(vec2 p, float r) {
float a = atan(p.x, p.y) + pi / r;
float n = pi2 / r;
a = floor(a / n) * n;
return p * rot(-a);
}
float box(vec3 p, vec3 b) {
vec3 d = abs(p) - b;
return min(max(d.x, max(d.y, d.z)), 0.0) + length(max(d, 0.0));
}
float ifsBox(vec3 p) {
for (int i = 0; i < 5; i++) {
p = abs(p) - 1.0;
p.xy *= rot(iTime * 0.3 + u_audio * 0.5);
p.xz *= rot(iTime * 0.1);
}
p.xz *= rot(iTime);
return box(p, vec3(0.4, 0.8, 0.3));
}
float map(vec3 p, vec3 cPos) {
vec3 p1 = p;
p1.x = mod(p1.x - 5.0, 10.0) - 5.0;
p1.y = mod(p1.y - 5.0, 10.0) - 5.0;
p1.z = mod(p1.z, 16.0) - 8.0;
p1.xy = pmod(p1.xy, 5.0);
return ifsBox(p1);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 p = (fragCoord.xy * 2.0 - iResolution.xy) / min(iResolution.x, iResolution.y);
vec3 cPos = vec3(0.0, 0.0, -3.0 * iTime);
vec3 cDir = normalize(vec3(0.0, 0.0, -1.0));
vec3 cUp = vec3(sin(iTime), 1.0, 0.0);
vec3 cSide = cross(cDir, cUp);
vec3 ray = normalize(cSide * p.x + cUp * p.y + cDir);
float acc = 0.0;
float acc2 = 0.0;
float t = 0.0;
for (int i = 0; i < 99; i++) {
vec3 pos = cPos + ray * t;
float dist = map(pos, cPos);
dist = max(abs(dist), 0.02);
float a = exp(-dist * (3.0 + u_audio * 2.0));
if (mod(length(pos) + 24.0 * iTime, 30.0) < 3.0) {
a *= 2.0;
acc2 += a;
}
acc += a;
t += dist * 0.5;
}
vec3 col = vec3(acc * 0.01, acc * 0.011 + acc2 * 0.002, acc * 0.012 + acc2 * 0.005);
fragColor = vec4(col, 1.0 - t * 0.03);
}
`,
`
#define hash(x) fract(sin(x) * 43758.5453123)
vec3 pal(float t){return 0.5+0.5*cos(6.28*(1.0*t+vec3(0.0,0.1,0.1)));}
float stepNoise(float x, float n) { // From Kamoshika shader
const float factor = 0.3;
float i = floor(x);
float f = x - i;
float u = smoothstep(0.5 - factor, 0.5 + factor, f);
float res = mix(floor(hash(i) * n), floor(hash(i + 1.0) * n), u);
res /= (n - 1.0) * 0.5;
return res - 1.0;
}
vec3 path(vec3 p) {
vec3 o = vec3(0.0);
o.x += stepNoise(p.z * 0.05, 5.0) * 5.0;
o.y += stepNoise(p.z * 0.07, 3.975) * 5.0;
return o;
}
float diam2(vec2 p, float s) {
p = abs(p);
return (p.x + p.y - s) * inversesqrt(3.0);
}
vec3 erot(vec3 p, vec3 ax, float t) {
return mix(dot(ax, p) * ax, p, cos(t)) + cross(ax, p) * sin(t);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
vec3 col = vec3(0.0);
vec3 ro = vec3(0.0, 0.0, -1.0), rt = vec3(0.0);
ro.z += iTime * 5.0;
rt.z += iTime * 5.0;
ro += path(ro);
rt += path(rt);
vec3 z = normalize(rt - ro);
vec3 x = vec3(z.z, 0.0, -z.x);
float e = 0.0;
float g = 0.0;
vec3 rd = mat3(x, cross(z, x), z) *
erot(normalize(vec3(uv, 1.0)), vec3(0.0, 0.0, 1.0),
stepNoise(iTime + hash(uv.x * uv.y * iTime) * 0.05, 6.0));
for (int iLoop = 0; iLoop < 99; iLoop++) {
float iVal = float(iLoop) + 1.0;
vec3 p = ro + rd * g;
p -= path(p);
float r = 0.0;
vec3 pp = p;
float sc = 1.0;
for (int j = 0; j < 4; j++) {
float jVal = float(j) + 1.0;
r = clamp(r + abs(dot(sin(pp * 3.0), cos(pp.yzx * 2.0)) * 0.3 - 0.1) / sc, -0.5, 0.5);
pp = erot(pp, normalize(vec3(0.1, 0.2, 0.3)), 0.785 + jVal);
pp += pp.yzx + jVal * 50.0;
sc *= 1.5;
pp *= 1.5;
}
float h = abs(diam2(p.xy, 7.0)) - 3.0 - r;
p = erot(p, vec3(0.0, 0.0, 1.0), path(p).x * 0.5 + p.z * 0.2);
float t = length(abs(p.xy) - 0.5) - 0.1;
h = min(t, h);
float update = max(0.001, t == h ? abs(h) : h);
e = update;
g += update;
col += (t == h
? vec3(0.3, 0.2, 0.1) * (100.0 * exp(-20.0 * fract(p.z * 0.25 + iTime))) *
mod(float(floor(p.z * 4.0) + mod(floor(p.y * 4.0), 2.0)), 2.0)
: vec3(0.1)) * 0.0325 / exp(iVal * iVal * e);
}
col = mix(col, vec3(0.9, 0.9, 1.1), 1.0 - exp(-0.01 * g * g * g));
fragColor = vec4(col, 1.0);
}
`,
`
vec3 palette( float t ) {
vec3 a = vec3(0.5, 0.5, 0.5);
vec3 b = vec3(0.5, 0.5, 0.5);
vec3 c = vec3(1.0, 1.0, 1.0);
vec3 d = vec3(0.263, 0.416, 0.557);
// Audio shifts the color phase without affecting timing
return a + b*cos(6.28318*(c*t + d) + u_audio * 0.5);
}
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;
vec2 uv0 = uv;
vec3 finalColor = vec3(0.0);
for (float i = 0.0; i < 4.0; i++) {
uv = fract(uv * 1.5) - 0.5;
float d = length(uv) * exp(-length(uv0));
// Incorporate u_audio into the palette input for additional color variation
vec3 col = palette(length(uv0) + i * 0.4 + iTime * 0.4 + u_audio * 0.3);
d = sin(d * 8.0 + iTime) / 8.0;
d = abs(d);
d = pow(0.01 / d, 1.2);
finalColor += col * d;
}
// Audio modulates overall brightness without affecting animation speed
finalColor *= 1.0 + u_audio * 0.2;
fragColor = vec4(finalColor, 1.0);
}
`,
`
mat2 rotate(float angle) {
float c = cos(angle), s = sin(angle);
return mat2(c, s, -s, c);
}
const float pi = acos(-1.0);
const float tau = pi * 2.0;
vec2 spiralTransform(vec2 p, float r) {
float a = atan(p.y, p.x) + pi / r + sin(iTime * 3.0 + u_audio * 0.6) * 0.6;
float n = tau / r;
a = floor(a / n) * n;
return p * rotate(a);
}
float smoothDistance(vec3 p, vec3 b) {
vec3 d = abs(p) - b;
return min(max(d.x, max(d.y, d.z)), 0.0) + length(max(d, 0.0));
}
float toroidalFractal(vec3 p) {
for (int i = 0; i < 7; i++) {
p = abs(p) - 1.2;
p.xy *= rotate(iTime * 0.4 + u_audio * 0.2);
p.xz *= rotate(iTime * 0.4);
}
return smoothDistance(p, vec3(0.5, 0.5, 0.5));
}
float terrainDisplacement(vec3 p) {
vec3 p1 = p;
p1.x = mod(p1.x + 1.7, 4.5) - 2.25;
p1.y = mod(p1.y + 1.7, 4.5) - 2.25;
p1.z = mod(p1.z + 1.8, 9.0) - 4.5;
p1.xy = spiralTransform(p1.xy, 4.5);
return toroidalFractal(p1);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 p = (fragCoord * 2.0 - iResolution.xy) / max(iResolution.x, iResolution.y);
vec3 cPos = vec3(0.0, 0.0, -7.0 * iTime);
vec3 cDir = normalize(vec3(0.0, 0.0, -1.0));
vec3 cUp = normalize(vec3(cos(iTime * 0.7), 1.0, sin(iTime * 0.7)));
vec3 cSide = normalize(cross(cDir, cUp));
vec3 ray = normalize(cSide * p.x + cUp * p.y + cDir);
float acc = 0.0;
float acc2 = 0.0;
float t = 0.0;
for (int i = 0; i < 120; i++) {
vec3 pos = cPos + ray * t;
float dist = terrainDisplacement(pos);
dist = max(abs(dist), 0.01);
float a = exp(-dist * (8.0 + u_audio * 0.6));
if (mod(length(pos) + 15.0 * iTime, 10.0) < 4.0) {
a *= 1.7;
acc2 += a;
}
acc += a;
t += dist * 0.25;
}
vec3 col = vec3(acc * 0.1, acc * 0.025 + acc2 * 0.012, acc * 0.05 + acc2 * 0.015);
fragColor = vec4(col, 1.0 - t * 0.015);
}
`,
`
#define hash(x) fract(sin(x) * 43758.5453123)
vec3 palette(float t) {
// Audio modulates the color phase, adding extra variation.
return 0.5 + 0.5 * sin(6.28318 * (t + vec3(0.2, 0.5, 0.7)) + u_audio * 0.5);
}
float noiseStep(float x, float range) {
const float offset = 0.25;
float i = floor(x);
float f = x - i;
float u = smoothstep(0.5 - offset, 0.5 + offset, f);
return (mix(floor(hash(i) * range), floor(hash(i + 1.0) * range), u) / (range - 1.0)) - 0.4;
}
vec3 motionOffset(vec3 position) {
// Apply an audio-based factor to modulate the noise amplitude (but not the time-based speed)
float audioFactor = 0.5 + u_audio;
return vec3(
noiseStep(position.y * 0.06, 6.0) * 2.5,
noiseStep(position.z * 0.07, 4.0) * 2.0,
noiseStep(position.x * 0.08, 5.0) * 1.2
) * audioFactor;
}
float triangleDist(vec2 p, float size) {
return abs(p.x) + abs(p.y) - size * 1.1;
}
vec3 rotateVector(vec3 vec, vec3 axis, float angle) {
return mix(dot(axis, vec) * axis, vec, cos(angle)) + cross(axis, vec) * sin(angle) * 1.1;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
vec3 color = vec3(0.0);
vec3 rayOrigin = vec3(0.0, 0.0, -6.0), rayTarget = vec3(0.0);
// Keep movement speed constant (only iTime affects z-offset)
rayOrigin.z += iTime * 1.5;
rayTarget.z += iTime * 1.5;
rayOrigin += motionOffset(rayOrigin);
rayTarget += motionOffset(rayTarget);
vec3 direction = normalize(rayTarget - rayOrigin);
vec3 right = vec3(direction.z, 0.0, -direction.x);
float accumulated = 0.0, distance = 0.0;
vec3 rayDir = mat3(right, cross(direction, right), direction) *
rotateVector(normalize(vec3(uv, 1.0)), vec3(0.0, 0.0, 1.0),
noiseStep(iTime + hash(uv.x * uv.y * iTime) * 0.2, 5.0));
for (int i = 0; i < 80; i++) {
float index = float(i) + 1.0;
vec3 position = rayOrigin + rayDir * distance;
position -= motionOffset(position);
float light = 0.0;
vec3 point = position;
float scale = 1.0;
for (int j = 0; j < 6; j++) {
float jIndex = float(j) + 1.0;
light += clamp(abs(dot(sin(point * 1.5), cos(point.yzx * 1.5)) * 0.7 - 0.15) / scale, -0.7, 0.7);
point = rotateVector(point, normalize(vec3(0.5, 0.4, 0.2)), 0.8 + jIndex);
point += point.yzx + jIndex * 10.0;
scale *= 1.3;
point *= 1.2;
}
float height = abs(triangleDist(position.xy, 12.0)) - 2.5 - light;
position = rotateVector(position, vec3(0.0, 0.0, 1.0), motionOffset(position).x * 0.2 + position.z * 0.6);
float thickness = length(abs(position.xy) - 0.25) - 0.07;
height = min(thickness, height);
float updateVal = max(0.001, thickness == height ? abs(height) : height);
accumulated = updateVal;
distance += updateVal;
// Audio influences the brightness modulation (without affecting movement speed)
color += (thickness == height
? vec3(0.9, 0.4, 0.6) * (40.0 * exp(-8.0 * fract(position.z * 0.1 + iTime)))
: vec3(0.03)) * (0.08 + u_audio * 0.05) / exp(index * index * accumulated);
}
// Blend with a near-white base; the blending factor is also slightly modulated by audio.
color = mix(color, vec3(1.0, 1.0, 0.9), 1.0 - exp(-0.02 * distance * (1.0 + u_audio * 0.2)));
fragColor = vec4(color, 1.0);
}
`,
`
#define hash(x) fract(sin(x) * 43758.5453123)
vec3 vibrantPalette(float t) {
// Modulate color with audio without affecting speed
return 0.5 + 0.5 * sin(6.28318 * (t + vec3(0.1, 0.9, 0.5)) + u_audio * 0.5);
}
float refinedNoise(float x, float scale) {
const float spread = 0.1;
float i = floor(x);
float f = x - i;
float s = smoothstep(0.5 - spread, 0.5 + spread, f);
return (mix(floor(hash(i) * scale), floor(hash(i + 1.0) * scale), s) / (scale - 1.0)) - 0.3;
}
vec3 dynamicShift(vec3 pos) {
// Apply audio to affect only the distortion strength.
float audioFactor = 0.5 + u_audio;
return vec3(
refinedNoise(pos.y * 0.05, 8.0) * 4.0,
refinedNoise(pos.z * 0.06, 7.0) * 5.0,
refinedNoise(pos.x * 0.04, 6.0) * 3.0
) * audioFactor;
}
float circleDistance(vec2 p, float radius) {
return length(p) - radius;
}
vec3 rotateAround(vec3 vec, vec3 axis, float theta) {
return mix(dot(axis, vec) * axis, vec, cos(theta)) + cross(axis, vec) * sin(theta) * 0.6;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
vec3 color = vec3(0.0);
vec3 start = vec3(0.0, 0.0, -10.0), end = vec3(0.0);
// Speed remains solely based on iTime.
start.z += iTime * 3.5;
end.z += iTime * 3.5;
start += dynamicShift(start);
end += dynamicShift(end);
vec3 direction = normalize(end - start);
vec3 lateral = vec3(direction.z, 0.0, -direction.x);
float lightAccumulation = 0.0, distanceTraveled = 0.0;
vec3 rayDir = mat3(lateral, cross(direction, lateral), direction) *
rotateAround(normalize(vec3(uv, 1.0)), vec3(0.0, 0.0, 1.0),
refinedNoise(iTime + hash(uv.x * uv.y * iTime) * 0.2, 5.0));
for (int i = 0; i < 70; i++) {
float idx = float(i) + 1.0;
vec3 currentPos = start + rayDir * distanceTraveled;
currentPos -= dynamicShift(currentPos);
float illuminationValue = 0.0;
vec3 tmp = currentPos;
float scaleVal = 1.0;
for (int j = 0; j < 4; j++) {
float jIdx = float(j) + 1.0;
illuminationValue += clamp(abs(dot(sin(tmp * 1.5), cos(tmp.yzx * 1.5)) * 0.7 - 0.1) / scaleVal, 0.0, 1.0);
tmp = rotateAround(tmp, normalize(vec3(0.5, 0.5, 0.5)), 0.5 + jIdx * 0.3);
tmp += tmp.yzx + jIdx * 4.0;
scaleVal *= 1.4;
tmp *= 1.1;
}
float depthMeasure = abs(circleDistance(currentPos.xy, 8.0)) - 1.5 - illuminationValue;
currentPos = rotateAround(currentPos, vec3(0.0, 0.0, 1.0), dynamicShift(currentPos).x * 0.25 + currentPos.z * 0.7);
float borderThickness = length(abs(currentPos.xy) - 0.5) - 0.2;
depthMeasure = min(borderThickness, depthMeasure);
float adjustAmount = max(0.001, borderThickness == depthMeasure ? abs(depthMeasure) : depthMeasure);
lightAccumulation = adjustAmount;
distanceTraveled += adjustAmount;
// Use u_audio to slightly boost vibrant contributions without affecting movement speed.
color += (borderThickness == depthMeasure
? vibrantPalette(currentPos.z * 0.5) * (20.0 * exp(-3.0 * fract(currentPos.z * 0.1 + iTime)))
: vec3(0.02)) * (0.1 + u_audio * 0.05) / exp(idx * idx * lightAccumulation);
}
color = mix(color, vec3(0.5, 0.7, 0.6), 1.0 - exp(-0.03 * distanceTraveled));
fragColor = vec4(color, 1.0);
}
`,
`
mat2 rotate(float angle) {
float c = cos(angle), s = sin(angle);
return mat2(c, s, -s, c);
}
const float PI = 3.14159265359;
const float TAU = PI * 2.0;
vec2 periodicMod(vec2 p, float res) {
float angle = atan(p.y, p.x) + PI / res;
float delta = TAU / res;
angle = floor(angle / delta) * delta;
return p * rotate(-angle);
}
float toroidalDistance(vec3 p, vec3 size) {
p = abs(p) - size;
return length(max(p, 0.0)) + min(max(p.x, max(p.y, p.z)), 0.0);
}
float fractalTorus(vec3 p, float u_audio) {
for (int i = 0; i < 6; i++) {
p = abs(p) - 1.0;
p.xy *= rotate(iTime * 0.5 + u_audio * 0.4);
p.xz *= rotate(iTime * 0.4);
p.yz *= rotate(iTime * 0.2);
}
return toroidalDistance(p, vec3(0.4, 0.4, 0.4));
}
float surfaceFunction(vec3 p, float u_audio) {
vec3 adjustedPos = p;
adjustedPos.x = mod(adjustedPos.x + 5.0, 10.0) - 5.0;
adjustedPos.y = mod(adjustedPos.y + 5.0, 10.0) - 5.0;
adjustedPos.z = mod(adjustedPos.z, 8.0) - 4.0;
adjustedPos.xy = periodicMod(adjustedPos.xy, 4.0);
return fractalTorus(adjustedPos, u_audio);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 p = (fragCoord * 2.0 - iResolution.xy) / min(iResolution.x, iResolution.y);
vec3 cameraPos = vec3(0.0, 3.0, -5.0 * iTime);
vec3 cameraDir = normalize(vec3(0.0, -0.6, -1.0));
vec3 cameraUp = vec3(sin(iTime * 0.3), 1.0, 0.0);
vec3 cameraSide = cross(cameraDir, cameraUp);
vec3 rayDirection = normalize(cameraSide * p.x + cameraUp * p.y + cameraDir);
float totalAcc = 0.0;
float highlightAcc = 0.0;
float t = 0.0;
for (int i = 0; i < 80; i++) {
vec3 pos = cameraPos + rayDirection * t;
float distance = surfaceFunction(pos, u_audio);
distance = max(abs(distance), 0.01);
float alpha = exp(-distance * (5.0 + u_audio * 1.0));
if (mod(length(pos) + 15.0 * iTime, 15.0) < 2.0) {
alpha *= 3.0;
highlightAcc += alpha;
}
totalAcc += alpha;
t += distance * 0.3;
}
vec3 finalColor = vec3(totalAcc * 0.05, totalAcc * 0.025 + highlightAcc * 0.01, totalAcc * 0.015 + highlightAcc * 0.005);
fragColor = vec4(finalColor, 1.0 - t * 0.004);
}
`,
`
float gTime = 0.0;
const float REPEAT = 4.0;
mat2 rotate(float angle) {
float c = cos(angle), s = sin(angle);
return mat2(c, s, -s, c);
}
float sdSphere(vec3 p, float radius) {
return length(p) - radius;
}
float createSphere(vec3 pos, float scale) {
return -sdSphere(pos * scale, 0.5);
}
float animatedSphereCluster(vec3 pos, float iTime) {
vec3 basePos = pos;
float dynamicOffset = cos(gTime * 1.8) * 1.5;
pos.y += dynamicOffset;
float s1 = createSphere(pos, 0.3 + 0.2 * cos(gTime * 1.5) + 0.1 * u_audio);
pos = basePos;
pos.y -= dynamicOffset;
float s2 = createSphere(pos, 0.3 + 0.3 * sin(gTime * 1.2) + 0.1 * u_audio);
pos = basePos;
pos.x += sin(gTime * 0.6) * dynamicOffset;
float s3 = createSphere(pos, 0.3 + 0.1 * cos(gTime * 1.8) + 0.1 * u_audio);
return max(max(s1, s2), s3);
}
float traceScene(vec3 pos, float iTime) {
return animatedSphereCluster(pos, iTime);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 p = (fragCoord * 2.0 - iResolution.xy) / min(iResolution.x, iResolution.y);
vec3 ro = vec3(0.0, -1.5, iTime * 2.5);
vec3 ray = normalize(vec3(p, 1.0));
ray.xy *= rotate(cos(iTime * 0.3 + u_audio) * 0.5);
ray.yz *= rotate(sin(iTime * 0.5 + u_audio) * 0.4);
float t = 0.1;
vec3 col = vec3(0.0);
float accumulation = 0.0;
for (int i = 0; i < 80; i++) {
vec3 pos = ro + ray * t;
pos = mod(pos + 1.5, 3.0) - 1.5;
gTime = iTime - float(i) * 0.02;
float d = traceScene(pos, iTime);
d = max(abs(d), 0.01);
accumulation += exp(-d * (40.0 + u_audio * 8.0));
t += d * 0.55;
}
col = vec3(accumulation * 0.08);
col += vec3(0.5 + 0.5 * cos(iTime * 0.45), 0.2 + 0.5 * sin(iTime), 0.8 + 0.2 * cos(iTime * 1.5));
fragColor = vec4(col, 1.0 - t * (0.02 + 0.03 * cos(iTime * 0.5)));
}
`,
`
float gTime = 0.0;
const float REPEAT = 5.0;
mat2 rotate(float angle) {
float c = cos(angle), s = sin(angle);
return mat2(c, s, -s, c);
}
float sdBoxShape(vec3 p, vec3 b) {
vec3 q = abs(p) - b;
return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
}
float createBox(vec3 pos, float scale) {
pos *= scale;
float baseShape = sdBoxShape(pos, vec3(0.5, 0.5, 0.1)) / 1.5;
pos.xy *= rotate(0.75 + u_audio * 0.5);
pos.y -= 3.0;
return -baseShape;
}
float animatedBoxSet(vec3 pos, float iTime) {
vec3 originalPos = pos;
pos.y += sin(gTime * (0.5 + u_audio * 0.3)) * 2.0;
pos.xy *= rotate(0.7);
float box1 = createBox(pos, 1.8 - abs(sin(gTime * 0.5)) * 1.5);
pos = originalPos;
pos.y -= sin(gTime * 0.5) * 2.0;
pos.xy *= rotate(0.7);
float box2 = createBox(pos, 1.8 - abs(sin(gTime * 0.5)) * 1.5);
pos = originalPos;
pos.x += sin(gTime * 0.5) * 2.0;
pos.xy *= rotate(0.7);
float box3 = createBox(pos, 1.8 - abs(sin(gTime * 0.5)) * 1.5);
pos = originalPos;
pos.x -= sin(gTime * 0.5) * 2.0;
pos.xy *= rotate(0.7);
float box4 = createBox(pos, 1.8 - abs(sin(gTime * 0.5)) * 1.5);
pos = originalPos;
pos.xy *= rotate(0.7);
float box5 = createBox(pos, 0.3) * 6.0;
pos = originalPos;
float box6 = createBox(pos, 0.3) * 6.0;
return max(max(max(max(max(box1, box2), box3), box4), box5), box6);
}
float sceneMap(vec3 pos, float iTime) {
return animatedBoxSet(pos, iTime);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 p = (fragCoord.xy * 2.0 - iResolution.xy) / min(iResolution.x, iResolution.y);
vec3 ro = vec3(0.0, -0.2, iTime * 4.0);
vec3 ray = normalize(vec3(p, 1.5));
ray.xy *= rotate(sin(iTime * 0.03 + u_audio) * 5.0);
ray.yz *= rotate(sin(iTime * 0.05 + u_audio) * 0.2);
float t = 0.1;
vec3 col = vec3(0.0);
float accumulation = 0.0;
for (int i = 0; i < 99; i++) {
vec3 pos = ro + ray * t;
pos = mod(pos - 2.0, 4.0) - 2.0;
gTime = iTime - float(i) * 0.01;
float d = sceneMap(pos, iTime);
d = max(abs(d), 0.01);
accumulation += exp(-d * (23.0 + u_audio * 5.0));
t += d * 0.55;
}
col = vec3(accumulation * 0.02);
col += vec3(0.0, 0.2 * abs(sin(iTime + u_audio)), 0.5 + sin(iTime) * 0.2);
fragColor = vec4(col, 1.0 - t * (0.02 + 0.02 * sin(iTime)));
}
`,
`
mat2 rotate(float angle) {
float c = cos(angle), s = sin(angle);
return mat2(c, s, -s, c);
}
const float PI = acos(-1.0);
const float PI2 = PI * 2.0;
vec2 pmod(vec2 position, float radius) {
float angle = atan(position.x, position.y) + PI / radius;
float interval = PI2 / radius;
angle = floor(angle / interval) * interval;
return position * rotate(-angle);
}
float torusShape(vec3 position, float radius1, float radius2) {
float d = length(vec2(length(position.xy) - radius1, position.z)) - radius2;
return d;
}
float dynamicTorus(vec3 position) {
// Audio modulates both the distortion and the torus radii.
float audioFactor = 0.5 + u_audio * 0.5;
position += vec3(
sin(iTime + position.y * 1.5 + u_audio * 0.5),
cos(iTime + position.x * 2.0 + u_audio * 0.5),
sin(iTime * 0.5 + u_audio * 0.5)
);
return torusShape(position, 1.0 * audioFactor, 0.4);
}
float distanceField(vec3 position) {
vec3 p1 = position;
p1.x = mod(p1.x + 6.0, 12.0) - 6.0;
p1.y = mod(p1.y + 6.0, 12.0) - 6.0;
p1.z = mod(p1.z + 6.0, 12.0) - 6.0;
p1.xy = pmod(p1.xy, 6.0);
return dynamicTorus(p1);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 p = (fragCoord * 2.0 - iResolution.xy) / min(iResolution.x, iResolution.y);
vec3 cameraPos = vec3(0.0, 0.0, -4.0);
vec3 cameraDir = normalize(vec3(0.0, 0.0, 1.0));
vec3 cameraUp = vec3(0.0, 1.0, 0.0);
vec3 cameraSide = cross(cameraDir, cameraUp);
vec3 ray = normalize(cameraSide * p.x + cameraUp * p.y + cameraDir);
float alphaAccumulation = 0.0;
float t = 0.0;
for (int i = 0; i < 100; i++) {
vec3 pos = cameraPos + ray * t;
float dist = distanceField(pos);
dist = max(abs(dist), 0.01);
float alpha = exp(-dist * 5.0);
alphaAccumulation += alpha;
t += dist * 0.2; // movement speed remains unaffected by u_audio
}
// Audio modulates the brightness of the final color.
vec3 color = vec3(
alphaAccumulation * (0.1 + u_audio * 0.05),
alphaAccumulation * (0.06 + u_audio * 0.05),
alphaAccumulation * (0.03 + u_audio * 0.05)
);
fragColor = vec4(color, 1.0 - t * 0.005);
}
`,
`
vec3 palette(float t) {
vec3 p0 = vec3(0.5, 0.5, 0.5);
vec3 p1 = vec3(0.3, 0.5, 0.7);
vec3 p2 = vec3(0.9, 1.0, 1.0);
vec3 p3 = vec3(0.1, 0.2, 0.4);
// Audio influences the phase shift of the palette without affecting timing
return p0 + p1 * cos(6.28318 * (p2 * t + p3) + u_audio * 0.5);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;
vec2 uvPattern = uv;
vec3 accumulatedColor = vec3(0.0);
for (float i = 0.0; i < 5.0; i++) {
uv = fract(uv * 1.2) - 0.5;
float dist = length(uv) * exp(-length(uvPattern));
// Incorporate u_audio into the palette parameter for extra variation.
vec3 color = palette(length(uvPattern) + i * 0.5 + iTime * 0.3 + u_audio * 0.3);
dist = sin(dist * 10.0 + iTime) / 10.0;
dist = abs(dist);
dist = pow(0.02 / dist, 1.5);
accumulatedColor += color * dist;
}
// Use audio to modulate overall brightness without affecting animation speed.
accumulatedColor *= 1.0 + u_audio * 0.2;
fragColor = vec4(accumulatedColor, 1.0);
}
`,`
#define T (iTime * 1.5)
#define DIST_MAX 100.0
#define rot(a) mat2(cos(a), -sin(a), sin(a), cos(a))
float smoothTanh(float x) {
return (exp(2.0 * x) - 1.0) / (exp(2.0 * x) + 1.0);
}
vec3 GetPosition(float z) {
return vec3(
smoothTanh(cos(z * 0.2) * 0.5) * 1.0 - 1.0,
1.0 + sin(T * 0.4) * 0.2,
z
);
}
float SphereDistance(vec3 p) {
return length(p - vec3(
GetPosition(p.z).x + sin(sin(p.z * 0.25) + T * 0.5) * 0.3,
GetPosition(p.z).y + sin(sin(p.z * 0.4) + T * 0.25) * 0.6 - 0.3,
5.0 + T + tan(cos(T * 0.25) * 0.4) * 5.0));
}
float TerrainMap(vec3 p) {
const float scale = 1.2;
float h = 2.0 - p.y;
float freq = 0.5;
for (int i = 0; i < 5; i++) {
h -= abs(dot(sin(p * (freq * 3.0)) * 0.35, vec3(1.0))) / freq * 0.15;
freq *= 1.5;
}
p.xy = mod(p.xy + 0.5, 3.0) - 1.5;
p.y *= 0.6;
p.z = mod(p.z, 4.0) - 1.5;
vec4 q = vec4(p, 1.0);
for (int i = 0; i < 6; i++) {
q.xyz = abs(sin(q.xyz * 1.2)) - 1.0;
q = scale * q / pow(length(q.xyz), 1.5) - vec4(0.0);
}
h = min(h, length(q.xyz) / q.w - 0.001);
return h;
}
vec3 ComputeNormal(vec3 p) {
vec2 e = vec2(0.02, 0.0);
vec3 n = TerrainMap(p) - vec3(
TerrainMap(p - vec3(e.x, e.y, e.y)),
TerrainMap(p - vec3(e.y, e.x, e.y)),
TerrainMap(p - vec3(e.y, e.y, e.x))
);
return normalize(n);
}
void mainImage(out vec4 o, in vec2 u) {
vec2 r = iResolution.xy;
u = (u - r * 0.5) / r.y;
vec3 p;
vec3 ro = GetPosition(T);
vec3 la = GetPosition(T + 3.0);
vec3 laz = normalize(la - ro);
vec3 lax = normalize(cross(laz, vec3(0.0, -1.0, 0.0)));
vec3 lay = cross(lax, laz);
vec3 rd = vec3(rot(sin(T * 0.2) * 0.3) * u, 1.0) * mat3(-lax, lay, laz);
float d = 0.0;
float s;
float steps = 0.0;
for (int i = 0; i < 50; i++) {
p = ro + rd * d;
s = TerrainMap(p);
d += s;
steps += 1.0;
if (d >= DIST_MAX || s <= 0.001) {
break;
}
}
if (d < DIST_MAX && steps < 50.0) {
vec3 c = vec3(0.8, 0.6, 0.7);
o.rgb = c + vec3(0.5);
o.rgb *= max(dot(ComputeNormal(p), normalize(ro - p)), 0.0) * 2.0;
o.rgb /= pow(SphereDistance(p), 1.5);
o.rgb *= mix(vec3(0.2, 0.4, 0.3) / d,
vec3(0.5, 0.2, 0.1) / d,
smoothstep(5.0, 1.0, d));
o.rgb = pow(o.rgb, vec3(0.35));
}
}
`,`
#define T (iTime * 2.5)
#define DIST_MAX 100.0
#define rot(a) mat2(cos(a), -sin(a), sin(a), cos(a))
float smoothCosh(float x) {
return (exp(x) + exp(-x)) * 0.4;
}
vec3 GetPosition(float z) {
return vec3(
smoothCosh(cos(z * 0.2) * 0.5) * 2.0 - 2.0,
1.0 + sin(T * 0.8) * 0.3,
z
);
}
float SphereDistance(vec3 p) {
return length(p - vec3(
GetPosition(p.z).x + cos(p.z * 0.5 + T * 0.4) * 0.6,
GetPosition(p.z).y + sin(p.z * 0.35 + T * 0.2) * 0.6 - 0.7,
5.0 + T + cos(T * 0.6) * 4.0));
}
float TerrainMap(vec3 p) {
const float scale = 1.2;
float h = 4.0 - p.y;
float freq = 1.0;
for (int i = 0; i < 5; i++) {
h -= abs(dot(cos(p * (freq * 1.5)), vec3(1.0))) / freq * 0.2;
freq *= 1.4;
}
p.xy = mod(p.xy + 0.1, 5.0) - 2.5;
p.y *= 0.75;
p.z = mod(p.z, 5.0) - 2.5;
vec4 q = vec4(p, 1.0);
for (int i = 0; i < 6; i++) {
q.xyz = abs(sin(q.xyz * 1.2)) - 1.0;
q = scale * q / pow(length(q.xyz), 1.5) - vec4(0.0);
}
h = min(h, length(q.xyz) / q.w - 0.002);
return h;
}
vec3 ComputeNormal(vec3 p) {
vec2 e = vec2(0.05, 0.0);
vec3 n = TerrainMap(p) - vec3(
TerrainMap(p - vec3(e.x, e.y, e.y)),
TerrainMap(p - vec3(e.y, e.x, e.y)),
TerrainMap(p - vec3(e.y, e.y, e.x))
);
return normalize(n);
}
void mainImage(out vec4 o, in vec2 u) {
vec2 r = iResolution.xy;
u = (u - r * 0.5) / r.y;
vec3 p;
vec3 ro = GetPosition(T);
vec3 la = GetPosition(T + 4.0);
vec3 laz = normalize(la - ro);
vec3 lax = normalize(cross(laz, vec3(0.0, 1.0, 0.0)));
vec3 lay = cross(lax, laz);
vec3 rd = vec3(rot(sin(T * 0.4) * 0.6) * u, 1.0) * mat3(-lax, lay, laz);
float d = 0.0;
float s;
float steps = 0.0;
for (int i = 0; i < 60; i++) {
p = ro + rd * d;
s = TerrainMap(p);
d += s;
steps += 1.0;
if (d >= DIST_MAX || s <= 0.001) {
break;
}
}
if (d < DIST_MAX && steps < 60.0) {
vec3 c = vec3(0.6, 0.3, 0.8);
o.rgb = c + vec3(0.5);
o.rgb *= max(dot(ComputeNormal(p), normalize(ro - p)), 0.0) * 2.0;
o.rgb /= pow(SphereDistance(p), 1.5);
o.rgb *= mix(vec3(0.5, 0.7, 0.6) / d,
vec3(0.8, 0.5, 0.4) / d,
smoothstep(5.0, 1.0, d));
o.rgb = pow(o.rgb, vec3(0.7));
}
}
`
];
// ================== HELPER FUNCTIONS ==================
function cleanShaderCode(shaderCode) {
if (shaderCode.startsWith("```glsl")) {
shaderCode = shaderCode.substring(shaderCode.indexOf("\n") + 1);
}
const lines = shaderCode.split("\n");
if (lines.length && lines[lines.length - 1].trim() === "```") {
lines.pop();
shaderCode = lines.join("\n");
}
return shaderCode;
}
function createShader(gl, type, source) {
if (type === gl.FRAGMENT_SHADER) {
const header = `precision highp float;
uniform vec2 iResolution;
uniform float iTime;
uniform float u_audio;
`;
const footer = `
void main() {
mainImage(gl_FragColor, gl_FragCoord.xy);
}
`;
source = header + source + footer;
}
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error("Shader compile error:", gl.getShaderInfoLog(shader));
console.error("Shader source code:\n" + source);
gl.deleteShader(shader);
return null;
}
return shader;
}
function createProgram(gl, vertexSource, fragmentSource) {
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
if (!vertexShader || !fragmentShader) return null;
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error("Program link error:", gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
return program;
}
function testShaderCompilation(shaderCode) {
const tempCanvas = document.createElement("canvas");
tempCanvas.width = 300;
tempCanvas.height = 300;
const gl = tempCanvas.getContext("webgl");
if (!gl) return false;
const prog = createProgram(gl, vertexShaderSource, shaderCode);
if (prog) {
gl.deleteProgram(prog);
return true;
}
return false;
}
// ================== LLM CALL FUNCTIONS ==================
async function generateCrossoverShader(selectedShaders) {
const prompt =
"You are a world-recognized artist and a programmer who implements astonishing GLSL shaders. " +
"Inspire from the following GLSL fragment shader codes into a new interesting shader code. " +
"Return only the shader code with no comments, explanations, or markdown formatting:\n" +
selectedShaders
.map((shader, index) => `Shader ${index + 1}:\n${shader}`)
.join("\n");
showProgress("Generating crossover shader...");
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + API_KEY
},
body: JSON.stringify({
model: "gpt-4o-mini",
messages: [{ role: "user", content: prompt }],
temperature: 1.0
})
});
if (!response.ok) {
const err = await response.text();
throw new Error("API request failed: " + err);
}
const data = await response.json();
if (!data.choices || data.choices.length === 0) {
console.error("Unexpected API response:", data);
throw new Error("No choices returned from the API.");
}
const rawCode = data.choices[0].message.content.trim();
hideProgress();
return cleanShaderCode(rawCode);
}
async function generateMutatedShader(shaderCode) {
const prompt =
"You are a world-recognized artist and shader programmer. Your aim is to design and implement astonishing GLSL shaders to showcase. " +
"Create an innovative and better variant of the following GLSL fragment shader code in a creative way while preserving its structure. " +
"Return only the shader code with no comments, explanations, or markdown formatting:\n" +
shaderCode;
showProgress("Generating shader mutations...");
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + API_KEY
},
body: JSON.stringify({
model: "gpt-4o-mini",
messages: [{ role: "user", content: prompt }],
temperature: 1.0
})
});
if (!response.ok) {
const err = await response.text();
throw new Error("API request failed: " + err);
}
const data = await response.json();
if (!data.choices || data.choices.length === 0) {
console.error("Unexpected API response:", data);
throw new Error("No choices returned from the API.");
}
const rawCode = data.choices[0].message.content.trim();
hideProgress();
return cleanShaderCode(rawCode);
}
// ================== VARIANT CREATION & MANAGEMENT ==================
let population = [];
function updateDownloadButtonState() {
const downloadButton = document.getElementById("downloadButton");
const hasSelection = population.some(v => v.isSelected);
downloadButton.disabled = !hasSelection;
}
function updateEvolveButtonState() {
const evolveButton = document.getElementById("evolveButton");
const hasSelection = population.some(v => v.isSelected);
evolveButton.disabled = !hasSelection;
}
function createVariant(id, shaderCode) {
const canvas = document.createElement("canvas");
canvas.classList.add("variant");
canvas.width = 300;
canvas.height = 300;
document.getElementById("container").appendChild(canvas);
const gl = canvas.getContext("webgl");
if (!gl) {
alert("WebGL not supported");
return;
}
const program = createProgram(gl, vertexShaderSource, shaderCode);
if (!program) {
console.error("Failed to compile shader for variant", id);
return;
}
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
const vertices = new Float32Array([
-1, -1,
1, -1,
-1, 1,
1, 1
]);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const a_position = gl.getAttribLocation(program, "a_position");
gl.enableVertexAttribArray(a_position);
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);
const uniforms = {
iTime: gl.getUniformLocation(program, "iTime"),
iResolution: gl.getUniformLocation(program, "iResolution"),
u_audio: gl.getUniformLocation(program, "u_audio")
};
const variant = {
id,
canvas,
gl,
program,
shaderCode,
uniforms,
isSelected: false,
timeStart: Date.now(),
originalSize: { width: canvas.width, height: canvas.height },
isFullscreen: false
};
// Long press detection variables
let longPressTimer = null;
let longPressTriggered = false;
// For mouse events
canvas.addEventListener("mousedown", (e) => {
longPressTriggered = false;
longPressTimer = setTimeout(() => {
longPressTriggered = true;
enterFullscreen();
document.body.classList.add("fullscreen-active");
}, 1500);
});
canvas.addEventListener("mouseup", (e) => {
clearTimeout(longPressTimer);
if (longPressTriggered) {
exitFullscreen();
document.body.classList.remove("fullscreen-active");
} else {
// Short press toggles selection
variant.isSelected = !variant.isSelected;
canvas.classList.toggle("selected", variant.isSelected);
updateDownloadButtonState();
updateEvolveButtonState();
}
});
// For touch events
canvas.addEventListener("touchstart", (e) => {
longPressTriggered = false;
longPressTimer = setTimeout(() => {
longPressTriggered = true;
enterFullscreen();
document.body.classList.add("fullscreen-active");
}, 1500);
});
canvas.addEventListener("touchend", (e) => {
clearTimeout(longPressTimer);
if (longPressTriggered) {
exitFullscreen();
document.body.classList.remove("fullscreen-active");
} else {
variant.isSelected = !variant.isSelected;
canvas.classList.toggle("selected", variant.isSelected);
updateDownloadButtonState();
updateEvolveButtonState();
}
});
function enterFullscreen() {
if (variant.isFullscreen) return;
variant.isFullscreen = true;
// Save current canvas size
variant.originalSize = { width: canvas.width, height: canvas.height };
// Set canvas to fullscreen
canvas.classList.add("fullscreen");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
function exitFullscreen() {
if (!variant.isFullscreen) return;
variant.isFullscreen = false;
canvas.classList.remove("fullscreen");
// Restore canvas size
canvas.width = variant.originalSize.width;
canvas.height = variant.originalSize.height;
}
function render() {
const now = Date.now();
const elapsed = (now - variant.timeStart) / 1000;
let audioValue = 0.0;
if (audioAnalyser) {
const fftValues = audioAnalyser.getValue();
let sum = 0, count = 0;
for (let i = 0; i < fftValues.length; i++) {
if (fftValues[i] > -100) {
sum += fftValues[i];
count++;
}
}
audioValue = count > 0 ? (sum / count) : 0.0;
audioValue = (audioValue + 50.0) / 50.0;
}
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(variant.program);
gl.uniform1f(variant.uniforms.iTime, elapsed);
gl.uniform2f(variant.uniforms.iResolution, canvas.width, canvas.height);
gl.uniform1f(variant.uniforms.u_audio, audioValue);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(render);
}
render();
return variant;
}
function updateVariantShader(variant, newShaderCode) {
const newProgram = createProgram(variant.gl, vertexShaderSource, newShaderCode);
if (newProgram) {
variant.shaderCode = newShaderCode;
variant.program = newProgram;
variant.uniforms.iTime = variant.gl.getUniformLocation(newProgram, "iTime");
variant.uniforms.iResolution = variant.gl.getUniformLocation(newProgram, "iResolution");
variant.uniforms.u_audio = variant.gl.getUniformLocation(newProgram, "u_audio");
const a_position = variant.gl.getAttribLocation(newProgram, "a_position");
variant.gl.useProgram(newProgram);
variant.gl.enableVertexAttribArray(a_position);
variant.gl.vertexAttribPointer(a_position, 2, variant.gl.FLOAT, false, 0, 0);
} else {
alert("Shader compilation failed for variant " + variant.id);
}
variant.isSelected = false;
variant.canvas.classList.remove("selected");
updateDownloadButtonState();
updateEvolveButtonState();
}
// ================== INITIALIZATION ==================
async function initPopulation() {
const container = document.getElementById("container");
container.innerHTML = "";
population = [];
baseShaders.forEach((shaderCode, idx) => {
const variant = createVariant(`base-${idx}`, shaderCode);
if (variant) population.push(variant);
});
const remaining = POPULATION_SIZE - population.length;
for (let i = 0; i < remaining; i++) {
const randomBase = baseShaders[Math.floor(Math.random() * baseShaders.length)];
let mutatedShader = null;
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
try {
const generated = await generateMutatedShader(randomBase);
if (testShaderCompilation(generated)) {
mutatedShader = generated;
break;
} else {
console.warn("Mutated shader failed to compile. Retrying...");
}
} catch (err) {
console.error("Mutation LLM error:", err);
}
}
if (!mutatedShader) {
console.warn("Falling back to base shader for extra variant " + i);
mutatedShader = randomBase;
}
const variant = createVariant(`mutated-${i}`, mutatedShader);
if (variant) population.push(variant);
}
document.getElementById("evolveButton").disabled = true;
}
// ================== EVOLUTION LOGIC ==================
async function evolvePopulation() {
const evolveButton = document.getElementById("evolveButton");
evolveButton.disabled = true;
showProgress("Evolving shaders...");
const selectedVariants = population.filter(v => v.isSelected);
if (selectedVariants.length === 0) {
alert("Please select at least one variant.");
evolveButton.disabled = true;
hideProgress();
return;
}
let baseForMutation;
if (selectedVariants.length === 1) {
baseForMutation = selectedVariants[0].shaderCode;
} else {
const selectedShaderCodes = selectedVariants.map(v => v.shaderCode);
let crossoverShader = null;
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
try {
const generated = await generateCrossoverShader(selectedShaderCodes);
if (testShaderCompilation(generated)) {
crossoverShader = generated;
break;
} else {
console.warn("Crossover shader failed to compile. Retrying...");
}
} catch (err) {
console.error("Crossover LLM error:", err);
}
}
if (!crossoverShader) {
alert("Failed to generate a valid crossover shader code.");
evolveButton.disabled = true;
hideProgress();
return;
}
baseForMutation = crossoverShader;
}
const updatePromises = population.map(async variant => {
if (!variant.isSelected) {
let mutatedShader = null;
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
try {
const generated = await generateMutatedShader(baseForMutation);
if (testShaderCompilation(generated)) {
mutatedShader = generated;
break;
} else {
console.warn(`Mutated shader for variant ${variant.id} failed to compile. Retrying...`);
}
} catch (err) {
console.error("Mutation LLM error:", err);
}
}
if (!mutatedShader) {
alert(`Failed to generate a valid mutated shader for variant ${variant.id}.`);
return;
}
updateVariantShader(variant, mutatedShader);
}
});
await Promise.all(updatePromises);
updateEvolveButtonState();
hideProgress();
}
// ================== DOWNLOAD SELECTED SHADERS ==================
function downloadSelectedShaders() {
const selectedVariants = population.filter(v => v.isSelected);
if (selectedVariants.length === 0) {
alert("No shaders selected for download.");
return;
}
const shadersText = selectedVariants.map(v => v.shaderCode).join("\n\n// ---------------------------\n\n");
const blob = new Blob([shadersText], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "selected_shaders.glsl";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// ================== INITIALIZATION & EVENT HANDLING ==================
initPopulation();
document.getElementById("evolveButton").addEventListener("click", evolvePopulation);
document.getElementById("downloadButton").addEventListener("click", downloadSelectedShaders);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment