Back to shaders
Shader test bench
20251126
runnable fragment
Complete GLSL fragment shader. Stronghold runs it directly when the browser can compile it.
Code
precision mediump float;
#define fragColor gl_FragColor
uniform float u_time;
uniform vec2 u_resolution;
// Color palette - purple and blue theme
const vec3 color1 = vec3(0.212, 0.176, 0.471); // #362d78
const vec3 color2 = vec3(0.322, 0.247, 0.639); // #523fa3
const vec3 color3 = vec3(0.569, 0.424, 0.800); // #916ccc
const vec3 color4 = vec3(0.741, 0.631, 0.898); // #bda1e5
const vec3 color5 = vec3(0.784, 0.753, 0.914); // #c8c0e9
const vec3 color6 = vec3(0.518, 0.729, 0.906); // #84bae7
const vec3 color7 = vec3(0.318, 0.416, 0.831); // #516ad4
const vec3 color8 = vec3(0.200, 0.247, 0.529); // #333f87
const vec3 color9 = vec3(0.161, 0.188, 0.224); // #293039
#define PI 3.14159265359
#define PHI 1.618033988749 // Golden ratio
// Rotation matrices
mat2 rot(float a) {
float c = cos(a), s = sin(a);
return mat2(c, -s, s, c);
}
mat3 rotateY(float a) {
float c = cos(a), s = sin(a);
return mat3(c, 0, s, 0, 1, 0, -s, 0, c);
}
// Hash function for procedural randomness
float hash(vec3 p) {
p = fract(p * 0.3183099 + 0.1);
p *= 17.0;
return fract(p.x * p.y * p.z * (p.x + p.y + p.z));
}
// 3D noise function
float noise(vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
return mix(mix(mix(hash(i + vec3(0, 0, 0)), hash(i + vec3(1, 0, 0)), f.x),
mix(hash(i + vec3(0, 1, 0)), hash(i + vec3(1, 1, 0)), f.x), f.y),
mix(mix(hash(i + vec3(0, 0, 1)), hash(i + vec3(1, 0, 1)), f.x),
mix(hash(i + vec3(0, 1, 1)), hash(i + vec3(1, 1, 1)), f.x), f.y), f.z);
}
// Fractal Brownian Motion
float fbm(vec3 p) {
float value = 0.0;
float amplitude = 0.5;
for(int i = 0; i < 5; i++) {
value += amplitude * noise(p);
p *= 2.0;
amplitude *= 0.5;
}
return value;
}
// Signed Distance Functions
float sdSphere(vec3 p, float r) {
return length(p) - r;
}
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 sdOctahedron(vec3 p, float s) {
p = abs(p);
float m = p.x + p.y + p.z - s;
vec3 q;
if(3.0 * p.x < m) q = p.xyz;
else if(3.0 * p.y < m) q = p.yzx;
else if(3.0 * p.z < m) q = p.zxy;
else return m * 0.57735027;
float k = clamp(0.5 * (q.z - q.y + s), 0.0, s);
return length(vec3(q.x, q.y - s + k, q.z - k));
}
// Smooth minimum function for organic blending
float smin(float a, float b, float k) {
float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
return mix(b, a, h) - k * h * (1.0 - h);
}
// TODO(human)
// Grid repetition function
vec3 repeat(vec3 p, vec3 c) {
return mod(p + 0.5 * c, c) - 0.5 * c;
}
// Scene composition with grid system
float map(vec3 p) {
// Domain warping using mathematical formulas
float warp = fbm(p * 0.5 + u_time * 0.1) * 0.3;
// Apply Fibonacci spiral warping
float angle = atan(p.z, p.x) + u_time * 0.2;
float radius = length(p.xz);
p.y += sin(angle * PHI + u_time) * 0.2;
// Store original position for effects
vec3 originalP = p;
// Rotating global space
p.xz *= rot(u_time * 0.1);
p.xy *= rot(sin(u_time * 0.3) * 0.2);
// Grid system 1: Infinite octahedron grid
vec3 gridSize = vec3(3.0, 3.0, 3.0);
vec3 gridP = repeat(p, gridSize);
// Rotate each cell based on its position
vec3 cellId = floor((p + 0.5 * gridSize) / gridSize);
float cellHash = hash(cellId);
gridP.xy *= rot(u_time * 0.5 + cellHash * PI * 2.0);
gridP.yz *= rot(u_time * 0.3 + cellHash * PI);
float octGrid = sdOctahedron(gridP, 0.3 + sin(u_time + cellHash * 6.28) * 0.1);
// Grid system 2: Box lattice with sine wave modulation
vec3 boxGridSize = vec3(2.5);
vec3 boxP = repeat(p + vec3(1.25), boxGridSize);
vec3 boxId = floor((p + vec3(1.25) + 0.5 * boxGridSize) / boxGridSize);
float boxHash = hash(boxId);
// Modulate size with sine waves
float sizeModulation = sin(length(boxId) * 0.5 + u_time) * 0.5 + 0.5;
float boxGrid = sdBox(boxP, vec3(0.2 + sizeModulation * 0.15));
// Central sphere with golden ratio scaling
float centralSphere = sdSphere(p, 1.5 + sin(u_time * PHI) * 0.3);
// Toroidal structure following parametric equation
vec3 torusP = p;
torusP.yz *= rot(u_time * 0.4);
float majorR = 3.0 + sin(u_time * 0.7) * 0.5;
float minorR = 0.3;
vec2 q = vec2(length(torusP.xz) - majorR, torusP.y);
float torusMain = length(q) - minorR;
// Mandelbrot-inspired displacement
float displacement = sin(10.0 * originalP.x + u_time) *
sin(10.0 * originalP.y - u_time * 0.7) *
sin(10.0 * originalP.z + u_time * 0.5) * 0.05;
// Combine all elements
float d = min(octGrid, boxGrid);
d = max(d, -centralSphere); // Carve out center
d += displacement; // Add surface detail
d = min(d, torusMain);
// Add FBM displacement for organic feel
d += fbm(originalP * 2.0 + u_time * 0.2) * 0.05;
return d;
}
// Calculate normal for lighting
vec3 calcNormal(vec3 p) {
const float h = 0.001;
const vec2 k = vec2(1, -1);
return normalize(k.xyy * map(p + k.xyy * h) +
k.yyx * map(p + k.yyx * h) +
k.yxy * map(p + k.yxy * h) +
k.xxx * map(p + k.xxx * h));
}
// Raymarching function
float rayMarch(vec3 ro, vec3 rd) {
float t = 0.0;
for (int i = 0; i < 80; i++) {
vec3 p = ro + rd * t;
float d = map(p);
if (d < 0.001 || t > 100.0) break;
t += d;
}
return t;
}
void main() {
// Normalize coordinates
vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution.xy) / u_resolution.y;
// Camera setup
vec3 ro = vec3(0.0, 0.0, 8.0); // ray origin
vec3 rd = normalize(vec3(uv, -1.0)); // ray direction
// Rotate camera
rd.xz *= rot(sin(u_time * 0.1) * 0.3);
rd.yz *= rot(cos(u_time * 0.15) * 0.2);
// Raymarch
float t = rayMarch(ro, rd);
// Background color
vec3 col = mix(color9, color1, uv.y * 0.5 + 0.5);
col = mix(col, color8, length(uv) * 0.3);
// If we hit something
if (t < 100.0) {
vec3 p = ro + rd * t;
vec3 n = calcNormal(p);
// Multiple light sources
vec3 lightPos1 = vec3(sin(u_time) * 5.0, 5.0, cos(u_time) * 5.0);
vec3 lightPos2 = vec3(-sin(u_time * 0.7) * 4.0, 3.0, -cos(u_time * 0.7) * 4.0);
vec3 lightDir1 = normalize(lightPos1 - p);
vec3 lightDir2 = normalize(lightPos2 - p);
// Diffuse lighting from multiple sources
float diff1 = max(dot(n, lightDir1), 0.0);
float diff2 = max(dot(n, lightDir2), 0.0);
// Specular lighting
vec3 viewDir = normalize(ro - p);
vec3 halfDir1 = normalize(lightDir1 + viewDir);
vec3 halfDir2 = normalize(lightDir2 + viewDir);
float spec1 = pow(max(dot(n, halfDir1), 0.0), 32.0);
float spec2 = pow(max(dot(n, halfDir2), 0.0), 16.0);
// Mathematical color distribution using FBM and grid patterns
float colorNoise = fbm(p * 1.5 + u_time * 0.1);
float gridPattern = sin(p.x * PI * 2.0) * sin(p.y * PI * 2.0) * sin(p.z * PI * 2.0);
// Create color layers using golden ratio and Fibonacci patterns
float layer1 = sin(length(p) * PHI - u_time) * 0.5 + 0.5;
float layer2 = sin(atan(p.y, p.x) * 8.0 + u_time * 0.5) * 0.5 + 0.5;
float layer3 = sin(length(p.xz) * 3.0 + p.y - u_time * 0.3) * 0.5 + 0.5;
// Build base color from mathematical patterns
vec3 objColor = mix(color1, color2, layer1);
objColor = mix(objColor, color3, layer2 * colorNoise);
objColor = mix(objColor, color4, layer3);
// Add grid-influenced coloring
objColor = mix(objColor, color7, gridPattern * 0.3 * (sin(u_time * 0.5) * 0.5 + 0.5));
// Distance-based color variation
float distField = length(mod(p, 2.0) - 1.0);
objColor = mix(objColor, color6, smoothstep(0.3, 0.7, distField) * 0.4);
// Fresnel effect with color variation
float fresnel = pow(1.0 - max(dot(n, viewDir), 0.0), 3.0);
vec3 fresnelColor = mix(color7, color6, sin(u_time * 0.3) * 0.5 + 0.5);
// Combine lighting with color-tinted lights
col = objColor * (diff1 * 0.6 + diff2 * 0.4 + 0.2);
col += color5 * spec1 * 0.8;
col += color3 * spec2 * 0.5;
col += fresnelColor * fresnel * 0.4;
// Add ambient occlusion approximation
float ao = 1.0 - smoothstep(0.0, 2.0, t * 0.1);
col *= ao * 0.3 + 0.7;
// Atmospheric depth with color shift
vec3 fogColor = mix(color1, color8, sin(u_time * 0.2) * 0.5 + 0.5);
col = mix(col, fogColor, smoothstep(0.0, 50.0, t) * 0.6);
// Add rim lighting effect
float rim = 1.0 - max(dot(n, viewDir), 0.0);
rim = smoothstep(0.6, 1.0, rim);
col += color4 * rim * 0.3;
} else {
// Procedural starfield using mathematical patterns
vec2 starUV = uv * 20.0;
vec2 starId = floor(starUV);
vec2 starLocal = fract(starUV);
float starHash = fract(sin(dot(starId, vec2(12.9898, 78.233))) * 43758.5453);
float starDist = length(starLocal - 0.5);
float star = smoothstep(0.05, 0.0, starDist) * step(0.95, starHash);
// Twinkling stars
star *= sin(u_time * 3.0 + starHash * 6.28) * 0.5 + 0.5;
col += mix(color5, color6, starHash) * star * 0.8;
// Nebula effect in background
float nebula = fbm(vec3(uv * 2.0, u_time * 0.05));
col += mix(color2, color8, nebula) * nebula * 0.15;
}
// Vignette
col *= 1.0 - length(uv) * 0.3;
// Color grading
col = pow(col, vec3(0.9)); // Slight gamma adjustment
fragColor = vec4(col, 1.0);
}