Skip to content

Instantly share code, notes, and snippets.

@TheNorthEestern
Forked from destefanis/liquidGlass.metal
Created May 4, 2025 13:57
Show Gist options
  • Save TheNorthEestern/9688873da351cea0e3637d8b6ef6cdfb to your computer and use it in GitHub Desktop.
Save TheNorthEestern/9688873da351cea0e3637d8b6ef6cdfb to your computer and use it in GitHub Desktop.
LiquidGlass.metal
#include "Common.metal"
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct Uniforms {
float2 iResolution;
float iTime;
float patternScale;
float waveSize;
float refraction;
float edge;
float patternBlur;
float liquid;
float3 backgroundColor;
float grainIntensity;
float grainSpeed;
float grainMean;
float grainVariance;
int grainBlendMode;
float rectWidth;
float rectHeight;
float cornerRadius;
float edgeSoftness;
float speed;
};
namespace liquidglass {
// Constants
constant float PI = 3.14159265358979323846;
// Grain shader functions
float3 channel_mix(float3 a, float3 b, float3 w) {
return float3(mix(a.r, b.r, w.r), mix(a.g, b.g, w.g), mix(a.b, b.b, w.b));
}
float gaussian(float z, float u, float o) {
return (1.0 / (o * sqrt(2.0 * PI))) * exp(-(((z - u) * (z - u)) / (2.0 * (o * o))));
}
float3 madd(float3 a, float3 b, float w) {
return a + a * b * w;
}
float3 screen(float3 a, float3 b, float w) {
return mix(a, float3(1.0) - (float3(1.0) - a) * (float3(1.0) - b), w);
}
float3 overlay(float3 a, float3 b, float w) {
return mix(a, channel_mix(
2.0 * a * b,
float3(1.0) - 2.0 * (float3(1.0) - a) * (float3(1.0) - b),
step(float3(0.5), a)
), w);
}
float3 soft_light(float3 a, float3 b, float w) {
return mix(a, pow(a, pow(float3(2.0), 2.0 * (float3(0.5) - b))), w);
}
// Noise functions
float3 mod289(float3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
float2 mod289(float2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
float3 permute(float3 x) { return mod289(((x * 34.0) + 1.0) * x); }
float snoise(float2 v) {
const float4 C = float4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);
float2 i = floor(v + dot(v, C.yy));
float2 x0 = v - i + dot(i, C.xx);
float2 i1 = (x0.x > x0.y) ? float2(1.0, 0.0) : float2(0.0, 1.0);
float4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
i = mod289(i);
float3 p = permute(permute(i.y + float3(0.0, i1.y, 1.0)) + i.x + float3(0.0, i1.x, 1.0));
float3 m = max(0.5 - float3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
m = m * m;
m = m * m;
float3 x = 2.0 * fract(p * C.www) - 1.0;
float3 h = abs(x) - 0.5;
float3 ox = floor(x + 0.5);
float3 a0 = x - ox;
m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);
float3 g;
g.x = a0.x * x0.x + h.x * x0.y;
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
return 130.0 * dot(m, g);
}
float2 rotate(float2 uv, float th) {
float s = sin(th);
float c = cos(th);
float2x2 rotMat = float2x2(c, s, -s, c);
return rotMat * uv;
}
// Function to calculate distance to a rounded rectangle
float roundedRectDistance(float2 p, float2 size, float radius, float ratio) {
// Adjust for aspect ratio
p.x *= ratio;
size.x *= ratio;
// Calculate distance to the rounded rectangle
float2 q = abs(p) - size + float2(radius);
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius;
}
float3 apply_grain(float3 color, float2 uv, float time, float intensity, float speed, float mean, float variance, int blend_mode) {
float seed = dot(uv, float2(12.9898, 78.233));
float noise = fract(sin(seed) * 43758.5453 + time * speed);
noise = gaussian(noise, mean, variance * variance);
float3 grain = float3(noise) * (1.0 - color);
float w = intensity;
if (blend_mode == 0) {
// Addition
return color + grain * w;
} else if (blend_mode == 1) {
// Screen
return screen(color, grain, w);
} else if (blend_mode == 2) {
// Overlay
return overlay(color, grain, w);
} else if (blend_mode == 3) {
// Soft Light
return soft_light(color, grain, w);
} else if (blend_mode == 4) {
// Lighten-Only
return max(color, grain * w);
}
return color;
}
float get_color_channel(float c1, float c2, float stripe_p, float3 w, float extra_blur, float b, float patternBlur) {
float ch = c2;
float border = 0.0;
float blur = patternBlur + extra_blur;
ch = mix(ch, c1, smoothstep(0.0, blur, stripe_p));
border = w[0];
ch = mix(ch, c2, smoothstep(border - blur, border + blur, stripe_p));
b = smoothstep(0.2, 0.8, b);
border = w[0] + 0.4 * (1.0 - b) * w[1];
ch = mix(ch, c1, smoothstep(border - blur, border + blur, stripe_p));
border = w[0] + 0.5 * (1.0 - b) * w[1];
ch = mix(ch, c2, smoothstep(border - blur, border + blur, stripe_p));
border = w[0] + w[1];
ch = mix(ch, c1, smoothstep(border - blur, border + blur, stripe_p));
float gradient_t = (stripe_p - w[0] - w[1]) / w[2];
float gradient = mix(c1, c2, smoothstep(0.0, 1.0, gradient_t));
ch = mix(ch, gradient, smoothstep(border - blur, border + blur, stripe_p));
return ch;
}
// Add GLSL-style mod function
float mod_glsl(float x, float y) {
return x - y * floor(x/y);
}
float2 mod_glsl(float2 x, float2 y) {
return x - y * floor(x/y);
}
float3 mod_glsl(float3 x, float3 y) {
return x - y * floor(x/y);
}
vertex VertexOut vertex_main(VertexIn in [[stage_in]],
constant Uniforms &uniforms [[buffer(1)]]) {
VertexOut out;
out.position = float4(in.position, 1.0);
out.uv = in.uv;
return out;
}
fragment float4 fragment_main(VertexOut in [[stage_in]],
constant Uniforms &uniforms [[buffer(0)]]) {
float2 uv = in.uv;
// Calculate aspect ratio
float ratio = uniforms.iResolution.x / uniforms.iResolution.y;
// Scale the UVs to control the size of the waves without changing their frequency
float2 scaledUV = uv / uniforms.waveSize;
// Adjust UVs to maintain aspect ratio
float2 centeredUV = scaledUV;
centeredUV.x *= ratio;
// Apply liquid effect to the coordinates
float2 distortedUV = centeredUV;
// Flip Y to match original shader
distortedUV.y = 1.0 - distortedUV.y;
// Calculate rounded rectangle mask
float2 rectUV = uv - float2(0.5, 0.5); // Center the rectangle
float2 halfSize = float2(uniforms.rectWidth, uniforms.rectHeight) * 0.5; // Half size of the rectangle
float rectDist = roundedRectDistance(rectUV, halfSize, uniforms.cornerRadius, ratio);
float rectMask = 1.0 - smoothstep(-uniforms.edgeSoftness, 0.0, rectDist);
// Modified diagonal calculation to extend the effect to the edges
// Scale the diagonal to ensure it covers the entire UV space
float diagonal = (distortedUV.x - distortedUV.y) * 0.5;
// Simplified time usage - match WebGL exactly
float t = uniforms.iTime;
// Set edge value from parameters
float edge = uniforms.edge;
float3 color = float3(0.0);
float opacity = 1.0;
float3 color1 = float3(0.98, 0.98, 1.0);
float3 color2 = float3(0.1, 0.1, 0.1 + 0.1 * smoothstep(0.7, 1.3, distortedUV.x + distortedUV.y));
float2 grad_uv = distortedUV;
// Extend the gradient effect by scaling the offset
grad_uv -= 0.5;
// Modify the distance calculation to extend the effect
float dist = length(grad_uv + float2(0.0, 0.2 * diagonal));
// Adjust rotation to ensure the effect covers the entire space
grad_uv = rotate(grad_uv, (0.25 - 0.2 * diagonal) * PI);
// Modify bulge calculation to ensure it extends to the edges
float bulge = pow(1.5 * dist, 1.0); // Reduced power to extend effect
bulge = 1.0 - bulge;
bulge *= pow(max(0.1, distortedUV.y), 0.2); // Reduced power to extend effect
// Apply rounded rectangle mask to the bulge
bulge *= rectMask;
// Enhance pattern definition for sharper rendering
float cycle_width = uniforms.patternScale;
float thin_strip_1_ratio = 0.12 / cycle_width * (1.0 - 0.4 * bulge);
float thin_strip_2_ratio = 0.07 / cycle_width * (1.0 + 0.4 * bulge);
float wide_strip_ratio = (1.0 - thin_strip_1_ratio - thin_strip_2_ratio);
// Adjust strip widths for sharper edges
float thin_strip_1_width = max(0.001, cycle_width * thin_strip_1_ratio);
float thin_strip_2_width = max(0.001, cycle_width * thin_strip_2_ratio);
// Set opacity to full to ensure the effect covers the entire UV space
opacity = 1.0;
// Match WebGL exactly
float noise = snoise((distortedUV - t) * 0.9);
// Apply rounded rectangle mask to noise
noise *= rectMask;
edge += (1.0 - edge) * uniforms.liquid * noise;
float refr = 0.0;
refr += (1.0 - bulge);
refr = clamp(refr, 0.0, 1.0);
float dir = grad_uv.x;
// Increase diagonal influence to extend the effect
dir += diagonal * 0.4;
dir -= 2.0 * noise * diagonal * (smoothstep(0.0, 1.0, edge) * smoothstep(1.0, 0.0, edge));
// Modify bulge influence to ensure it extends to the edges
bulge = max(bulge, 0.2 * rectMask); // Ensure minimum bulge value with rectangle mask
dir *= (0.1 + (1.1 - edge) * bulge);
dir *= smoothstep(1.0, 0.7, edge);
dir += 0.18 * (smoothstep(0.1, 0.2, distortedUV.y) * smoothstep(0.4, 0.2, distortedUV.y));
dir += 0.03 * (smoothstep(0.1, 0.2, 1.0 - distortedUV.y) * smoothstep(0.4, 0.2, 1.0 - distortedUV.y));
dir *= (0.5 + 0.5 * pow(distortedUV.y, 2.0));
// Match WebGL exactly - order matters here
dir *= cycle_width;
dir -= t;
// Safety clamp to avoid potential artifacts
dir = clamp(dir, -1000.0, 1000.0);
float refr_r = refr;
refr_r += 0.03 * bulge * noise;
float refr_b = 1.3 * refr;
refr_r += 5.0 * (smoothstep(-0.1, 0.2, distortedUV.y) * smoothstep(0.5, 0.1, distortedUV.y)) *
(smoothstep(0.4, 0.6, bulge) * smoothstep(1.0, 0.4, bulge));
// Reduce diagonal influence on refraction to extend the effect
refr_r -= diagonal * 0.3;
refr_b += (smoothstep(0.0, 0.4, distortedUV.y) * smoothstep(0.8, 0.1, distortedUV.y)) *
(smoothstep(0.4, 0.6, bulge) * smoothstep(0.8, 0.4, bulge));
refr_b -= 0.2 * edge;
refr_r *= uniforms.refraction * 1.2;
refr_b *= uniforms.refraction * 1.2;
float3 w = float3(thin_strip_1_width, thin_strip_2_width, wide_strip_ratio);
w[1] -= 0.02 * smoothstep(0.0, 1.0, edge + bulge);
// Use GLSL mod instead of fmod
float stripe_r = mod_glsl(dir + refr_r, 1.0);
float r = get_color_channel(color1.r, color2.r, stripe_r, w, 0.02 + 0.03 * uniforms.refraction * bulge, bulge, uniforms.patternBlur);
float stripe_g = mod_glsl(dir, 1.0);
float g = get_color_channel(color1.g, color2.g, stripe_g, w, max(0.001, 0.01 / (1.001 - diagonal * 0.3)), bulge, uniforms.patternBlur);
float stripe_b = mod_glsl(dir - refr_b, 1.0);
float b = get_color_channel(color1.b, color2.b, stripe_b, w, 0.01, bulge, uniforms.patternBlur);
color = float3(r, g, b);
// Apply rounded rectangle mask to final color
color = mix(color2, color, rectMask);
color *= opacity;
// Apply grain effect to the final color
color = apply_grain(color, in.uv, t, uniforms.grainIntensity, uniforms.grainSpeed,
uniforms.grainMean, uniforms.grainVariance, uniforms.grainBlendMode);
return float4(color, 1.0);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment