Free 60FPS WebGL Mesh Gradient Studio for Mac & Web
Design organic, multi-point fluid gradients powered by hardware-accelerated GLSL fragment shaders. Export pixel-perfect 8K wallpapers for your MacBook Pro and Studio Display, smooth 60FPS looping MP4 videos, and interactive WebGL embed code for web apps.
60FPS WebGL Shaders
Hardware-accelerated simplex noise algorithms render fluid, continuous color waves on your GPU with zero CPU strain or frame drops.
Zero-Mud Color Theory
Over 55 curated palettes audited for harmonic color wheel transitions (ΔHue ≤ 50°) to eliminate muddy, desaturated blending dead zones.
60FPS Looping Videos
Record silky 5s to 15s looping MP4 and WebM videos directly in your browser. Perfect for website heroes, app previews, and live wallpapers.
Embeddable WebGL Code
Export ultra-crisp 4K/8K PNGs for Mac screens or copy clean, ultra-optimized WebGL code running at 60FPS with zero runtime dependencies.
Apple Device Wallpaper Resolution Guide
Choose the exact aspect ratio in the studio toolbar to ensure your mesh wallpaper fits without vertical cropping or stretching.
| Device Type | Aspect Ratio | Target Display | Recommended Export |
|---|---|---|---|
| MacBook Pro 16" & 14" | 16:10 | Liquid Retina XDR (3456 × 2234) | 4K / 8K UHD |
| MacBook Air 13" & 15" | 16:10 | Liquid Retina (2880 × 1864) | 4K UHD |
| Studio Display & Pro Display XDR | 16:9 | 5K / 6K Retina (5120 × 2880 / 6016 × 3384) | 8K UHD (7680 × 4320) |
| iPhone 16 Pro & 15 Pro | 9:16 | Super Retina XDR (1290 × 2796) | 2K / 4K Mobile |
| iPad Pro 11" & 13" (M4) | 4:3 | Ultra Retina XDR (2064 × 2752) | 4K Tablet |
| Social Avatars & App Icons | 1:1 | Square Graphics & Icons | 2048 × 2048 / 4096 × 4096 |
How to Create Custom Mesh Gradients in 5 Steps
Produce stunning, bespoke wallpapers and website backgrounds in seconds using intuitive Mac-inspired controls.
Pick or Shuffle a Palette
Press Space to generate harmonic palettes or browse 55+ audited schemes including Aurora, Sunset, Cyberpunk, and Deep Space.
Sculpt the Shader Dynamics
Use the floating Studio Dock to adjust animation speed, flow frequency, wave amplitude, and physical analog film grain clustering.
Select Target Aspect Ratio
Toggle between 16:10 for MacBooks, 16:9 for 5K displays, 9:16 for iPhones, 4:3 for iPads, or 1:1 for social profile art.
Fine-Tune Color Anchors
Click individual color swatches to adjust hex values, or drag anchor position coordinates to balance warm and cool tones across the composition.
Export Ultra-HD Images, Video, or Web Code
Download up to 8K resolution PNG, JPG, or WebP images, record 60FPS looping MP4/WebM video clips, or copy ultra-optimized WebGL snippets for instant frontend use.
Interactive WebGL Shader Code
Ultra-optimized, 60FPS hardware-accelerated WebGL code ready to embed in any web application or landing page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mesh Gradient - Organic Dunes</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
#mesh-canvas {
position: fixed;
inset: 0;
width: 100%;
height: 100%;
display: block;
}
</style>
</head>
<body>
<canvas id="mesh-canvas"></canvas>
<script>
(function () {
const canvas = document.getElementById('mesh-canvas');
const gl = canvas.getContext('webgl', {
alpha: false,
antialias: false,
depth: false,
stencil: false,
preserveDrawingBuffer: true,
powerPreference: 'high-performance'
}) || canvas.getContext('experimental-webgl');
if (!gl) {
document.body.innerHTML = '<p style="color:#fff;padding:2rem;font-family:sans-serif;">WebGL is not supported.</p>';
return;
}
function compile(type, source) {
const s = gl.createShader(type);
gl.shaderSource(s, source);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(s));
gl.deleteShader(s);
return null;
}
return s;
}
const vs = compile(gl.VERTEX_SHADER, "attribute vec2 a_position;\n\tvarying vec2 v_uv;\n\tvoid main() {\n\t\tv_uv = (a_position + 1.0) * 0.5;\n\t\tgl_Position = vec4(a_position, 0.0, 1.0);\n\t}");
const fs = compile(gl.FRAGMENT_SHADER, "precision highp float;\n\tvarying vec2 v_uv;\n\n\tuniform vec2 u_resolution;\n\tuniform float u_time;\n\tuniform float u_speed;\n\tuniform float u_distortion;\n\tuniform float u_swirl;\n\tuniform float u_scale;\n\tuniform float u_rotation;\n\tuniform float u_edge;\n\tuniform float u_film;\n\tuniform int u_style;\n\tuniform int u_effect;\n\n\tuniform int u_colorCount;\n\tuniform vec3 u_colors[8];\n\n\t// ── Simplex Noise 2D ──\n\tvec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }\n\tvec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }\n\tvec3 permute(vec3 x) { return mod289(((x*34.0)+1.0)*x); }\n\n\tfloat snoise(vec2 v) {\n\t\tconst vec4 C = vec4(0.211324865405187, 0.366025403784439,\n\t\t -0.577350269189626, 0.024390243902439);\n\t\tvec2 i = floor(v + dot(v, C.yy));\n\t\tvec2 x0 = v - i + dot(i, C.xx);\n\t\tvec2 i1;\n\t\ti1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n\t\tvec4 x12 = x0.xyxy + C.xxzz;\n\t\tx12.xy -= i1;\n\t\ti = mod289(i);\n\t\tvec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))\n\t\t\t+ i.x + vec3(0.0, i1.x, 1.0 ));\n\t\tvec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);\n\t\tm = m*m;\n\t\tm = m*m;\n\t\tvec3 x = 2.0 * fract(p * C.www) - 1.0;\n\t\tvec3 h = abs(x) - 0.5;\n\t\tvec3 ox = floor(x + 0.5);\n\t\tvec3 a0 = x - ox;\n\t\tm *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );\n\t\tvec3 g;\n\t\tg.x = a0.x * x0.x + h.x * x0.y;\n\t\tg.yz = a0.yz * x12.xz + h.yz * x12.yw;\n\t\treturn 130.0 * dot(m, g);\n\t}\n\n\tfloat fbm(vec2 p) {\n\t\tfloat v = 0.0;\n\t\tfloat a = 0.5;\n\t\tfloat f = 1.0;\n\t\tfor (int i = 0; i < 4; i++) {\n\t\t\tv += a * snoise(p * f);\n\t\t\tf *= 2.0;\n\t\t\ta *= 0.5;\n\t\t}\n\t\treturn v;\n\t}\n\n\t// Constant-Indexed Color Accessor (100% WebGL 1.0 & 2.0 compliant)\n\tvec3 getColor(int idx) {\n\t\tif (idx <= 0) return u_colors[0];\n\t\tif (idx == 1) return u_colors[1];\n\t\tif (idx == 2) return u_colors[2];\n\t\tif (idx == 3) return u_colors[3];\n\t\tif (idx == 4) return u_colors[4];\n\t\tif (idx == 5) return u_colors[5];\n\t\tif (idx == 6) return u_colors[6];\n\t\treturn u_colors[7];\n\t}\n\n\t// Reusable Linear RGB Multi-Stop Hermite Spline\n\tvec3 samplePalette(float tVal) {\n\t\tfloat seg = clamp(tVal, 0.0, 0.9999) * float(u_colorCount - 1);\n\t\tfloat fIdx = floor(seg);\n\t\tint idx = int(fIdx);\n\t\tfloat t = smoothstep(0.0, 1.0, seg - fIdx);\n\t\tvec3 c0 = pow(getColor(idx), vec3(2.2));\n\t\tvec3 c1 = pow(getColor(idx + 1), vec3(2.2));\n\t\treturn pow(mix(c0, c1, t), vec3(1.0 / 2.2));\n\t}\n\n\tvoid main() {\n\t\tvec2 st = gl_FragCoord.xy / u_resolution.xy;\n\t\tfloat aspect = u_resolution.x / u_resolution.y;\n\t\tvec2 uv = st - vec2(0.5);\n\t\tuv.x *= aspect;\n\n\t\t// Coordinate Rotation\n\t\tfloat rad = u_rotation * 0.0174532925;\n\t\tmat2 rot = mat2(cos(rad), -sin(rad), sin(rad), cos(rad));\n\t\tuv = rot * uv;\n\n\t\t// Silky Smooth Swirl Vortex\n\t\tfloat r = length(uv);\n\t\tfloat angle = atan(uv.y, uv.x);\n\t\tfloat swirlStrength = (u_swirl / 100.0) * 3.2;\n\t\tangle += swirlStrength * exp(-r * 1.4);\n\t\tvec2 p = vec2(cos(angle), sin(angle)) * r * (u_scale * 1.3);\n\n\t\tfloat t = u_time * u_speed * 0.22;\n\t\tfloat field = 0.0;\n\n\t\t// ── STYLE 0: ORGANIC DUNES (Apple Mojave / Big Sur Dunes) ──\n\t\tif (u_style == 0) {\n\t\t\tfloat base = uv.x * 0.6 + uv.y * 0.4 + 0.5;\n\t\t\tvec2 q = vec2(snoise(p * 1.1 + vec2(t * 0.2, t * 0.15)), snoise(p * 1.1 + vec2(4.3, 2.1) - t * 0.18));\n\t\t\tvec2 r2 = vec2(snoise(p * 1.3 + 2.0 * q + vec2(1.7, 9.2) + t * 0.12), snoise(p * 1.3 + 2.0 * q + vec2(8.3, 2.8) - t * 0.1));\n\t\t\tfloat warp = snoise(p * 1.2 + (u_distortion / 100.0) * 2.5 * r2);\n\t\t\tfield = clamp(base + warp * 0.45 * (u_distortion / 100.0 + 0.3), 0.0, 1.0);\n\t\t}\n\n\t\t// ── STYLE 1: FLUID AURORA (Deep Swirling Northern Lights) ──\n\t\telse if (u_style == 1) {\n\t\t\tvec2 q = vec2(snoise(p * 1.3 + t * 0.3), snoise(p * 1.3 + vec2(5.2, 1.3) - t * 0.25));\n\t\t\tvec2 r2 = vec2(snoise(p * 1.5 + 2.5 * q + t * 0.18), snoise(p * 1.5 + 2.5 * q + vec2(3.1, 7.4) - t * 0.14));\n\t\t\tfloat fluid = snoise(p * 1.2 + (u_distortion / 100.0) * 2.8 * r2);\n\t\t\tfield = clamp(fluid * 0.75 + 0.5 + uv.y * 0.2, 0.0, 1.0);\n\t\t}\n\n\t\t// ── STYLE 2: SONOMA WAVES (macOS Sonoma Harmonic Waves) ──\n\t\telse if (u_style == 2) {\n\t\t\tfloat w1 = sin(p.x * 2.4 + p.y * 0.8 + t * 0.8);\n\t\t\tfloat w2 = sin(p.x * 1.4 - p.y * 1.6 - t * 0.6);\n\t\t\tfloat w3 = cos(p.x * 3.2 + t * 0.4);\n\t\t\tfloat noise = snoise(p * 1.6 + t * 0.2) * (u_distortion / 100.0) * 0.8;\n\t\t\tfield = clamp((w1 * 0.4 + w2 * 0.3 + w3 * 0.2 + noise + 1.2) * 0.42, 0.0, 1.0);\n\t\t}\n\n\t\t// ── STYLE 3: LIQUID MERCURY (Apple Pro Display Ambient) ──\n\t\telse if (u_style == 3) {\n\t\t\tvec2 q = vec2(fbm(p * 1.2 + t * 0.25), fbm(p * 1.2 + vec2(5.2, 1.3) - t * 0.2));\n\t\t\tvec2 r2 = vec2(fbm(p * 1.4 + 2.8 * q + vec2(1.7, 9.2) + t * 0.15), fbm(p * 1.4 + 2.8 * q + vec2(8.3, 2.8) - t * 0.12));\n\t\t\tfloat f = fbm(p * 1.3 + (u_distortion / 100.0) * 3.2 * r2);\n\t\t\tfield = clamp(f * 1.1 + 0.1, 0.0, 1.0);\n\t\t}\n\n\t\t// ── STYLE 4: CAUSTIC SILK (Silky Diagonal Satin Folds) ──\n\t\telse if (u_style == 4) {\n\t\t\tfloat diagonal = (uv.x + uv.y) * 1.8;\n\t\t\tfloat silk = sin(diagonal * 3.0 + snoise(p * 2.0 + t * 0.35) * (u_distortion / 100.0) * 3.5);\n\t\t\tfloat silk2 = cos(diagonal * 2.0 - snoise(p * 1.5 - t * 0.2) * (u_distortion / 100.0) * 2.0);\n\t\t\tfield = clamp((silk * 0.5 + silk2 * 0.4 + 1.0) * 0.52, 0.0, 1.0);\n\t\t}\n\n\t\t// ── STYLE 5: RADIANT BEAM (Mac Studio Light Streak) ──\n\t\telse if (u_style == 5) {\n\t\t\tfloat distToLine = abs(uv.x * 0.707 + uv.y * 0.707);\n\t\t\tfloat beam = exp(-distToLine * 2.8) * 1.2;\n\t\t\tfloat noise = snoise(p * 1.5 + t * 0.2) * (u_distortion / 100.0) * 0.35;\n\t\t\tfield = clamp(beam + noise + (uv.x - uv.y) * 0.15, 0.0, 1.0);\n\t\t}\n\n\t\t// ── STYLE 6: VELVET STRIPES ──\n\t\telse {\n\t\t\tfloat stripes = sin((uv.x + uv.y * 0.5) * 6.0 + snoise(p + t * 0.3) * (u_distortion / 100.0) * 2.0);\n\t\t\tfield = clamp((stripes + 1.0) * 0.5, 0.0, 1.0);\n\t\t}\n\n\t\t// Sample our radiant, calibrated Linear RGB palette\n\t\tvec3 color = samplePalette(field);\n\n\t\t// ── Analog Film Grain (Organic Multi-Pixel Emulsion) ──\n\t\tfloat grainStrength = u_film / 100.0;\n\t\tif (grainStrength > 0.001 || u_effect == 1) {\n\t\t\t// Group coordinates into 2x2 physical pixel crystals for tactile texture on Retina displays\n\t\t\tvec2 grainCoord = floor(gl_FragCoord.xy * 0.5);\n\t\t\tfloat grainTime = fract(u_time * 7.41);\n\t\t\tfloat n1 = fract(sin(dot(grainCoord + grainTime, vec2(12.9898, 78.233))) * 43758.5453) - 0.5;\n\t\t\tfloat n2 = fract(sin(dot(grainCoord * 1.6 - grainTime, vec2(39.346, 11.135))) * 23421.631) - 0.5;\n\t\t\tfloat grain = (n1 * 0.65 + n2 * 0.35);\n\n\t\t\t// Physical film emulsion response (more pronounced in midtones)\n\t\t\tfloat lum = dot(color, vec3(0.299, 0.587, 0.114));\n\t\t\tfloat emulsion = 1.0 - 0.5 * abs(lum - 0.5);\n\n\t\t\tfloat amount = grainStrength * 0.35 + (u_effect == 1 ? 0.09 : 0.0);\n\t\t\tcolor += grain * amount * emulsion;\n\t\t}\n\n\t\t// Posterize effect\n\t\tif (u_effect == 2) {\n\t\t\tcolor = floor(color * 6.0) / 6.0;\n\t\t}\n\n\t\t// Dither effect\n\t\tif (u_effect == 3) {\n\t\t\tfloat dither = fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233))) * 43758.5453);\n\t\t\tcolor += (dither - 0.5) * 0.06;\n\t\t}\n\n\t\tgl_FragColor = vec4(clamp(color, 0.0, 1.0), 1.0);\n\t}");
const prog = gl.createProgram();
gl.attachShader(prog, vs);
gl.attachShader(prog, fs);
gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(prog));
return;
}
gl.useProgram(prog);
// Fullscreen quad geometry
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW);
const pos = gl.getAttribLocation(prog, 'a_position');
gl.enableVertexAttribArray(pos);
gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);
// Pre-cached uniform locations
const uRes = gl.getUniformLocation(prog, 'u_resolution');
const uTime = gl.getUniformLocation(prog, 'u_time');
const uSpeed = gl.getUniformLocation(prog, 'u_speed');
const uDistortion = gl.getUniformLocation(prog, 'u_distortion');
const uSwirl = gl.getUniformLocation(prog, 'u_swirl');
const uScale = gl.getUniformLocation(prog, 'u_scale');
const uRotation = gl.getUniformLocation(prog, 'u_rotation');
const uEdge = gl.getUniformLocation(prog, 'u_edge');
const uFilm = gl.getUniformLocation(prog, 'u_film');
const uStyle = gl.getUniformLocation(prog, 'u_style');
const uEffect = gl.getUniformLocation(prog, 'u_effect');
const uColorCount = gl.getUniformLocation(prog, 'u_colorCount');
const uColors = [];
for (let i = 0; i < 8; i++) {
uColors[i] = gl.getUniformLocation(prog, 'u_colors[' + i + ']');
}
// Upload static uniforms once (zero per-frame CPU-GPU driver overhead)
gl.uniform1f(uSpeed, 0.29);
gl.uniform1f(uDistortion, 80);
gl.uniform1f(uSwirl, 74);
gl.uniform1f(uScale, 0.96);
gl.uniform1f(uRotation, 282);
gl.uniform1f(uEdge, 26);
gl.uniform1f(uFilm, 12);
gl.uniform1i(uStyle, 0);
gl.uniform1i(uEffect, 0);
gl.uniform1i(uColorCount, 5);
gl.uniform3f(uColors[0], 0.2627, 0.3608, 0.5059);
gl.uniform3f(uColors[1], 0.2667, 0.4667, 0.5098);
gl.uniform3f(uColors[2], 0.3490, 0.5961, 0.5373);
gl.uniform3f(uColors[3], 0.4902, 0.6902, 0.5412);
gl.uniform3f(uColors[4], 0.6235, 0.7961, 0.6157);
gl.uniform3f(uColors[5], 0.0, 0.0, 0.0);
gl.uniform3f(uColors[6], 0.0, 0.0, 0.0);
gl.uniform3f(uColors[7], 0.0, 0.0, 0.0);
// Responsive DPR-capped resize handler
function resize() {
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const w = Math.max(1, Math.round(window.innerWidth * dpr));
const h = Math.max(1, Math.round(window.innerHeight * dpr));
if (canvas.width !== w || canvas.height !== h) {
canvas.width = w;
canvas.height = h;
gl.viewport(0, 0, w, h);
gl.uniform2f(uRes, w, h);
}
}
window.addEventListener('resize', resize, { passive: true });
resize();
// Ultra-efficient 60FPS render loop
const startTime = performance.now();
function render(now) {
gl.uniform1f(uTime, (now - startTime) * 0.001);
gl.drawArrays(gl.TRIANGLES, 0, 6);
requestAnimationFrame(render);
}
render(startTime);
})();
</script>
</body>
</html>Mesh Gradient Studio FAQ
Everything you need to know about WebGL shaders, resolution standards, and commercial licensing.
What is a WebGL mesh gradient, and how is it calculated?
A WebGL mesh gradient is a multi-dimensional, fluid color surface computed in real time on your graphics processor (GPU) using GLSL fragment shaders. Unlike simple linear or radial CSS gradients that only interpolate between stops in one dimension, a mesh gradient blends multiple moving color anchor points using harmonic 2D/3D simplex noise algorithms. Every individual pixel on screen is evaluated dynamically at 60 frames per second, resulting in velvety, organic color blending.
Can I use the exported gradients in commercial apps and client projects?
Yes, 100%. All assets generated by Mesh Gradient Studio—including 4K/8K PNGs, JPGs, WebP files, 60FPS looping MP4/WebM videos, and WebGL code—are free for commercial and personal use with zero royalty fees or attribution requirements.
Which aspect ratio should I select for Apple MacBook and Studio Display screens?
Select 16:10 for Apple laptops, including MacBook Pro 14", MacBook Pro 16", and MacBook Air (13" & 15"). Select 16:9 for external monitors like the 27" Apple Studio Display (5K) and 32" Pro Display XDR (6K). Select 9:16 for iPhone lockscreens and mobile wallpapers, and 4:3 for iPad Pro and iPad Air.
How does the Analog Film Grain feature work?
The analog film grain simulator injects micro-crystalline noise clustered in 2x2 physical pixel blocks into the shader pipeline. This accomplishes two things: it replicates the tactile, warm aesthetic of 35mm film emulsion, and it effectively eliminates digital color banding (posterization) that can occur in smooth color transitions on 8-bit and 10-bit screens.
How do I set the downloaded wallpaper on macOS?
Once your high-resolution PNG or JPG is downloaded, open macOS System Settings > Wallpaper. Drag and drop the downloaded file directly into the wallpaper thumbnail area, or right-click the file in Finder and select "Set Desktop Picture".
How does 60FPS looping video export work directly in the browser?
Mesh Gradient Studio uses modern HTML5 Canvas captureStream and MediaRecorder APIs to stream rendered WebGL frames straight from your GPU into video encoding chunks. The result is a smooth, high-bitrate 60FPS MP4 or WebM video with seamless loop points that you can use in website hero backgrounds or live wallpaper apps.
Why do the color combinations look so harmonic without muddy gray or brown tones?
Every one of our 55+ designer palettes has been audited against color wheel harmony principles. In traditional gradients, blending complementary colors (opposites on the color wheel) creates desaturated brownish-gray "mud". Our color system enforces tight hue deltas (ΔHue ≤ 50° between adjacent anchor points) and optimizes chroma so the blending zones remain vibrant, luminous, and clean.
Can I embed the interactive 60FPS mesh gradient into my website?
Yes! Open the Export modal and select the Web Code tab, or press key C. The studio exports a standalone, ultra-optimized WebGL canvas code snippet with pre-cached uniforms and zero dependencies that runs smoothly at 60FPS on any modern browser.
Explore More Free Tools for Mac Power Users
DiscoverMacApps builds handcrafted, privacy-first web utilities designed to boost your macOS productivity and creative workflow.