From e309bc71d643faeeda2cbf9ca20f7f12cc2d716e Mon Sep 17 00:00:00 2001 From: Leonxlnx Date: Thu, 11 Jun 2026 17:47:47 +0200 Subject: [PATCH] Richer random styles, glass shaders, and eye-safe randomize tuning Add geometric genome patterns (bands, grids, hex, arcs, hatch, orbs), glass refraction shading, improved reeded and chrome materials, and smart exposure/contrast/lighting when randomizing so results stay pleasant. Co-authored-by: Cursor --- js/main.js | 112 +++++++++++++++++++++++++++++++++++++--------- js/palettes.js | 6 +-- js/shaders.js | 119 +++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 199 insertions(+), 38 deletions(-) diff --git a/js/main.js b/js/main.js index c8ea4ad..9f7233e 100644 --- a/js/main.js +++ b/js/main.js @@ -81,9 +81,20 @@ var RECIPES = { travel: [0.5, 1.1], loop: [5, 10] }, mosaic: { tone: ["light", "dark"], cell: [40, 120], warp: [0.3, 1.2], scale: [0.8, 1.5], grain: [0, 0.04], contrast: [0.95, 1.15], sat: [0.95, 1.3], vig: [0, 0.15], - travel: [0.5, 1.2], loop: [5, 10], soft: [0.8, 1.4] } + travel: [0.5, 1.2], loop: [5, 10], soft: [0.8, 1.4] }, + genome: { tone: ["dark", "light", "dark"], scale: [0.9, 1.7], complex: [2.5, 5.5], warp: [0.35, 1.3], + flow: [0.1, 0.8], stretch: [-0.2, 0.45], lines: [30, 100], cell: [50, 130], + grain: [0, 0.08], ca: [0, 0.15], vig: [0.05, 0.25], soft: [0.75, 1.35], + travel: [0.35, 0.95], loop: [4, 9] } }; +var FIELD_NAMES = ["FLUX", "RIDGE", "WAVE", "RING", "CELL", "FLOW", "BAND", "GRID", "HEX", "ARC", "HATCH", "ORB"]; +var FIELD_TYPES = FIELD_NAMES.length; +var DOMAIN_TYPES = 7; +var COLOR_MAPS = 6; +var SHADE_TYPES = 5; +var OVERLAY_TYPES = 5; + var FORM_KEYS = ["scale", "complex", "warp", "flow", "stretch"]; var LIGHT_KEYS = ["light", "gloss", "lightAngle", "irid", "glow", "contrast"]; var TEXTURE_KEYS = ["grain", "cell", "lines", "ca", "vig", "soft"]; @@ -94,11 +105,21 @@ var GRADE_KEYS = ["sat", "exposure"]; function rnd() { return Math.random(); } function randIn(range) { return range[0] + rnd() * (range[1] - range[0]); } +function recipeKey() { + if (P.genomeOn) return "genome"; + return MODES[P.mode].key; +} + function rangeFor(key) { - var rec = RECIPES[MODES[P.mode].key] || {}; + var rec = RECIPES[recipeKey()] || {}; return rec[key] || DEF_RANGE[key]; } +function pickTone() { + var rec = RECIPES[recipeKey()] || { tone: ["dark", "light"] }; + return rec.tone[Math.floor(rnd() * rec.tone.length)]; +} + var activePreset = 3; /* Ember — matches the startup design */ var setPresetActive = function () {}; @@ -110,19 +131,58 @@ function applyPalette(pal, presetIdx) { setPresetActive(presetIdx); } -function randomizePalette() { - var rec = RECIPES[MODES[P.mode].key] || { tone: ["dark", "light"] }; - var tone = rec.tone[Math.floor(rnd() * rec.tone.length)]; - if (rnd() < 0.25) { - applyPalette(generateRandomPalette(rnd, tone), -1); - } else { +function randomizePalette(tone) { + tone = tone || pickTone(); + if (rnd() < 0.78) { var pool = []; PALETTES.forEach(function (p, i) { if (p.tone === tone) pool.push(i); }); + if (!pool.length) pool = PALETTES.map(function (_, i) { return i; }); var idx = pool[Math.floor(rnd() * pool.length)]; applyPalette(PALETTES[idx], idx); + } else { + applyPalette(generateRandomPalette(rnd, tone), -1); } P.hue = 0; - GRADE_KEYS.forEach(function (k) { P[k] = randIn(rangeFor(k)); }); +} + +/* tuned grading + lighting so random hits stay pleasant, not harsh */ +function smartTune(tone) { + var key = recipeKey(); + var isLight = tone === "light"; + var isGlass = key === "chrome" || key === "reeded" || key === "silk" || + (P.genomeOn && Math.round(P.genes[5]) === 4); + var isSoft = key === "bloom" || key === "aura" || key === "mosaic" || + key === "rays" || key === "halftone"; + + P.exposure = isLight ? randIn([0.94, 1.06]) : randIn([0.90, 1.04]); + P.contrast = isLight ? randIn([0.94, 1.10]) : randIn([0.92, 1.12]); + P.sat = isLight ? randIn([0.82, 1.06]) : randIn([0.86, 1.16]); + P.hue = randIn([-14, 14]); + + if (isGlass) { + P.light = randIn([0.62, 1.32]); + P.gloss = Math.round(randIn([26, 92])); + P.lightAngle = Math.round(randIn([0, 360])); + P.irid = randIn([0, 0.38]); + P.glow = randIn([0, 0.2]); + P.soft = randIn([0.82, 1.28]); + } else if (isSoft) { + P.light = randIn([0.12, 0.72]); + P.gloss = Math.round(randIn([10, 44])); + P.lightAngle = Math.round(randIn([0, 360])); + P.irid = randIn([0, 0.22]); + P.glow = randIn([0, 0.18]); + } else { + P.light = randIn([0.48, 1.28]); + P.gloss = Math.round(randIn([14, 76])); + P.lightAngle = Math.round(randIn([0, 360])); + P.irid = randIn([0, 0.32]); + P.glow = randIn([0, 0.28]); + } + + P.ca = randIn([0, 0.16]); + P.vig = randIn([0.05, 0.26]); + P.grain = randIn([0, isLight ? 0.055 : 0.085]); } function randomizeKeys(keys) { @@ -139,19 +199,25 @@ function newSeed() { P.seed = Math.floor(rnd() * 10000); } /* genome: 12 genes define a complete standalone style that does not exist in the base set. fields x domains x colors x shading x overlays gives thousands of distinct archetypes. */ +function pickFieldType() { + /* mix organic noise fields with clean geometric patterns */ + if (rnd() < 0.48) return 6 + Math.floor(rnd() * (FIELD_TYPES - 6)); + return Math.floor(rnd() * 6); +} + function generateGenomeStyle() { P.genes = [ - Math.floor(rnd() * 6), // 0 field type - Math.floor(rnd() * 5), // 1 domain op - rnd() * 1.6, // 2 extra warp + pickFieldType(), // 0 field type + Math.floor(rnd() * DOMAIN_TYPES), // 1 domain op + 0.15 + rnd() * 1.0, // 2 extra warp 2 + Math.floor(rnd() * 6), // 3 fold count - Math.floor(rnd() * 4), // 4 color map - Math.floor(rnd() * 4), // 5 shading - Math.floor(rnd() * 4), // 6 overlay - 0.3 + rnd() * 1.2, // 7 overlay scale + Math.floor(rnd() * COLOR_MAPS), // 4 color map + Math.floor(rnd() * SHADE_TYPES),// 5 shading + Math.floor(rnd() * OVERLAY_TYPES), // 6 overlay + 0.25 + rnd() * 1.0, // 7 overlay scale rnd(), // 8 ridge sharpness rnd(), // 9 poster steps - 0.2 + rnd() * 0.9, // 10 field scale + 0.25 + rnd() * 0.85, // 10 field scale rnd() // 11 rotation ]; P.genomeOn = true; @@ -162,8 +228,8 @@ function genomeName() { var g = P.genes; var n = (g[0] * 7 + g[1] * 13 + g[4] * 29 + g[5] * 47 + g[6] * 71 + Math.round(g[11] * 99)) % 1000; - var FIELD = ["FLUX", "RIDGE", "WAVE", "RING", "CELL", "FLOW"]; - return FIELD[g[0]] + " " + String(Math.round(n)).padStart(3, "0"); + var ft = Math.min(Math.max(Math.round(g[0]) || 0, 0), FIELD_TYPES - 1); + return FIELD_NAMES[ft] + " " + String(Math.round(n)).padStart(3, "0"); } function styleName() { @@ -175,7 +241,7 @@ function styleName() { function randomizeAll() { if (!P.lockStyle) { - if (rnd() < 0.35) { + if (rnd() < 0.42) { generateGenomeStyle(); } else { P.synthOn = false; @@ -183,8 +249,10 @@ function randomizeAll() { P.mode = Math.floor(rnd() * MODES.length); } } - randomizePalette(); - randomizeKeys(FORM_KEYS.concat(LIGHT_KEYS, TEXTURE_KEYS, MOTION_KEYS)); + var tone = pickTone(); + randomizePalette(tone); + randomizeKeys(FORM_KEYS.concat(TEXTURE_KEYS, MOTION_KEYS)); + smartTune(tone); newSeed(); refreshAll(); } diff --git a/js/palettes.js b/js/palettes.js index 8d8b958..e65c8f9 100644 --- a/js/palettes.js +++ b/js/palettes.js @@ -50,10 +50,10 @@ function generateRandomPalette(rand, tone) { var dark = tone === "dark"; var colors = sch.map(function (off, i) { var hue = ((h + off) % 360 + 360) % 360; - var s = dark ? 0.75 + rand() * 0.25 : 0.55 + rand() * 0.35; + var s = dark ? 0.48 + rand() * 0.32 : 0.38 + rand() * 0.28; var l = dark - ? (i === 0 ? 0.52 : 0.28 + rand() * 0.45) - : (i === 0 ? 0.5 : 0.45 + rand() * 0.4); + ? (i === 0 ? 0.48 : 0.26 + rand() * 0.38) + : (i === 0 ? 0.46 : 0.42 + rand() * 0.34); return hslToHex(hue, s, l); }); var bg = dark diff --git a/js/shaders.js b/js/shaders.js index 327ab54..9b6ab58 100644 --- a/js/shaders.js +++ b/js/shaders.js @@ -179,6 +179,10 @@ vec3 sceneChrome(vec2 uv){ col += alb2 * spec2 * u_light * 1.35; col += palette(clamp(fres*0.85 + u_irid*n.y*0.4, 0.0, 1.0)) * fres * u_light * 0.55; col += vec3(1.0) * pow(spec, 3.0) * u_light * 0.5; + /* clearcoat glass sheen on top of the metal */ + float coat = pow(max(dot(n, normalize(vec3(0.0, 0.0, 1.0))), 0.0), 18.0); + col += vec3(1.0) * coat * u_light * 0.22; + col = mix(col, col + palette(fres*0.5)*0.08, fres*0.35); return col; } @@ -383,19 +387,37 @@ vec3 boldField(vec2 p){ return col; } +vec3 sampleReeded(vec2 suv, float blur){ + vec3 c0 = boldField(toP(suv)*0.8); + vec3 c1 = boldField(toP(suv + vec2(blur, blur*0.22))*0.8); + vec3 c2 = boldField(toP(suv - vec2(blur, blur*0.22))*0.8); + return c0*0.52 + c1*0.24 + c2*0.24; +} + vec3 sceneReeded(vec2 uv){ - float nx = uv.x * u_lines*0.55; + float ridgeFreq = max(u_lines*0.55, 6.0); + float nx = uv.x * ridgeFreq; float ci = floor(nx); - float lx = fract(nx)-0.5; + float lx = fract(nx) - 0.5; - float srcX = (ci + 0.5 + lx*0.20 + sin(lx*PI)*0.34*u_warp) / (u_lines*0.55); - vec2 suv = vec2(srcX, uv.y); - vec3 col = boldField(toP(suv)*0.8); + /* lens refraction across each flute */ + float lens = sin(lx*PI); + float refr = lx*0.28*u_warp + lens*0.11*u_warp; + float srcX = (ci + 0.5 + refr) / ridgeFreq; + float blur = 0.0035*u_soft*(0.6 + u_warp*0.5); + vec3 col = sampleReeded(vec2(srcX, uv.y), blur); - float shade = 0.86 + 0.26*cos(lx*PI); - float edge = smoothstep(0.5, 0.465, abs(lx)); - col *= mix(0.62, shade, edge); - col += vec3(1.0)*pow(max(cos(lx*PI), 0.0), 30.0)*0.12*u_light; + float ridge = cos(lx*PI); + float shade = 0.74 + 0.30*ridge; + float groove = smoothstep(0.48, 0.40, abs(lx)); + col *= mix(0.50, shade, groove); + + /* fresnel rim + peak specular */ + float fres = pow(1.0 - abs(ridge), 2.6); + float specPow = mix(10.0, 48.0, clamp(u_gloss/120.0, 0.0, 1.0)); + float spec = pow(max(ridge, 0.0), specPow); + col += vec3(1.0)*spec*u_light*0.20; + col += mix(vec3(0.0), col*0.35 + vec3(0.06, 0.05, 0.04), fres*u_light*0.42); return col; } @@ -452,9 +474,41 @@ float gnField(int ft, vec2 p){ float v = gnVoro(p*1.4 + lt*0.8); return pow(clamp(v, 0.0, 1.0), 0.8 + u_g3.x*2.0); } - /* flow: fbm fed through itself */ - float f1 = fbm(p + so + lt); - return fbm(p + 2.4*vec2(f1, fbm(p + so*1.3 - lt)) + so); + if (ft == 5) { /* flow */ + float f1 = fbm(p + so + lt); + return fbm(p + 2.4*vec2(f1, fbm(p + so*1.3 - lt)) + so); + } + if (ft == 6) { /* cross bands */ + float bx = sin(p.x*(2.2 + u_g3.x*4.5) + lt.x*2.0); + float by = cos(p.y*(1.6 + u_g3.x*3.2) - lt.y*2.0); + return bx*by*0.28 + 0.5 + fbm(p*0.25 + so)*0.08; + } + if (ft == 7) { /* plaid grid */ + vec2 q = p*(1.8 + u_g3.z*1.2); + float gx = step(0.5, fract(q.x + fbm(p*0.35 + so)*0.15)); + float gy = step(0.5, fract(q.y + fbm(p.yx*0.35 - so)*0.15)); + return mix(gx*gy, 1.0 - gx*gy, 0.5 + 0.5*sin(lt.x*3.0)); + } + if (ft == 8) { /* hex lattice edges */ + float v = gnVoro(p*1.65 + lt*0.55); + return smoothstep(0.02, 0.20, v); + } + if (ft == 9) { /* concentric arcs */ + float a = atan(p.y, p.x) + lt.x*0.6; + float d = length(p); + return fract(sin(a*3.0 + u_seed*0.01)*0.5 + d*(0.75 + u_g3.x) + fbm(p*0.28 + so)*0.18); + } + if (ft == 10) { /* hatch lines */ + float h1 = sin((p.x + p.y)*(6.0 + u_g3.x*6.0) + lt.x); + float h2 = sin((p.x - p.y)*(6.0 + u_g3.x*6.0) - lt.y); + float hatch = smoothstep(-0.15, 0.55, h1*h2); + return mix(fbm(p*0.45 + so)*0.35, hatch, 0.72); + } + /* soft orb grid */ + vec2 cell = fract(p*(0.85 + u_g3.z*0.8)) - 0.5; + float d = length(cell); + float pulse = sin(TAU*u_phase + hash21(floor(p*(0.85 + u_g3.z*0.8)))*6.28)*0.08; + return smoothstep(0.34 + pulse, 0.04, d); } vec2 gnDomain(int dop, vec2 p){ @@ -473,6 +527,19 @@ vec2 gnDomain(int dop, vec2 p){ if (dop == 4) { /* soft grid repeat */ return (fract(p*0.5) - 0.5)*2.6; } + if (dop == 5) { /* hex mirror */ + float seg = TAU/6.0; + float a = atan(p.y, p.x); + a = abs(mod(a, seg) - seg*0.5); + return vec2(cos(a), sin(a))*length(p); + } + if (dop == 6) { /* brick weave */ + vec2 q = p*0.62; + vec2 cell = floor(q); + vec2 f = fract(q) - 0.5; + if (mod(cell.y, 2.0) > 0.5) f.x += 0.5; + return f*2.4; + } return p; } @@ -487,6 +554,14 @@ vec3 gnColor(int cm, float t, vec2 p){ vec3 c = mix(u_bg, u_c1, smoothstep(0.15, 0.75, t)); return mix(c, u_c3, smoothstep(0.82, 0.98, t)); } + if (cm == 4) { /* clean bands */ + float bands = 4.0 + floor(u_g3.y*5.0); + return palette(floor(t*bands)/max(bands - 1.0, 1.0)); + } + if (cm == 5) { /* angular sweep */ + float ang = atan(p.y, p.x)/PI*0.5 + 0.5; + return paletteCyc(mix(t, ang, 0.52)); + } return palette(t); } @@ -522,8 +597,22 @@ vec3 sceneGenome(vec2 uv){ col = mix(u_bg, col, 0.35); col += palette(clamp(f + 0.2, 0.0, 1.0)) * smoothstep(0.01, 0.14, g) * u_light * 1.4; } else if (sh == 3) { /* deep contrast carve */ - col *= smoothstep(0.0, 0.55, f)*1.25; + col *= smoothstep(0.0, 0.55, f)*1.15; col = mix(u_bg, col, smoothstep(0.08, 0.35, f)); + } else if (sh == 4) { /* glass refraction */ + float e = 0.055; + float fx = gnField(ft, p + vec2(e, 0.0)); + float fy = gnField(ft, p + vec2(0.0, e)); + vec2 grad = vec2((fx - f)/e, (fy - f)/e); + vec2 pRef = p - grad*(0.12 + u_g1.z*0.22); + float f2 = gnField(ft, pRef); + col = gnColor(cm, f2*1.08 - 0.04, pRef); + float fres = pow(1.0 - clamp(length(grad)*1.8, 0.0, 1.0), 2.4); + float flute = 0.86 + 0.14*abs(sin(p0.x*u_lines*0.32 + p0.y*0.18)); + col *= flute; + col += vec3(1.0)*fres*u_light*0.14; + float spec = pow(max(1.0 - abs(grad.x + grad.y)*0.5, 0.0), mix(8.0, 36.0, u_gloss/120.0)); + col += vec3(1.0)*spec*u_light*0.18; } if (ov == 1) { /* stripes */ @@ -534,6 +623,10 @@ vec3 sceneGenome(vec2 uv){ col *= 0.78 + 0.22*smoothstep(0.42, 0.30, length(g)); } else if (ov == 3) { /* scanlines */ col *= 0.86 + 0.14*sin(uv.y*u_res.y*0.7 + f*3.0); + } else if (ov == 4) { /* fine weave */ + float wx = sin(p0.x*28.0 + f*4.0); + float wy = sin(p0.y*28.0 - f*4.0); + col *= 0.90 + 0.10*smoothstep(-0.2, 0.5, wx*wy); } return col;