Instantly share code, notes, and snippets.
Forked from destefanis/liquidGlass.metal
Created
May 4, 2025 13:57
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save TheNorthEestern/9688873da351cea0e3637d8b6ef6cdfb to your computer and use it in GitHub Desktop.
LiquidGlass.metal
This file contains hidden or 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
#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