diff --git a/docs/registry.json b/docs/registry.json
index f984641d..0dc24449 100644
--- a/docs/registry.json
+++ b/docs/registry.json
@@ -275,6 +275,19 @@
"type": "registry:component"
}
]
+ },
+ {
+ "name": "blobs-grid",
+ "type": "registry:component",
+ "title": "Blobs Grid Example",
+ "description": "Blobs Grid shader example.",
+ "dependencies": ["@paper-design/shaders-react"],
+ "files": [
+ {
+ "path": "registry/blobs-grid-example.tsx",
+ "type": "registry:component"
+ }
+ ]
}
]
}
diff --git a/docs/registry/blobs-grid-example.tsx b/docs/registry/blobs-grid-example.tsx
new file mode 100644
index 00000000..87e9f9ba
--- /dev/null
+++ b/docs/registry/blobs-grid-example.tsx
@@ -0,0 +1,5 @@
+import { BlobsGrid, type BlobsGridProps } from '@paper-design/shaders-react';
+
+export function BlobsGridExample(props: BlobsGridProps) {
+ return ;
+}
diff --git a/docs/src/app/blobs-grid/layout.tsx b/docs/src/app/blobs-grid/layout.tsx
new file mode 100644
index 00000000..9f44d330
--- /dev/null
+++ b/docs/src/app/blobs-grid/layout.tsx
@@ -0,0 +1,9 @@
+import { Metadata } from 'next';
+
+export const metadata: Metadata = {
+ title: 'Blobs Grid Shader | Paper',
+};
+
+export default function Layout({ children }: { children: React.ReactNode }) {
+ return <>{children}>;
+}
diff --git a/docs/src/app/blobs-grid/page.tsx b/docs/src/app/blobs-grid/page.tsx
new file mode 100644
index 00000000..3bafc2e6
--- /dev/null
+++ b/docs/src/app/blobs-grid/page.tsx
@@ -0,0 +1,112 @@
+'use client';
+
+import { BlobsGrid, type BlobsGridParams, blobsGridPresets } from '@paper-design/shaders-react';
+import { useControls, button, folder } from 'leva';
+import { setParamsSafe, useResetLevaParams } from '@/helpers/use-reset-leva-params';
+import { usePresetHighlight } from '@/helpers/use-preset-highlight';
+import Link from 'next/link';
+import { BackButton } from '@/components/back-button';
+import { cleanUpLevaParams } from '@/helpers/clean-up-leva-params';
+import { ShaderFit, ShaderFitOptions, blobsGridMeta } from '@paper-design/shaders';
+import { useColors } from '@/helpers/use-colors';
+import { toHsla } from '@/helpers/to-hsla';
+
+/**
+ * You can copy/paste this example to use BlobsGrid in your app
+ */
+const BlobsGridExample = () => {
+ return ;
+};
+
+/**
+ * This example has controls added so you can play with settings in the example app
+ */
+
+const { worldWidth, worldHeight, ...defaults } = blobsGridPresets[0].params;
+
+const BlobsGridWithControls = () => {
+ const { colors, setColors } = useColors({
+ defaultColors: defaults.colors,
+ maxColorCount: blobsGridMeta.maxColorCount,
+ });
+
+ const [params, setParams] = useControls(() => {
+ return {
+ Parameters: folder(
+ {
+ stepsPerColor: { value: defaults.stepsPerColor, min: 1, max: 3, step: 1, order: 0 },
+ colorBack: { value: toHsla(defaults.colorBack), order: 100 },
+ colorShade: { value: toHsla(defaults.colorShade), order: 101 },
+ colorSpecular: { value: toHsla(defaults.colorSpecular), order: 101 },
+ colorInnerShadow: { value: toHsla(defaults.colorInnerShadow), order: 102 },
+ distortion: { value: defaults.distortion, min: 0, max: 20, order: 300 },
+ size: { value: defaults.size, min: 0, max: 1, order: 301 },
+ specular: { value: defaults.specular, min: 0, max: 1, order: 302 },
+ specularNormal: { value: defaults.specularNormal, min: 0, max: 1, order: 303 },
+ shade: { value: defaults.shade, min: 0, max: 1, order: 304 },
+ innerShadow: { value: defaults.innerShadow, min: 0, max: 1, order: 305 },
+ speed: { value: defaults.speed, min: 0, max: 2, order: 400 },
+ },
+ { order: 1 }
+ ),
+ Transform: folder(
+ {
+ scale: { value: defaults.scale, min: 0.01, max: 4, order: 400 },
+ rotation: { value: defaults.rotation, min: 0, max: 360, order: 401 },
+ offsetX: { value: defaults.offsetX, min: -1, max: 1, order: 402 },
+ offsetY: { value: defaults.offsetY, min: -1, max: 1, order: 403 },
+ },
+ {
+ order: 2,
+ collapsed: false,
+ }
+ ),
+ Fit: folder(
+ {
+ fit: { value: defaults.fit, options: Object.keys(ShaderFitOptions) as ShaderFit[], order: 404 },
+ worldWidth: { value: 1000, min: 1, max: 5120, order: 405 },
+ worldHeight: { value: 500, min: 1, max: 5120, order: 406 },
+ originX: { value: defaults.originX, min: 0, max: 1, order: 407 },
+ originY: { value: defaults.originY, min: 0, max: 1, order: 408 },
+ },
+ {
+ order: 3,
+ collapsed: true,
+ }
+ ),
+ };
+ }, [colors.length]);
+
+ useControls(() => {
+ const presets = Object.fromEntries(
+ blobsGridPresets.map(({ name, params: { worldWidth, worldHeight, ...preset } }) => [
+ name,
+ button(() => {
+ const { colors, ...presetParams } = preset;
+ setColors(colors);
+ setParamsSafe(params, setParams, presetParams);
+ }),
+ ])
+ );
+ return {
+ Presets: folder(presets, { order: -1 }),
+ };
+ });
+
+ // Reset to defaults on mount, so that Leva doesn't show values from other
+ // shaders when navigating (if two shaders have a color1 param for example)
+ useResetLevaParams(params, setParams, defaults);
+ usePresetHighlight(blobsGridPresets, params);
+ cleanUpLevaParams(params);
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
+
+export default BlobsGridWithControls;
diff --git a/docs/src/home-shaders.ts b/docs/src/home-shaders.ts
index 895a1ab1..fb6a0477 100644
--- a/docs/src/home-shaders.ts
+++ b/docs/src/home-shaders.ts
@@ -63,6 +63,8 @@ import {
staticMeshGradientPresets,
StaticRadialGradient,
staticRadialGradientPresets,
+ BlobsGrid,
+ blobsGridPresets,
} from '@paper-design/shaders-react';
import { StaticImageData } from 'next/image';
import TextureTest from './app/texture-test/page';
@@ -84,150 +86,148 @@ export const homeShaders = [
// shaderConfig: {},
// },
{
- name: 'grain gradient',
- url: '/grain-gradient',
- ShaderComponent: GrainGradient,
- image: grainGradientImg,
- shaderConfig: { ...grainGradientPresets[0].params, frame: 7000, speed: 2 },
+ name: 'simplex noise',
+ image: simplexNoiseImg,
+ url: '/simplex-noise',
+ ShaderComponent: SimplexNoise,
+ shaderConfig: { ...simplexNoisePresets[0].params, scale: 0.35 },
},
{
name: 'mesh gradient',
image: meshGradientImg,
url: '/mesh-gradient',
ShaderComponent: MeshGradient,
- shaderConfig: { ...meshGradientPresets[0].params, frame: 41500 },
+ shaderConfig: { ...meshGradientPresets[0].params },
},
{
- name: 'static mesh gradient',
- url: '/static-mesh-gradient',
- ShaderComponent: StaticMeshGradient,
- image: staticMeshGradientImg,
- shaderConfig: { ...staticMeshGradientPresets[0].params, rotation: 270 },
- },
- {
- name: 'static radial gradient',
- url: '/static-radial-gradient',
- ShaderComponent: StaticRadialGradient,
- image: staticRadialGradientImg,
- shaderConfig: { ...staticRadialGradientPresets[0].params, scale: 0.7 },
- },
- {
- name: 'dithering',
- url: '/dithering',
- ShaderComponent: Dithering,
- image: ditheringImg,
- shaderConfig: { ...ditheringPresets[0].params, scale: 0.45 },
+ name: 'neuro noise',
+ image: neuroNoiseImg,
+ url: '/neuro-noise',
+ ShaderComponent: NeuroNoise,
+ shaderConfig: { ...neuroNoisePresets[0].params },
},
{
name: 'dot orbit',
image: dotOrbitImg,
url: '/dot-orbit',
ShaderComponent: DotOrbit,
- shaderConfig: { ...dotOrbitPresets[0].params, scale: 0.45 },
+ shaderConfig: { ...dotOrbitPresets[0].params, scale: 0.35 },
+ },
+ {
+ name: 'smoke ring',
+ image: smokeRingImg,
+ url: '/smoke-ring',
+ ShaderComponent: SmokeRing,
+ shaderConfig: { ...smokeRingPresets[3].params },
+ },
+ {
+ name: 'metaballs',
+ image: metaballsImg,
+ url: '/metaballs',
+ ShaderComponent: Metaballs,
+ shaderConfig: { ...metaballsPresets[0].params },
},
{
name: 'dot grid',
url: '/dot-grid',
ShaderComponent: DotGrid,
image: dotGridImg,
- shaderConfig: { ...dotGridPresets[0].params, scale: 1.1, size: 2 },
+ shaderConfig: { ...dotGridPresets[0].params, scale: 0.4 },
+ },
+ {
+ name: 'perlin',
+ url: '/perlin-noise',
+ ShaderComponent: PerlinNoise,
+ image: perlinNoiseImg,
+ shaderConfig: { ...perlinNoisePresets[1].params, scale: 0.8 },
+ },
+ {
+ name: 'voronoi',
+ url: '/voronoi',
+ ShaderComponent: Voronoi,
+ image: voronoiImg,
+ shaderConfig: { ...voronoiPresets[0].params, scale: 0.5 },
+ },
+ {
+ name: 'waves',
+ url: '/waves',
+ ShaderComponent: Waves,
+ image: wavesImg,
+ shaderConfig: { ...wavesPresets[0].params, shape: 1 },
},
{
name: 'warp',
url: '/warp',
ShaderComponent: Warp,
image: warpImg,
- shaderConfig: { ...warpPresets[0].params, speed: 2, offsetX: -0.2, scale: 0.6, frame: 20000 },
+ shaderConfig: { ...warpPresets[2].params, scale: 0.25 },
+ },
+ {
+ name: 'god rays',
+ url: '/god-rays',
+ ShaderComponent: GodRays,
+ image: godRaysImg,
+ shaderConfig: { ...godRaysPresets[0].params, offsetX: -1.1, midSize: 1 },
},
{
name: 'spiral',
url: '/spiral',
ShaderComponent: Spiral,
image: spiralImg,
- shaderConfig: { ...spiralPresets[0].params, scale: 0.5, speed: 2 },
+ shaderConfig: { ...spiralPresets[1].params },
},
{
name: 'swirl',
url: '/swirl',
ShaderComponent: Swirl,
- image: swirlImg,
- shaderConfig: { ...swirlPresets[0].params, speed: 0.7 },
- },
- {
- name: 'waves',
- url: '/waves',
- ShaderComponent: Waves,
- image: wavesImg,
- shaderConfig: { ...wavesPresets[0].params, scale: 0.9 },
+ shaderConfig: { ...swirlPresets[0].params },
},
{
- name: 'neuro noise',
- image: neuroNoiseImg,
- url: '/neuro-noise',
- ShaderComponent: NeuroNoise,
- shaderConfig: { ...neuroNoisePresets[0].params, scale: 0.6, frame: 1500, offsetX: -0.17 },
- },
- {
- name: 'perlin',
- url: '/perlin-noise',
- ShaderComponent: PerlinNoise,
- image: perlinNoiseImg,
- shaderConfig: { ...perlinNoisePresets[0].params, scale: 0.8, speed: 0.2 },
+ name: 'dithering',
+ url: '/dithering',
+ ShaderComponent: Dithering,
+ shaderConfig: { ...ditheringPresets[0].params },
},
{
- name: 'simplex noise',
- image: simplexNoiseImg,
- url: '/simplex-noise',
- ShaderComponent: SimplexNoise,
- shaderConfig: { ...simplexNoisePresets[0].params, scale: 0.4 },
+ name: 'liquid metal',
+ url: '/liquid-metal',
+ ShaderComponent: LiquidMetal,
+ shaderConfig: { ...liquidMetalPresets[0].params },
},
{
- name: 'voronoi',
- url: '/voronoi',
- ShaderComponent: Voronoi,
- image: voronoiImg,
- shaderConfig: { ...voronoiPresets[0].params, scale: 0.35 },
+ name: 'grain gradient',
+ url: '/grain-gradient',
+ ShaderComponent: GrainGradient,
+ shaderConfig: { ...grainGradientPresets[0].params },
},
{
name: 'pulsing border',
url: '/pulsing-border',
ShaderComponent: PulsingBorder,
- image: pulsingBorderImg,
- shaderConfig: { ...pulsingBorderPresets[0].params, scale: 0.45 },
- },
- {
- name: 'metaballs',
- image: metaballsImg,
- url: '/metaballs',
- ShaderComponent: Metaballs,
- shaderConfig: { ...metaballsPresets[0].params, scale: 0.7, frame: 21300, offsetY: -0.01 },
+ shaderConfig: { ...pulsingBorderPresets[0].params },
},
{
name: 'color panels',
url: '/color-panels',
ShaderComponent: ColorPanels,
- image: colorPanelsImg,
- shaderConfig: { ...colorPanelsPresets[0].params, speed: 2, scale: 0.6 },
+ shaderConfig: { ...colorPanelsPresets[0].params },
},
{
- name: 'smoke ring',
- image: smokeRingImg,
- url: '/smoke-ring',
- ShaderComponent: SmokeRing,
- shaderConfig: { ...smokeRingPresets[0].params, scale: 0.6, speed: 2 },
+ name: 'static mesh gradient',
+ url: '/static-mesh-gradient',
+ ShaderComponent: StaticMeshGradient,
+ shaderConfig: { ...staticMeshGradientPresets[0].params },
},
{
- name: 'liquid metal',
- url: '/liquid-metal',
- ShaderComponent: LiquidMetal,
- image: liquidMetalImg,
- shaderConfig: { ...liquidMetalPresets[0].params, scale: 0.45 },
+ name: 'static radial gradient',
+ url: '/static-radial-gradient',
+ ShaderComponent: StaticRadialGradient,
+ shaderConfig: { ...staticRadialGradientPresets[0].params },
},
{
- name: 'god rays',
- url: '/god-rays',
- ShaderComponent: GodRays,
- image: godRaysImg,
- shaderConfig: { ...godRaysPresets[0].params, speed: 2, scale: 0.5, offsetY: -0.5 },
+ name: 'blobs grid',
+ url: '/blobs-grid',
+ ShaderComponent: BlobsGrid,
+ shaderConfig: { ...blobsGridPresets[0].params },
},
] satisfies HomeShaderConfig[];
diff --git a/packages/shaders-react/src/index.ts b/packages/shaders-react/src/index.ts
index 9b4d7130..2895fc28 100644
--- a/packages/shaders-react/src/index.ts
+++ b/packages/shaders-react/src/index.ts
@@ -87,6 +87,10 @@ export { StaticRadialGradient, staticRadialGradientPresets } from './shaders/sta
export type { StaticRadialGradientProps } from './shaders/static-radial-gradient.js';
export type { StaticRadialGradientUniforms, StaticRadialGradientParams } from '@paper-design/shaders';
+export { BlobsGrid, blobsGridPresets } from './shaders/blobs-grid.js';
+export type { BlobsGridProps } from './shaders/blobs-grid.js';
+export type { BlobsGridUniforms, BlobsGridParams } from '@paper-design/shaders';
+
export { isPaperShaderElement, getShaderColorFromString } from '@paper-design/shaders';
export type { PaperShaderElement, ShaderFit, ShaderSizingParams, ShaderSizingUniforms } from '@paper-design/shaders';
diff --git a/packages/shaders-react/src/shaders/blobs-grid.tsx b/packages/shaders-react/src/shaders/blobs-grid.tsx
new file mode 100644
index 00000000..f5dd0e6a
--- /dev/null
+++ b/packages/shaders-react/src/shaders/blobs-grid.tsx
@@ -0,0 +1,170 @@
+import { memo } from 'react';
+import { ShaderMount, type ShaderComponentProps } from '../shader-mount.js';
+import { colorPropsAreEqual } from '../color-props-are-equal.js';
+import {
+ defaultPatternSizing,
+ getShaderColorFromString,
+ blobsGridFragmentShader,
+ ShaderFitOptions,
+ type BlobsGridParams,
+ type BlobsGridUniforms,
+ type ShaderPreset,
+} from '@paper-design/shaders';
+
+export interface BlobsGridProps extends ShaderComponentProps, BlobsGridParams {}
+
+type BlobsGridPreset = ShaderPreset;
+
+export const defaultPreset: BlobsGridPreset = {
+ name: 'Default',
+ params: {
+ ...defaultPatternSizing,
+ scale: 0.45,
+ speed: 0.5,
+ frame: 0,
+ colors: ['#e4ab1b', '#33b1ff', '#db579b'],
+ stepsPerColor: 2,
+ colorBack: '#000000',
+ colorShade: '#ffffff',
+ colorSpecular: '#ffffffa1',
+ colorInnerShadow: '#000000',
+ distortion: 8.6,
+ shade: 0.0,
+ specular: 0,
+ specularNormal: 0,
+ size: 0.49,
+ innerShadow: 0,
+ },
+};
+
+export const dropsPreset: BlobsGridPreset = {
+ name: 'Drops',
+ params: {
+ ...defaultPatternSizing,
+ scale: 0.5,
+ speed: 2,
+ frame: 0,
+ colors: ['#ff00aa'],
+ stepsPerColor: 3,
+ colorBack: '#ffaa00',
+ colorShade: '#ffffff',
+ colorSpecular: '#ffffffa1',
+ colorInnerShadow: '#00ffd0',
+ distortion: 9.2,
+ size: 0.04,
+ specular: 1.0,
+ specularNormal: 1.0,
+ shade: 1.0,
+ innerShadow: 0.06,
+ offsetX: 0,
+ offsetY: 0,
+ rotation: 0,
+ },
+};
+
+export const shadowsPreset: BlobsGridPreset = {
+ name: 'Shadows',
+ params: {
+ ...defaultPatternSizing,
+ scale: 0.5,
+ speed: 2,
+ frame: 0,
+ colors: ['#c8e9c8', '#037c6e', '#ff9100'],
+ stepsPerColor: 3,
+ colorBack: '#ffffff',
+ colorShade: '#ffffff',
+ colorSpecular: '#ffffffa1',
+ colorInnerShadow: '#000000',
+ distortion: 5.6,
+ shade: 1.0,
+ specular: 0.0,
+ specularNormal: 0.82,
+ size: 0.51,
+ innerShadow: 0,
+ },
+};
+
+export const specularPreset: BlobsGridPreset = {
+ name: 'Specular',
+ params: {
+ ...defaultPatternSizing,
+ speed: 1,
+ frame: 0,
+ colors: ['#ff7b00', '#ff29f1', '#00b2ff'],
+ stepsPerColor: 2,
+ colorBack: '#000000',
+ colorShade: '#5b2f3d',
+ colorSpecular: '#ffffff80',
+ colorInnerShadow: '#000000',
+ distortion: 7,
+ shade: 0.3,
+ specular: 0.75,
+ specularNormal: 0.75,
+ size: 0.75,
+ innerShadow: 0.27,
+ },
+};
+
+export const blobsGridPresets: BlobsGridPreset[] = [defaultPreset, shadowsPreset, dropsPreset, specularPreset] as const;
+
+export const BlobsGrid: React.FC = memo(function BlobsGridImpl({
+ // Own props
+ speed = defaultPreset.params.speed,
+ frame = defaultPreset.params.frame,
+ colors = defaultPreset.params.colors,
+ stepsPerColor = defaultPreset.params.stepsPerColor,
+ colorBack = defaultPreset.params.colorBack,
+ colorShade = defaultPreset.params.colorShade,
+ colorSpecular = defaultPreset.params.colorSpecular,
+ colorInnerShadow = defaultPreset.params.colorInnerShadow,
+ distortion = defaultPreset.params.distortion,
+ shade = defaultPreset.params.shade,
+ specular = defaultPreset.params.specular,
+ specularNormal = defaultPreset.params.specularNormal,
+ size = defaultPreset.params.size,
+ innerShadow = defaultPreset.params.innerShadow,
+
+ // Sizing props
+ fit = defaultPreset.params.fit,
+ scale = defaultPreset.params.scale,
+ rotation = defaultPreset.params.rotation,
+ originX = defaultPreset.params.originX,
+ originY = defaultPreset.params.originY,
+ offsetX = defaultPreset.params.offsetX,
+ offsetY = defaultPreset.params.offsetY,
+ worldWidth = defaultPreset.params.worldWidth,
+ worldHeight = defaultPreset.params.worldHeight,
+ ...props
+}: BlobsGridProps) {
+ const uniforms = {
+ // Own uniforms
+ u_colors: colors.map(getShaderColorFromString),
+ u_colorsCount: colors.length,
+ u_stepsPerColor: stepsPerColor,
+ u_colorBack: getShaderColorFromString(colorBack),
+ u_colorShade: getShaderColorFromString(colorShade),
+ u_colorSpecular: getShaderColorFromString(colorSpecular),
+ u_colorInnerShadow: getShaderColorFromString(colorInnerShadow),
+ u_distortion: distortion,
+ u_shade: shade,
+ u_specular: specular,
+ u_specularNormal: specularNormal,
+ u_size: size,
+ u_innerShadow: innerShadow,
+
+ // Sizing uniforms
+ u_fit: ShaderFitOptions[fit],
+ u_scale: scale,
+ u_rotation: rotation,
+ u_offsetX: offsetX,
+ u_offsetY: offsetY,
+ u_originX: originX,
+ u_originY: originY,
+ u_worldWidth: worldWidth,
+ u_worldHeight: worldHeight,
+ } satisfies BlobsGridUniforms;
+
+ return (
+
+ );
+}, colorPropsAreEqual);
diff --git a/packages/shaders/src/index.ts b/packages/shaders/src/index.ts
index baa8fa37..28ab86f3 100644
--- a/packages/shaders/src/index.ts
+++ b/packages/shaders/src/index.ts
@@ -171,6 +171,13 @@ export {
type StaticRadialGradientUniforms,
} from './shaders/static-radial-gradient.js';
+export {
+ blobsGridMeta,
+ blobsGridFragmentShader,
+ type BlobsGridParams,
+ type BlobsGridUniforms,
+} from './shaders/blobs-grid.js';
+
// ----- Utils ----- //
export { getShaderColorFromString } from './get-shader-color-from-string.js';
export { getShaderNoiseTexture } from './get-shader-noise-texture.js';
diff --git a/packages/shaders/src/shaders/blobs-grid.ts b/packages/shaders/src/shaders/blobs-grid.ts
new file mode 100644
index 00000000..687fa83e
--- /dev/null
+++ b/packages/shaders/src/shaders/blobs-grid.ts
@@ -0,0 +1,200 @@
+import type { vec4 } from '../types.js';
+import type { ShaderMotionParams } from '../shader-mount.js';
+import { sizingVariablesDeclaration, type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';
+import { declareRandom, declarePI, declareRotate, declareSimplexNoise } from '../shader-utils.js';
+
+export const blobsGridMeta = {
+ maxColorCount: 4,
+} as const;
+
+/**
+ * A dot grid with distortion + 3d-simulating overlays (shades, outlines, highlights)
+ *
+ * Uniforms:
+ * - u_colorBack, u_colorShade, u_colorSpecular, u_colorInnerShadow (RGBA)
+ * - u_colors (vec4[]), u_colorsCount (float used as integer)
+ * - u_stepsPerColor: discrete color steps between u_colors
+ * - u_size, u_distortion: blob size & shape
+ * - u_specular, u_specularNormal: highlight size & shape (on each cell)
+ * - u_innerShadow, u_shade: 2 additional color overlays (on each cell)
+ *
+ */
+
+// language=GLSL
+export const blobsGridFragmentShader: string = `#version 300 es
+precision mediump float;
+
+uniform float u_time;
+
+uniform vec4 u_colors[${blobsGridMeta.maxColorCount}];
+uniform float u_colorsCount;
+uniform float u_stepsPerColor;
+
+uniform vec4 u_colorBack;
+uniform vec4 u_colorShade;
+uniform vec4 u_colorSpecular;
+uniform vec4 u_colorInnerShadow;
+uniform float u_distortion;
+uniform float u_specular;
+uniform float u_specularNormal;
+uniform float u_size;
+uniform float u_innerShadow;
+uniform float u_shade;
+
+uniform float u_scale;
+
+${sizingVariablesDeclaration}
+
+out vec4 fragColor;
+
+${declarePI}
+${declareSimplexNoise}
+
+vec2 rand2(vec2 c) {
+ mat2 m = mat2(12.9898, .16180, 78.233, .31415);
+ return fract(sin(m * c) * vec2(43758.5453, 14142.1));
+}
+
+vec2 noise(vec2 p) {
+ vec2 co = floor(p);
+ vec2 mu = fract(p);
+ mu = 3. * mu * mu - 2. * mu * mu * mu;
+ vec2 a = rand2((co + vec2(0., 0.)));
+ vec2 b = rand2((co + vec2(1., 0.)));
+ vec2 c = rand2((co + vec2(0., 1.)));
+ vec2 d = rand2((co + vec2(1., 1.)));
+ return mix(mix(a, b, mu.x), mix(c, d, mu.x), mu.y);
+}
+
+${declareRandom}
+${declareRotate}
+
+void main() {
+
+ vec2 uv = v_patternUV * .1;
+
+ float t = .1 * u_time;
+
+ vec3 lightDir = normalize(vec3(-.5, .5, -.65));
+
+ vec2 dropDistortion = noise(uv * u_distortion + t);
+ vec2 grid = TWO_PI * uv + dropDistortion;
+
+ vec2 cellUV = fract(grid);
+ vec2 cellIdx = floor(grid);
+
+
+ vec2 pos = cellUV - .5;
+ float aax = 2. * fwidth(grid.x);
+ float aay = 2. * fwidth(grid.y);
+ float cellInnerShadow = smoothstep(0., aax, cellUV.x);
+ cellInnerShadow *= smoothstep(0., aay, cellUV.y);
+ cellInnerShadow *= (smoothstep(1., 1. - aax, cellUV.x));
+ cellInnerShadow *= (smoothstep(1., 1. - aay, cellUV.y));
+ cellInnerShadow = clamp(cellInnerShadow, 0., 1.);
+
+ vec2 posNorm = normalize(pos);
+ float l = length(pos);
+ float dist = 1. - l;
+
+ dist *= (.5 + .7 * u_size);
+
+ const float shapeOuter = .43;
+ float aa = fwidth(l);
+ float contour = smoothstep(shapeOuter, shapeOuter + 2. * aa, dist);
+ contour *= (.5 + .5 * cellInnerShadow);
+
+ float colorCode = random(noise(cellIdx));
+ float mixer = colorCode * (u_colorsCount - 1.);
+ mixer = (colorCode - .5 / u_colorsCount) * u_colorsCount;
+ float steps = max(1., u_stepsPerColor);
+
+ vec4 gradient = u_colors[0];
+ gradient.rgb *= gradient.a;
+ for (int i = 1; i < ${blobsGridMeta.maxColorCount}; i++) {
+ if (i >= int(u_colorsCount)) break;
+ float localT = clamp(mixer - float(i - 1), 0.0, 1.0);
+ localT = round(localT * steps) / steps;
+ vec4 c = u_colors[i];
+ c.rgb *= c.a;
+ gradient = mix(gradient, c, localT);
+ }
+
+ if ((mixer < 0.) || (mixer > (u_colorsCount - 1.))) {
+ float localT = mixer + 1.;
+ if (mixer > (u_colorsCount - 1.)) {
+ localT = mixer - (u_colorsCount - 1.);
+ }
+ localT = round(localT * steps) / steps;
+ vec4 cFst = u_colors[0];
+ cFst.rgb *= cFst.a;
+ vec4 cLast = u_colors[int(u_colorsCount - 1.)];
+ cLast.rgb *= cLast.a;
+ gradient = mix(cLast, cFst, localT);
+ }
+
+
+ vec3 color = gradient.rgb;
+ float opacity = gradient.a;
+
+ vec3 shadeNormal = normalize(vec3(posNorm * sin(l * 10. * u_shade), -2.));
+ float shade = 1. - dot(shadeNormal, lightDir);
+ shade *= smoothstep(.0, .4, u_shade);
+ shade *= u_colorShade.a;
+ color = mix(color, u_colorShade.rgb * u_colorShade.a, shade);
+
+ float innerShadow = (1. - smoothstep(shapeOuter, shapeOuter + .5 * u_innerShadow, dist));
+ innerShadow *= u_colorInnerShadow.a;
+ color = mix(color, u_colorInnerShadow.rgb * u_colorInnerShadow.a, innerShadow);
+
+ posNorm = normalize(pos - .08);
+ vec3 specularNormal = normalize(vec3(posNorm * sin(l * (3. + 20. * u_specularNormal)), -2.));
+ float specular = smoothstep(1. - .25 * u_specular, 1. + aa - .25 * u_specular, dot(specularNormal, lightDir));
+ specular *= u_colorSpecular.a;
+ specular -= innerShadow;
+ specular -= smoothstep(.3, .5, shade);
+ specular = clamp(specular, 0., 1.);
+ color = mix(color, u_colorSpecular.rgb, specular);
+
+ color *= contour;
+ opacity *= contour;
+
+ vec3 bgColor = u_colorBack.rgb * u_colorBack.a;
+ color = color + bgColor * (1. - opacity);
+ opacity = opacity + u_colorBack.a * (1. - opacity);
+
+ fragColor = vec4(color, opacity);
+}
+
+`;
+
+export interface BlobsGridUniforms extends ShaderSizingUniforms {
+ u_colors: vec4[];
+ u_colorsCount: number;
+ u_stepsPerColor: number;
+ u_colorBack: [number, number, number, number];
+ u_colorShade: [number, number, number, number];
+ u_colorSpecular: [number, number, number, number];
+ u_colorInnerShadow: [number, number, number, number];
+ u_distortion: number;
+ u_specular: number;
+ u_specularNormal: number;
+ u_shade: number;
+ u_size: number;
+ u_innerShadow: number;
+}
+
+export interface BlobsGridParams extends ShaderSizingParams, ShaderMotionParams {
+ colors?: string[];
+ stepsPerColor?: number;
+ colorBack?: string;
+ colorShade?: string;
+ colorSpecular?: string;
+ colorInnerShadow?: string;
+ shade?: number;
+ distortion?: number;
+ specular?: number;
+ specularNormal?: number;
+ size?: number;
+ innerShadow?: number;
+}