Update README and code to set default sparkle shape to 'none' and add opacity control

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-04-15 11:24:32 +02:00
parent 0fec97163d
commit c7382c11a5
7 changed files with 257 additions and 80 deletions
+24 -35
View File
@@ -15,10 +15,11 @@ uniform float uSparkleShapeKind;
uniform float uSparklePrimary;
uniform float uSparkleSecondary;
uniform float uSparkleTertiary;
uniform float uOpacity;
#define TWO_PI 6.28318530718
// Precomputed rotation matrices to avoid expensive runtime sin/cos calculations
// Precomputed rotation matrices reduce repeated sin/cos work in tiled layers.
#define ROT_0_78 mat2( 0.71091, 0.70328, -0.70328, 0.71091)
#define ROT_M0_5 mat2( 0.87758, -0.47943, 0.47943, 0.87758)
#define ROT_1_2 mat2( 0.36236, 0.93204, -0.93204, 0.36236)
@@ -65,7 +66,7 @@ float sdRegularPolygon(vec2 p, float sides, float radius) {
float sparkleStarMask(vec2 p, float points, float innerRatio) {
float angle = atan(p.y, p.x);
float radius = dot(p, p); // Optimized: Use squared distance to delay sqrt
float radius = dot(p, p);
float spikes = 0.5 + 0.5 * cos(angle * points);
float starRadius = mix(0.34 * innerRatio, 0.34, pow(spikes, 1.4));
return smoothstep(0.0009, -0.0009, radius - (starRadius * starRadius));
@@ -100,15 +101,18 @@ float sparklePolygonMask(vec2 p, float sides, float aspectRatio, float rotation)
float sparkleShapeMask(vec2 p, vec2 id) {
float randomRotation = hash(id + 5.1) * TWO_PI;
if (uSparkleShapeKind < 0.5) {
return sparkleStarMask(p, uSparklePrimary, uSparkleSecondary);
return 0.0;
}
if (uSparkleShapeKind < 1.5) {
return sparkleRectangleMask(p, uSparklePrimary, uSparkleSecondary, randomRotation * uSparkleTertiary);
return sparkleStarMask(p, uSparklePrimary, uSparkleSecondary);
}
if (uSparkleShapeKind < 2.5) {
return sparklePolygonMask(p, uSparklePrimary, uSparkleSecondary, uSparkleTertiary);
return sparkleRectangleMask(p, uSparklePrimary, uSparkleSecondary, randomRotation * uSparkleTertiary);
}
if (uSparkleShapeKind < 3.5) {
return sparklePolygonMask(p, uSparklePrimary, uSparkleSecondary, uSparkleTertiary);
}
if (uSparkleShapeKind < 4.5) {
float sides = 5.0 + floor(hash(id + 9.4) * 4.0);
float aspect = 0.7 + hash(id + 13.1) * 0.7;
return sparklePolygonMask(p, sides, aspect, randomRotation);
@@ -198,11 +202,11 @@ vec3 styleCrackedIce(vec2 uv, vec2 tilt, float time) {
}
vec3 styleSilverMosaic(vec2 uv, vec2 tilt, float time) {
// Setup grid - Scaled up 300% (18.0 -> 6.0)
// Mosaic tile grid.
vec2 g = uv * 6.0;
vec2 id = floor(g);
// Center coordinates around 0.0 for the radial math
// Center local tile coordinates for radial optics.
vec2 f = fract(g) - 0.5;
// Real foil sheets often alternate the grain of the squares to catch light from all angles.
@@ -210,50 +214,38 @@ vec3 styleSilverMosaic(vec2 uv, vec2 tilt, float time) {
f = vec2(-f.y, f.x);
}
// Get the radial vector (pointing outward from the center of the square)
vec2 radialDir = normalize(f + vec2(0.0001));
// Calculate the simulated light direction based on device tilt.
vec2 lightDir = normalize(tilt + vec2(0.001));
// --- THE OPTICS ---
// A radial highlight occurs where the radial vector aligns with the light vector.
// Radial highlight where tile direction aligns with light direction.
float alignment = abs(dot(radialDir, lightDir));
// Raise to a high power to narrow the reflection into a sharp, focused beam
float highlight = pow(alignment, 12.0);
// --- THE DIFFRACTION (Rainbow) ---
float angleDiff = acos(alignment);
// 2D cross product to find which side of the highlight we are on
// 2D cross product sign decides which side of the highlight receives hue shift.
float side = sign(radialDir.x * lightDir.y - radialDir.y * lightDir.x);
// Phase shift based on tilt magnitude and time.
float basePhase = length(tilt) * 2.5 + time * 0.1;
// Calculate the final color phase.
float phase = basePhase + side * angleDiff * 1.8;
vec3 holoColor = rainbow(phase, 0.85, 1.0);
// Add a pure white "specular core" where alignment is absolutely perfect
// White specular core appears when alignment is nearly perfect.
vec3 core = vec3(1.0) * smoothstep(0.98, 1.0, alignment);
// Base foil material
vec3 foilBase = vec3(0.25, 0.27, 0.30);
// Combine the light components
vec3 finalColor = foilBase + (holoColor * highlight * 1.5) + (core * 0.8);
// --- BORDERS AND DEPTH ---
// Crisp borders between the squares
// Tile borders and edge darkening add perceived depth.
float edgeX = 0.5 - abs(f.x);
float edgeY = 0.5 - abs(f.y);
// Tightened the border width to compensate for the larger tile size
float border = smoothstep(0.0, 0.015, min(edgeX, edgeY));
// Add a subtle darkening toward the edges of the tiles
float dist = length(f);
finalColor *= mix(0.7, 1.0, 1.0 - smoothstep(0.0, 0.5, dist) * 0.3);
@@ -265,9 +257,7 @@ vec3 styleSuperGoldVinyl(vec2 uv, vec2 tilt, float time) {
vec2 staggered = vec2(g.x + 0.5 * mod(floor(g.y), 2.0), g.y);
vec2 f = fract(staggered) - 0.5;
// Optimized: Use dot product for squared distance to avoid expensive sqrt/length.
// Original smoothstep values (0.42, 0.10) squared become (0.1764, 0.0100)
// Original smoothstep values (0.45, 0.18) squared become (0.2025, 0.0324)
// Distance-squared masks avoid square roots and preserve circular dots.
float d2 = dot(f, f);
float dotMask = smoothstep(0.1764, 0.01, d2);
float emboss = smoothstep(0.2025, 0.0324, d2);
@@ -281,13 +271,12 @@ vec3 styleSuperGoldVinyl(vec2 uv, vec2 tilt, float time) {
}
void main() {
// Standard normalized UVs [0.0 to 1.0] for macro lighting, tilt, and glare
// UVs in [0, 1] for global lighting and center-based effects.
vec2 uv = FlutterFragCoord().xy / uSize;
vec2 center = vec2(0.5, 0.5);
vec2 fromCenter = uv - center;
// Aspect-corrected UVs for physical patterns (grid, sparkles, mosaics)
// Dividing by the maximum dimension ensures squares remain squares.
// Aspect-corrected UVs keep grids and sparkles physically proportional.
float maxDim = max(uSize.x, uSize.y);
vec2 aspectUV = FlutterFragCoord().xy / maxDim;
@@ -295,7 +284,7 @@ void main() {
float safeTiltMag = max(tiltMag, 0.001);
vec2 tiltDir = uTilt / safeTiltMag;
// The highlight uses the stretched UV so the glare covers the whole widget nicely
// Highlight uses stretched UVs so glare spans the full widget area.
float highlightDistance = length(uv - (center + (uTilt * 0.35)));
float specularMask = pow(max(0.0, 1.0 - highlightDistance * 2.6), 3.2);
vec3 specular = vec3(specularMask) * uSpecular * (0.4 + 0.6 * tiltMag);
@@ -303,7 +292,7 @@ void main() {
bool isCrackedIce = uStyle >= 0.5 && uStyle < 1.5;
float sparkleGridScale = isCrackedIce ? 7.0 : 18.0;
// ---> USE aspectUV HERE so sparkles are drawn perfectly proportionally
// Sparkle grid uses aspect-corrected coordinates to avoid stretching.
vec2 sparkleGrid = aspectUV * sparkleGridScale;
vec2 grid = floor(sparkleGrid);
@@ -339,7 +328,7 @@ void main() {
}
vec3 styleBase;
// ---> USE aspectUV HERE so the foil patterns tile properly
// Style bases sample aspect-corrected UVs for stable pattern geometry.
if (uStyle < 0.5) {
styleBase = styleHolographicSilver(aspectUV, uTilt, uTime);
} else if (uStyle < 1.5) {
@@ -353,7 +342,7 @@ void main() {
float styleLuma = dot(styleBase, vec3(0.299, 0.587, 0.114));
vec3 chromaAdjusted = mix(vec3(styleLuma), styleBase, 0.2 + 0.8 * uPrismatic);
// General 3D lighting normal uses the stretched UV
// Directional lighting uses center-relative UVs for broad foil gradients.
vec2 normal2d = normalize(fromCenter + vec2(0.001));
float directional = 0.5 + 0.5 * dot(normal2d, tiltDir);
float tiltLighting = mix(0.86, 1.20, pow(directional, 1.2));
@@ -361,7 +350,7 @@ void main() {
float microStrength = isCrackedIce ? 0.08 : 0.18;
// ---> USE aspectUV HERE so the microscopic diffraction doesn't stretch
// Micro diffraction uses aspect-corrected UVs to keep grain isotropic.
float microMask = 0.5 + 0.5 * sin(dot(aspectUV, vec2(137.0, 57.0)) + noise(aspectUV * 14.0) * 4.0 + dot(uTilt, vec2(9.0, 4.0)) * 2.0);
vec3 microShimmer = rainbow(noise(aspectUV * 10.0) + dot(uTilt, vec2(0.4, -0.3)) * 0.3, 0.65, 1.0) * microMask * uDiffraction * microStrength;
@@ -371,7 +360,7 @@ void main() {
vec3 vibrantColor = mix(vec3(luma), mappedColor, 1.4);
float foilBrightness = dot(vibrantColor, vec3(0.333));
float alpha = clamp(foilBrightness * 0.5, 0.0, 0.35);
float alpha = clamp(foilBrightness * 0.5, 0.0, 0.35) * clamp(uOpacity, 0.0, 1.0);
vec3 finalColor = clamp(vibrantColor, 0.0, 1.0);
fragColor = vec4(finalColor * alpha, alpha);