Created
March 27, 2025 22:34
-
-
Save kayuksel/ea218cd57d88fae234c99e121e5e5153 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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