feat(mesh-bg): fade-in шейдера фона + modulepreload графа

- canvas стартует с opacity:0 и плавно проявляется (0.9s) при .is-ready
- initPageMeshShader ставит is-ready после первого кадра
- modulepreload page-mesh-shader.js для параллельной загрузки с main.js
- пересборка страниц (build:pages)
This commit is contained in:
root committed 2026-09-24 21:37:40 +02:00
1 parent b38f1a5332
commit daa623fe73
170 files changed
+11582 -1496

No files matched your search

+8 -2
View File
@@ -2,13 +2,19 @@
## Сборка и деплой
Деплой автоматический: пуш в git → автодеплой на хостинг.
Деплой автоматический, но **только по пушу в ветку `main`** → автодеплой на хостинг.
Пуш в любую другую ветку (включая `dev`) деплоя **не вызывает** — `dev` это обычная рабочая ветка, в неё можно пушить свободно.
Правило коммитов: **после каждой правки делай локальный git commit** (без пуша, если не просили). Пуш — только по явному запросу пользователя, и для деплоя он должен идти в `main`.
По запросу пользователя «собрать», «билд»:
1. При правках `src/pages/**` или `src/partials/**` — `npm run build:pages`.
2. При смене шрифтов/Plyr или `package.json` — `npm run build` (включает `vendor:copy`).
3. Для деплоя — сделать git commit + push.
3. Для деплоя — пуш в `main` (предварительно сделав commit).
> Если пользователь просит «влить в мастер» — см. раздел «Влить в master» ниже. Это не то же самое, что пуш в `dev`.
## Git
Binary file not shown.

After

Width:  |  Height:  |  Size: 968 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

+2 -1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/cases/ai-assistant-figma/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/ai-assistant-figma/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI-дизайн и проектирование интерфейсов"
},
{
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/cases/ai-platform-primo-rpa-2/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/ai-platform-primo-rpa-2/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI-дизайн и проектирование интерфейсов"
},
{
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/cases/ai-platform-primo-rpa-v2/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/ai-platform-primo-rpa-v2/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI-дизайн и проектирование интерфейсов"
},
{
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/cases/ai-platform-primo-rpa/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/ai-platform-primo-rpa/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI-дизайн и проектирование интерфейсов"
},
{
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/cases/card-columns-obsidian/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/card-columns-obsidian/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI-дизайн и проектирование интерфейсов"
},
{
+1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/cases/design-system/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/design-system/" />
+2 -1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/cases/lunacy/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/lunacy/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI-дизайн и проектирование интерфейсов"
},
{
+2 -1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/cases/manu-game-maker/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/manu-game-maker/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI-дизайн и проектирование интерфейсов"
},
{
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/cases/mini-projects-showcase/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/mini-projects-showcase/" />
+2 -1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/cases/mobile-clinic-app/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/mobile-clinic-app/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI-дизайн и проектирование интерфейсов"
},
{
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/cases/obsidian-plugin-columns-view/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/obsidian-plugin-columns-view/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI-дизайн и проектирование интерфейсов"
},
{
+2 -1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/cases/orc-automation-panel/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/orc-automation-panel/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI-дизайн и проектирование интерфейсов"
},
{
+2 -1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/cases/site-with-ai-ide/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/site-with-ai-ide/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI-дизайн и проектирование интерфейсов"
},
{
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/cases/task-distribution-concept/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/task-distribution-concept/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI-дизайн и проектирование интерфейсов"
},
{
+2 -1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/cases/task-tracker/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/task-tracker/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI-дизайн и проектирование интерфейсов"
},
{
@@ -94,6 +94,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/cases/template-elements-demo/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/template-elements-demo/" />
+1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/cookie-policy/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cookie-policy/" />
+7 -1
View File
@@ -289,7 +289,13 @@ body {
width: 100%;
height: 100%;
display: block;
opacity: 0.4; /* приглушаем яркость живого шейдера */
opacity: 0; /* старт для fade-in живого шэйдера */
transition: opacity 0.9s ease;
}
/* Шэйдер отрисовал первый кадр — плавно проявляем его поверх статичного фона */
.page-mesh-bg.is-ready canvas {
opacity: 0.4; /* приглушаем яркость живого шэйдера */
}
/* Вертикальный градиент-оверлэй: снизу фон закрыт цветом страницы (--bg,
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cases/ai-assistant-figma/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/ai-assistant-figma/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI design and interface design"
},
{
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cases/ai-platform-primo-rpa-2/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/ai-platform-primo-rpa-2/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI design and interface design"
},
{
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cases/ai-platform-primo-rpa-v2/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/ai-platform-primo-rpa-v2/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI design and interface design"
},
{
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cases/ai-platform-primo-rpa/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/ai-platform-primo-rpa/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI design and interface design"
},
{
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cases/card-columns-obsidian/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/card-columns-obsidian/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI design and interface design"
},
{
+1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cases/design-system/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/design-system/" />
+2 -1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cases/lunacy/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/lunacy/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI design and interface design"
},
{
+2 -1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cases/manu-game-maker/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/manu-game-maker/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI design and interface design"
},
{
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cases/mini-projects-showcase/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/mini-projects-showcase/" />
+2 -1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cases/mobile-clinic-app/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/mobile-clinic-app/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI design and interface design"
},
{
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cases/obsidian-plugin-columns-view/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/obsidian-plugin-columns-view/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI design and interface design"
},
{
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cases/orc-automation-panel/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/orc-automation-panel/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI design and interface design"
},
{
+2 -1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cases/site-with-ai-ide/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/site-with-ai-ide/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI design and interface design"
},
{
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cases/task-distribution-concept/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/task-distribution-concept/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI design and interface design"
},
{
+2 -1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cases/task-tracker/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/task-tracker/" />
@@ -180,7 +181,7 @@
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-15",
"datePublished": "2026-09-24",
"about": "UX/UI design and interface design"
},
{
@@ -94,6 +94,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cases/template-elements-demo/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/template-elements-demo/" />
+1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cookie-policy/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cookie-policy/" />
+1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/" />
+1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/resume/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/resume/" />
+1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/" />
@@ -24,6 +24,6 @@ export async function saveEdits(payload) {
export function isContentEditHost() {
const host = window.location.hostname;
const ALLOWED_HOSTS = ["localhost", "127.0.0.1", "dev.mamadaevv.site", "f2ogw6fzi6trnp7cp43x8wsd.31.129.103.242.sslip.io"];
const ALLOWED_HOSTS = ["localhost", "127.0.0.1", "10.200.201.3", "dev.mamadaevv.site", "f2ogw6fzi6trnp7cp43x8wsd.31.129.103.242.sslip.io"];
return ALLOWED_HOSTS.includes(host);
}
+112
View File
@@ -0,0 +1,112 @@
import { ShaderMount, meshGradientFragmentShader, getShaderColorFromString, defaultObjectSizing, ShaderFitOptions } from "/vendor/paper-shaders/index.js";
import { prefersReducedMotion } from "../shared/media.js";
// Живой mesh-gradient фон главной страницы (Paper Shaders, zero-dep WebGL).
// Цвета берутся из акцентной темы сайта (--theme-h/s/l), чтобы фон всегда
// совпадал с выбранным пользователем акцентом.
function hsl(h, s, l, a = 1) {
// getShaderColorFromString понимает "hsl(h, s%, l%)" — собираем ровно в этом виде.
return `hsl(${Math.round(h)}, ${Math.round(s)}%, ${Math.round(l)}%)`;
}
function paletteFromTheme(root) {
const cs = getComputedStyle(root);
const h = parseFloat(cs.getPropertyValue("--theme-h"));
const s = parseFloat(cs.getPropertyValue("--theme-s"));
const l = parseFloat(cs.getPropertyValue("--theme-l"));
if (![h, s, l].every(Number.isFinite)) return null;
// Монохромная палитра: все цвета — вариации акцента (±18°/±36° hue,
// без крупных сдвигов), чтобы фон оставался однотонным, не разноцветным.
const a1 = (h + 36) % 360;
const a2 = (h + 18) % 360;
const a3 = (h - 18 + 360) % 360;
return [
hsl(a1, s, Math.min(l + 2, 54)),
hsl(a2, s, Math.min(l, 50)),
hsl(a3, s, Math.min(l - 2, 46)),
hsl(h, s, Math.min(l + 6, 58)),
];
}
function colorVec4Array(colors) {
// Шейдер ждёт u_colors как vec4[10] → массив из 10 под-массивов [r,g,b,a].
// setUniformValues сам flatten'ит массив массивов в uniform4fv.
const out = [];
for (let i = 0; i < 10; i++) {
const c = colors[i % colors.length];
out.push(getShaderColorFromString(c));
}
return out;
}
export function initPageMeshShader() {
const host = document.querySelector(".page-mesh-bg");
if (!host) return;
if (prefersReducedMotion()) return; // статичный CSS-fallback остаётся виден
const sizing = () => ({
u_fit: ShaderFitOptions[defaultObjectSizing.fit],
u_scale: defaultObjectSizing.scale,
u_rotation: defaultObjectSizing.rotation,
u_offsetX: defaultObjectSizing.offsetX,
u_offsetY: defaultObjectSizing.offsetY,
u_originX: defaultObjectSizing.originX,
u_originY: defaultObjectSizing.originY,
u_worldWidth: defaultObjectSizing.worldWidth,
u_worldHeight: defaultObjectSizing.worldHeight,
});
const root = document.documentElement;
const base = paletteFromTheme(root) || ["#241d9a", "#f75092", "#9f50d3", "#0e6b8f"];
const uniforms = {
...sizing(),
u_colors: colorVec4Array(base),
u_colorsCount: base.length,
u_distortion: 0.85,
u_swirl: 0.35,
u_grainMixer: 0.06,
u_grainOverlay: 0.04,
};
let mount;
try {
mount = new ShaderMount(
host,
meshGradientFragmentShader,
uniforms,
{ antialias: true, alpha: true, preserveDrawingBuffer: true },
1.0, // speed
0, // frame
1, // minPixelRatio: ровно CSS-пиксели, без 2x апскейла (главный источник нагрузки)
2_500_000 // maxPixelCount: потолок ~1600x1000, фон размытый — качество не страдает
);
// не даём IntersectionObserver ставить паузу вне вьюпорта — фон на весь экран
try { mount.intersectionObserver?.disconnect(); } catch {}
// Первый кадр уже отрисован в конструкторе — отмечаем готовность,
// чтобы CSS плавно проявил canvas (fade-in), без рывка.
host.classList.add("is-ready");
} catch (e) {
console.warn("Mesh shader mount failed, fallback to static bg", e);
return;
}
// Обновляем палитру при смене акцента/темы.
const onTheme = () => {
const next = paletteFromTheme(root);
if (!next) return;
mount.setUniforms({
u_colors: colorVec4Array(next),
u_colorsCount: next.length,
});
};
window.addEventListener("site-theme-applied", onTheme, { passive: true });
// при уходе со страницы — чистим
window.addEventListener("pagehide", () => {
window.removeEventListener("site-theme-applied", onTheme);
try { mount.dispose(); } catch {}
}, { once: true });
}
+1
View File
@@ -97,6 +97,7 @@
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/resume/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/resume/" />
+16 -16
View File
@@ -3,112 +3,112 @@
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>https://mamadaevv.site/cases/ai-assistant-figma/</loc>
<lastmod>2026-09-15</lastmod>
<lastmod>2026-09-24</lastmod>
<xhtml:link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/cases/ai-assistant-figma/" />
<xhtml:link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/ai-assistant-figma/" />
<xhtml:link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/cases/ai-assistant-figma/" />
</url>
<url>
<loc>https://mamadaevv.site/cases/ai-platform-primo-rpa-2/</loc>
<lastmod>2026-09-15</lastmod>
<lastmod>2026-09-24</lastmod>
<xhtml:link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/cases/ai-platform-primo-rpa-2/" />
<xhtml:link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/ai-platform-primo-rpa-2/" />
<xhtml:link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/cases/ai-platform-primo-rpa-2/" />
</url>
<url>
<loc>https://mamadaevv.site/cases/ai-platform-primo-rpa-v2/</loc>
<lastmod>2026-09-15</lastmod>
<lastmod>2026-09-24</lastmod>
<xhtml:link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/cases/ai-platform-primo-rpa-v2/" />
<xhtml:link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/ai-platform-primo-rpa-v2/" />
<xhtml:link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/cases/ai-platform-primo-rpa-v2/" />
</url>
<url>
<loc>https://mamadaevv.site/cases/ai-platform-primo-rpa/</loc>
<lastmod>2026-09-15</lastmod>
<lastmod>2026-09-24</lastmod>
<xhtml:link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/cases/ai-platform-primo-rpa/" />
<xhtml:link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/ai-platform-primo-rpa/" />
<xhtml:link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/cases/ai-platform-primo-rpa/" />
</url>
<url>
<loc>https://mamadaevv.site/cases/card-columns-obsidian/</loc>
<lastmod>2026-09-15</lastmod>
<lastmod>2026-09-24</lastmod>
<xhtml:link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/cases/card-columns-obsidian/" />
<xhtml:link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/card-columns-obsidian/" />
<xhtml:link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/cases/card-columns-obsidian/" />
</url>
<url>
<loc>https://mamadaevv.site/cases/lunacy/</loc>
<lastmod>2026-09-15</lastmod>
<lastmod>2026-09-24</lastmod>
<xhtml:link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/cases/lunacy/" />
<xhtml:link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/lunacy/" />
<xhtml:link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/cases/lunacy/" />
</url>
<url>
<loc>https://mamadaevv.site/cases/manu-game-maker/</loc>
<lastmod>2026-09-15</lastmod>
<lastmod>2026-09-24</lastmod>
<xhtml:link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/cases/manu-game-maker/" />
<xhtml:link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/manu-game-maker/" />
<xhtml:link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/cases/manu-game-maker/" />
</url>
<url>
<loc>https://mamadaevv.site/cases/mobile-clinic-app/</loc>
<lastmod>2026-09-15</lastmod>
<lastmod>2026-09-24</lastmod>
<xhtml:link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/cases/mobile-clinic-app/" />
<xhtml:link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/mobile-clinic-app/" />
<xhtml:link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/cases/mobile-clinic-app/" />
</url>
<url>
<loc>https://mamadaevv.site/cases/obsidian-plugin-columns-view/</loc>
<lastmod>2026-09-15</lastmod>
<lastmod>2026-09-24</lastmod>
<xhtml:link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/cases/obsidian-plugin-columns-view/" />
<xhtml:link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/obsidian-plugin-columns-view/" />
<xhtml:link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/cases/obsidian-plugin-columns-view/" />
</url>
<url>
<loc>https://mamadaevv.site/cases/orc-automation-panel/</loc>
<lastmod>2026-09-15</lastmod>
<lastmod>2026-09-24</lastmod>
<xhtml:link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/cases/orc-automation-panel/" />
<xhtml:link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/orc-automation-panel/" />
<xhtml:link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/cases/orc-automation-panel/" />
</url>
<url>
<loc>https://mamadaevv.site/cases/site-with-ai-ide/</loc>
<lastmod>2026-09-15</lastmod>
<lastmod>2026-09-24</lastmod>
<xhtml:link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/cases/site-with-ai-ide/" />
<xhtml:link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/site-with-ai-ide/" />
<xhtml:link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/cases/site-with-ai-ide/" />
</url>
<url>
<loc>https://mamadaevv.site/cases/task-distribution-concept/</loc>
<lastmod>2026-09-15</lastmod>
<lastmod>2026-09-24</lastmod>
<xhtml:link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/cases/task-distribution-concept/" />
<xhtml:link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/task-distribution-concept/" />
<xhtml:link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/cases/task-distribution-concept/" />
</url>
<url>
<loc>https://mamadaevv.site/cases/task-tracker/</loc>
<lastmod>2026-09-15</lastmod>
<lastmod>2026-09-24</lastmod>
<xhtml:link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/cases/task-tracker/" />
<xhtml:link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/task-tracker/" />
<xhtml:link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/cases/task-tracker/" />
</url>
<url>
<loc>https://mamadaevv.site/cookie-policy/</loc>
<lastmod>2026-09-16</lastmod>
<lastmod>2026-09-24</lastmod>
<xhtml:link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/cookie-policy/" />
<xhtml:link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cookie-policy/" />
<xhtml:link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/cookie-policy/" />
</url>
<url>
<loc>https://mamadaevv.site/</loc>
<lastmod>2026-09-19</lastmod>
<lastmod>2026-09-24</lastmod>
<xhtml:link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/" />
<xhtml:link rel="alternate" hreflang="ru" href="https://mamadaevv.site/" />
<xhtml:link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/" />
</url>
<url>
<loc>https://mamadaevv.site/resume/</loc>
<lastmod>2026-09-21</lastmod>
<lastmod>2026-09-24</lastmod>
<xhtml:link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/resume/" />
<xhtml:link rel="alternate" hreflang="ru" href="https://mamadaevv.site/resume/" />
<xhtml:link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/resume/" />
+1
View File
@@ -0,0 +1 @@
export declare const emptyPixel = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
+10
View File
@@ -0,0 +1,10 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
const emptyPixel = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
export {
emptyPixel
};
//# sourceMappingURL=empty-pixel.js.map
+7
View File
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../src/empty-pixel.ts"],
"sourcesContent": ["export const emptyPixel = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';\n"],
"mappings": ";;;;;AAAO,MAAM,aAAa;",
"names": []
}
@@ -0,0 +1,3 @@
/** Convert color string from HSL, RGB, or hex to 0-to-1-range-RGBA array */
export declare function getShaderColorFromString(colorString: string | [number, number, number] | [number, number, number, number] | undefined): [number, number, number, number];
export declare const clamp: (n: number, min: number, max: number) => number;
@@ -0,0 +1,101 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
function getShaderColorFromString(colorString) {
if (Array.isArray(colorString)) {
if (colorString.length === 4) return colorString;
if (colorString.length === 3) return [...colorString, 1];
return fallbackColor;
}
if (typeof colorString !== "string") {
return fallbackColor;
}
let r, g, b, a = 1;
if (colorString.startsWith("#")) {
[r, g, b, a] = hexToRgba(colorString);
} else if (colorString.startsWith("rgb")) {
const rgba = parseRgba(colorString);
if (rgba === null) return fallbackColor;
[r, g, b, a] = rgba;
} else if (colorString.startsWith("hsl")) {
const hsla = parseHsla(colorString);
if (hsla === null) return fallbackColor;
[r, g, b, a] = hslaToRgba(hsla);
} else {
console.error("Unsupported color format", colorString);
return fallbackColor;
}
return [clamp(r, 0, 1), clamp(g, 0, 1), clamp(b, 0, 1), clamp(a, 0, 1)];
}
function hexToRgba(hex) {
hex = hex.replace(/^#/, "");
if (hex.length === 3 || hex.length === 4) {
hex = hex.split("").map((char) => char + char).join("");
}
if (hex.length === 6) {
hex = hex + "ff";
}
if (!/^[0-9a-f]{8}$/i.test(hex)) {
console.warn("Invalid hex color");
return fallbackColor;
}
const r = parseInt(hex.slice(0, 2), 16) / 255;
const g = parseInt(hex.slice(2, 4), 16) / 255;
const b = parseInt(hex.slice(4, 6), 16) / 255;
const a = parseInt(hex.slice(6, 8), 16) / 255;
return [r, g, b, a];
}
function parseRgba(rgba) {
const match = rgba.match(/^rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([0-9.]+))?\s*\)$/i);
if (!match) return null;
return [
parseInt(match[1] ?? "0") / 255,
parseInt(match[2] ?? "0") / 255,
parseInt(match[3] ?? "0") / 255,
match[4] === void 0 ? 1 : parseFloat(match[4])
];
}
function parseHsla(hsla) {
const match = hsla.match(/^hsla?\s*\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*(?:,\s*([0-9.]+))?\s*\)$/i);
if (!match) return null;
return [
parseInt(match[1] ?? "0"),
parseInt(match[2] ?? "0"),
parseInt(match[3] ?? "0"),
match[4] === void 0 ? 1 : parseFloat(match[4])
];
}
function hslaToRgba(hsla) {
const [h, s, l, a] = hsla;
const hDecimal = h / 360;
const sDecimal = s / 100;
const lDecimal = l / 100;
let r, g, b;
if (s === 0) {
r = g = b = lDecimal;
} else {
const hue2rgb = (p2, q2, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p2 + (q2 - p2) * 6 * t;
if (t < 1 / 2) return q2;
if (t < 2 / 3) return p2 + (q2 - p2) * (2 / 3 - t) * 6;
return p2;
};
const q = lDecimal < 0.5 ? lDecimal * (1 + sDecimal) : lDecimal + sDecimal - lDecimal * sDecimal;
const p = 2 * lDecimal - q;
r = hue2rgb(p, q, hDecimal + 1 / 3);
g = hue2rgb(p, q, hDecimal);
b = hue2rgb(p, q, hDecimal - 1 / 3);
}
return [r, g, b, a];
}
const clamp = (n, min, max) => Math.min(Math.max(n, min), max);
const fallbackColor = [0.5, 0.5, 0.5, 1];
export {
clamp,
getShaderColorFromString
};
//# sourceMappingURL=get-shader-color-from-string.js.map
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../src/get-shader-color-from-string.ts"],
"sourcesContent": ["/** Convert color string from HSL, RGB, or hex to 0-to-1-range-RGBA array */\nexport function getShaderColorFromString(\n colorString: string | [number, number, number] | [number, number, number, number] | undefined\n): [number, number, number, number] {\n // If the color string is already an array of 3 or 4 numbers, return it (with alpha=1 if needed)\n if (Array.isArray(colorString)) {\n if (colorString.length === 4) return colorString as [number, number, number, number];\n if (colorString.length === 3) return [...colorString, 1];\n return fallbackColor;\n }\n\n // If the color string is not a string, return the fallback\n if (typeof colorString !== 'string') {\n return fallbackColor;\n }\n\n let r: number,\n g: number,\n b: number,\n a = 1;\n if (colorString.startsWith('#')) {\n [r, g, b, a] = hexToRgba(colorString);\n } else if (colorString.startsWith('rgb')) {\n const rgba = parseRgba(colorString);\n if (rgba === null) return fallbackColor;\n [r, g, b, a] = rgba;\n } else if (colorString.startsWith('hsl')) {\n const hsla = parseHsla(colorString);\n if (hsla === null) return fallbackColor;\n [r, g, b, a] = hslaToRgba(hsla);\n } else {\n console.error('Unsupported color format', colorString);\n return fallbackColor;\n }\n\n return [clamp(r, 0, 1), clamp(g, 0, 1), clamp(b, 0, 1), clamp(a, 0, 1)];\n}\n\n/** Convert hex to RGBA (0 to 1 range) */\nfunction hexToRgba(hex: string): [number, number, number, number] {\n // Remove # if present\n hex = hex.replace(/^#/, '');\n\n // Expand shorthand hex (3-letter #rgb or 4-letter #rgba) by doubling each digit\n if (hex.length === 3 || hex.length === 4) {\n hex = hex\n .split('')\n .map((char) => char + char)\n .join('');\n }\n // Expand six-letter hex to eight-letter (add full opacity if no alpha)\n if (hex.length === 6) {\n hex = hex + 'ff';\n }\n\n // Bail out on malformed hex (wrong length or non-hex characters) so callers\n // get the fallback color instead of NaN channels leaking into the shader.\n if (!/^[0-9a-f]{8}$/i.test(hex)) {\n console.warn('Invalid hex color');\n return fallbackColor;\n }\n\n // Parse the components\n const r = parseInt(hex.slice(0, 2), 16) / 255;\n const g = parseInt(hex.slice(2, 4), 16) / 255;\n const b = parseInt(hex.slice(4, 6), 16) / 255;\n const a = parseInt(hex.slice(6, 8), 16) / 255;\n\n return [r, g, b, a];\n}\n\n/** Parse RGBA string to RGBA (0 to 1 range), or null if unparseable */\nfunction parseRgba(rgba: string): [number, number, number, number] | null {\n // Match both rgb and rgba patterns\n const match = rgba.match(/^rgba?\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*(?:,\\s*([0-9.]+))?\\s*\\)$/i);\n if (!match) return null;\n\n return [\n parseInt(match[1] ?? '0') / 255,\n parseInt(match[2] ?? '0') / 255,\n parseInt(match[3] ?? '0') / 255,\n match[4] === undefined ? 1 : parseFloat(match[4]),\n ];\n}\n\n/** Parse HSLA string, or null if unparseable */\nfunction parseHsla(hsla: string): [number, number, number, number] | null {\n const match = hsla.match(/^hsla?\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)%\\s*,\\s*(\\d+)%\\s*(?:,\\s*([0-9.]+))?\\s*\\)$/i);\n if (!match) return null;\n\n return [\n parseInt(match[1] ?? '0'),\n parseInt(match[2] ?? '0'),\n parseInt(match[3] ?? '0'),\n match[4] === undefined ? 1 : parseFloat(match[4]),\n ];\n}\n\n/** Convert HSLA to RGBA (0 to 1 range) */\nfunction hslaToRgba(hsla: [number, number, number, number]): [number, number, number, number] {\n const [h, s, l, a] = hsla;\n const hDecimal = h / 360;\n const sDecimal = s / 100;\n const lDecimal = l / 100;\n let r, g, b;\n\n if (s === 0) {\n r = g = b = lDecimal; // achromatic\n } else {\n const hue2rgb = (p: number, q: number, t: number) => {\n if (t < 0) t += 1;\n if (t > 1) t -= 1;\n if (t < 1 / 6) return p + (q - p) * 6 * t;\n if (t < 1 / 2) return q;\n if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;\n return p;\n };\n\n const q = lDecimal < 0.5 ? lDecimal * (1 + sDecimal) : lDecimal + sDecimal - lDecimal * sDecimal;\n const p = 2 * lDecimal - q;\n r = hue2rgb(p, q, hDecimal + 1 / 3);\n g = hue2rgb(p, q, hDecimal);\n b = hue2rgb(p, q, hDecimal - 1 / 3);\n }\n\n return [r, g, b, a];\n}\n\nexport const clamp = (n: number, min: number, max: number): number => Math.min(Math.max(n, min), max);\n\nconst fallbackColor = [0.5, 0.5, 0.5, 1] as [number, number, number, number];\n"],
"mappings": "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",
"names": ["p", "q"]
}
@@ -0,0 +1 @@
export declare function getShaderNoiseTexture(): HTMLImageElement | undefined;
+18
View File
@@ -0,0 +1,18 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
function getShaderNoiseTexture() {
if (typeof window === "undefined") {
return void 0;
}
const img = new Image();
img.src = noiseSrc;
return img;
}
const noiseSrc = "data:image/png;base64,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 Line truncated
export {
getShaderNoiseTexture
};
//# sourceMappingURL=get-shader-noise-texture.js.map
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../src/get-shader-noise-texture.ts"],
"sourcesContent": ["export function getShaderNoiseTexture(): HTMLImageElement | undefined {\n if (typeof window === 'undefined') {\n return undefined;\n }\n\n const img = new Image();\n img.src = noiseSrc;\n return img;\n}\n\nconst noiseSrc =\n 'data:image/png;base64,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 truncated
"mappings": ";;;;;AAAO,SAAS,wBAAsD;AACpE,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,IAAI,MAAM;AACtB,MAAI,MAAM;AACV,SAAO;AACT;AAEA,MAAM,WACJ;",
"names": []
}
+68
View File
@@ -0,0 +1,68 @@
/** The core Shader Mount class. Pass it a parent element and a fragment shader to get started. */
export { ShaderMount, isPaperShaderElement } from './shader-mount.js';
export type { PaperShaderElement, ShaderMotionParams, ShaderMountUniforms, ShaderPreset, ImageShaderPreset, } from './shader-mount.js';
/** Shader sizing options and uniforms */
export { defaultObjectSizing, defaultPatternSizing, ShaderFitOptions, type ShaderFit, type ShaderSizingParams, type ShaderSizingUniforms, } from './shader-sizing.js';
/** A flowing interplay of color spots, moving along distinct trajectories and transformed by organic distortion */
export { meshGradientFragmentShader, meshGradientMeta, type MeshGradientParams, type MeshGradientUniforms, } from './shaders/mesh-gradient.js';
/** Radial multi-colored gradient shaped with layered noise for a natural, smoky aesthetic */
export { smokeRingMeta, smokeRingFragmentShader, type SmokeRingParams, type SmokeRingUniforms, } from './shaders/smoke-ring.js';
/** A glowing, web-like structure of fluid lines and soft intersections. Great for creating atmospheric, organic-yet-futuristic visuals */
export { neuroNoiseFragmentShader, type NeuroNoiseParams, type NeuroNoiseUniforms } from './shaders/neuro-noise.js';
/** Animated multi-color dots pattern with each dot orbiting around its cell center. Supports up to 40 colors and various shape and motion controls. Great for playful, dynamic backgrounds and UI textures */
export { dotOrbitMeta, dotOrbitFragmentShader, type DotOrbitParams, type DotOrbitUniforms, } from './shaders/dot-orbit.js';
/** Static grid pattern made of circles, diamonds, squares or triangles */
export { dotGridFragmentShader, DotGridShapes, type DotGridShape, type DotGridParams, type DotGridUniforms, } from './shaders/dot-grid.js';
/** A multi-color gradient mapped into smooth, animated curves, delivering a sleek, futuristic visual */
export { simplexNoiseMeta, simplexNoiseFragmentShader, type SimplexNoiseParams, type SimplexNoiseUniforms, } from './shaders/simplex-noise.js';
/** Up to 20 gooey blobs moving around the center and merging into smooth organic shapes */
export { metaballsMeta, metaballsFragmentShader, type MetaballsParams, type MetaballsUniforms, } from './shaders/metaballs.js';
/** Animated 3D Perlin noise with exposed controls */
export { perlinNoiseFragmentShader, type PerlinNoiseParams, type PerlinNoiseUniforms } from './shaders/perlin-noise.js';
/** Anti-aliased animated Voronoi pattern with smooth and customizable edges */
export { voronoiMeta, voronoiFragmentShader, type VoronoiParams, type VoronoiUniforms } from './shaders/voronoi.js';
/** Static line pattern configurable into textures ranging from sharp zigzags to smooth flowing waves */
export { wavesFragmentShader, type WavesParams, type WavesUniforms } from './shaders/waves.js';
/** Animated color fields warped by noise and swirls, applied over base patterns (checks, stripes, or split edge). Blends up to 10 colors with adjustable distribution, softness, distortion, and swirl. Great for fluid, smoky, or marbled effects */
export { warpMeta, warpFragmentShader, WarpPatterns, type WarpParams, type WarpUniforms, type WarpPattern, } from './shaders/warp.js';
/** Animated rays of light radiating from the center, blended with up to 5 colors. The rays are adjustable by size, density, brightness and center glow. Great for dramatic backgrounds, logo reveals, and VFX like energy bursts or sun shafts */
export { godRaysMeta, godRaysFragmentShader, type GodRaysParams, type GodRaysUniforms } from './shaders/god-rays.js';
/** A single-colored animated spiral that morphs across a wide range of shapes - from crisp, thin-lined geometry to flowing whirlpool forms and wavy, abstract rings */
export { spiralFragmentShader, type SpiralParams, type SpiralUniforms } from './shaders/spiral.js';
/** Animated bands of color twisting and bending, producing spirals, arcs, and flowing circular patterns */
export { swirlMeta, swirlFragmentShader, type SwirlParams, type SwirlUniforms } from './shaders/swirl.js';
/** Animated 2-color dithering over with multiple pattern sources (noise, warp, dots, waves, ripple, swirl, sphere) */
export { ditheringFragmentShader, DitheringShapes, DitheringTypes, type DitheringParams, type DitheringUniforms, type DitheringShape, type DitheringType, } from './shaders/dithering.js';
/** Multi-color gradients with grainy, noise-textured distortion available in 7 animated abstract forms */
export { grainGradientFragmentShader, grainGradientMeta, GrainGradientShapes, type GrainGradientParams, type GrainGradientUniforms, type GrainGradientShape, } from './shaders/grain-gradient.js';
/** Luminous trails of color merging into a glowing gradient frame */
export { pulsingBorderMeta, pulsingBorderFragmentShader, PulsingBorderAspectRatios, type PulsingBorderParams, type PulsingBorderUniforms, type PulsingBorderAspectRatio, } from './shaders/pulsing-border.js';
/** Glowing translucent 3D panels rotating around a central axis */
export { colorPanelsFragmentShader, colorPanelsMeta, type ColorPanelsParams, type ColorPanelsUniforms, } from './shaders/color-panels.js';
/** Multi-point mesh gradients with up to 10 color spots, enhanced by two-direction warping, adjustable blend sharpness, and grain controls. Perfect for elegant wallpapers and atmospheric backdrops */
export { staticMeshGradientFragmentShader, staticMeshGradientMeta, type StaticMeshGradientParams, type StaticMeshGradientUniforms, } from './shaders/static-mesh-gradient.js';
/** Radial gradient with up to 10 blended colors, featuring advanced focal point control, shape distortion, and grain effects */
export { staticRadialGradientFragmentShader, staticRadialGradientMeta, type StaticRadialGradientParams, type StaticRadialGradientUniforms, } from './shaders/static-radial-gradient.js';
/** A static texture built from multiple noise layers, usable for a realistic paper and cardboard surfaces or generating abstract patterns. Can be used as a image filter or as a texture */
export { paperTextureFragmentShader, type PaperTextureParams, type PaperTextureUniforms, } from './shaders/paper-texture.js';
/** Water-like surface distortion with natural caustic realism. Works as an image filter or animated texture without image */
export { waterFragmentShader, type WaterParams, type WaterUniforms } from './shaders/water.js';
/** Fluted glass image filter transforms an image into streaked, ribbed distortions, giving a mix of clarity and obscurity */
export { flutedGlassFragmentShader, GlassDistortionShapes, GlassGridShapes, type GlassDistortionShape, type GlassGridShape, type FlutedGlassParams, type FlutedGlassUniforms, } from './shaders/fluted-glass.js';
/** A dithering image filter with support for 4 dithering modes and multiple color palettes (2-color, 3-color, and multicolor options, using either predefined colors or ones sampled directly from the original image) */
export { imageDitheringFragmentShader, type ImageDitheringParams, type ImageDitheringUniforms, } from './shaders/image-dithering.js';
/** Lens Distortion image filter that separates an image into shifting color layers, recreating the chromatic aberration of a lens, and warps the image geometry with barrel or pincushion curvature */
export { lensDistortionFragmentShader, lensDistortionMeta, type LensDistortionParams, type LensDistortionUniforms } from './shaders/lens-distortion.js';
/** A glowing gradient of colors flowing through an input image. The effect creates a smoothly animated wave of intensity across the image */
export { heatmapMeta, heatmapFragmentShader, toProcessedHeatmap, type HeatmapParams, type HeatmapUniforms, } from './shaders/heatmap.js';
/** Futuristic liquid metal material applied to uploaded logo or one of pre-defined abstract shape */
export { liquidMetalFragmentShader, LiquidMetalShapes, toProcessedLiquidMetal, type LiquidMetalShape, type LiquidMetalParams, type LiquidMetalUniforms, } from './shaders/liquid-metal.js';
/** A halftone-dot image filter featuring customizable grids, color palettes, and dot styles */
export { HalftoneDotsTypes, HalftoneDotsGrids, halftoneDotsFragmentShader, type HalftoneDotsParams, type HalftoneDotsUniforms, type HalftoneDotsType, type HalftoneDotsGrid, } from './shaders/halftone-dots.js';
/** A halftone CMYK classic algo */
export { HalftoneCmykTypes, halftoneCmykFragmentShader, type HalftoneCmykParams, type HalftoneCmykUniforms, type HalftoneCmykType, } from './shaders/halftone-cmyk.js';
/** Animated color fields running around a glassy logo shape */
export { gemSmokeMeta, gemSmokeFragmentShader, toProcessedGemSmoke, GemSmokeShapes, type GemSmokeShape, type GemSmokeParams, type GemSmokeUniforms, } from './shaders/gem-smoke.js';
export { getShaderColorFromString } from './get-shader-color-from-string.js';
export { getShaderNoiseTexture } from './get-shader-noise-texture.js';
export { emptyPixel } from './empty-pixel.js';
+189
View File
@@ -0,0 +1,189 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
import { ShaderMount, isPaperShaderElement } from "./shader-mount.js";
import {
defaultObjectSizing,
defaultPatternSizing,
ShaderFitOptions
} from "./shader-sizing.js";
import {
meshGradientFragmentShader,
meshGradientMeta
} from "./shaders/mesh-gradient.js";
import {
smokeRingMeta,
smokeRingFragmentShader
} from "./shaders/smoke-ring.js";
import { neuroNoiseFragmentShader } from "./shaders/neuro-noise.js";
import {
dotOrbitMeta,
dotOrbitFragmentShader
} from "./shaders/dot-orbit.js";
import {
dotGridFragmentShader,
DotGridShapes
} from "./shaders/dot-grid.js";
import {
simplexNoiseMeta,
simplexNoiseFragmentShader
} from "./shaders/simplex-noise.js";
import {
metaballsMeta,
metaballsFragmentShader
} from "./shaders/metaballs.js";
import { perlinNoiseFragmentShader } from "./shaders/perlin-noise.js";
import { voronoiMeta, voronoiFragmentShader } from "./shaders/voronoi.js";
import { wavesFragmentShader } from "./shaders/waves.js";
import {
warpMeta,
warpFragmentShader,
WarpPatterns
} from "./shaders/warp.js";
import { godRaysMeta, godRaysFragmentShader } from "./shaders/god-rays.js";
import { spiralFragmentShader } from "./shaders/spiral.js";
import { swirlMeta, swirlFragmentShader } from "./shaders/swirl.js";
import {
ditheringFragmentShader,
DitheringShapes,
DitheringTypes
} from "./shaders/dithering.js";
import {
grainGradientFragmentShader,
grainGradientMeta,
GrainGradientShapes
} from "./shaders/grain-gradient.js";
import {
pulsingBorderMeta,
pulsingBorderFragmentShader,
PulsingBorderAspectRatios
} from "./shaders/pulsing-border.js";
import {
colorPanelsFragmentShader,
colorPanelsMeta
} from "./shaders/color-panels.js";
import {
staticMeshGradientFragmentShader,
staticMeshGradientMeta
} from "./shaders/static-mesh-gradient.js";
import {
staticRadialGradientFragmentShader,
staticRadialGradientMeta
} from "./shaders/static-radial-gradient.js";
import {
paperTextureFragmentShader
} from "./shaders/paper-texture.js";
import { waterFragmentShader } from "./shaders/water.js";
import {
flutedGlassFragmentShader,
GlassDistortionShapes,
GlassGridShapes
} from "./shaders/fluted-glass.js";
import {
imageDitheringFragmentShader
} from "./shaders/image-dithering.js";
import { lensDistortionFragmentShader, lensDistortionMeta } from "./shaders/lens-distortion.js";
import {
heatmapMeta,
heatmapFragmentShader,
toProcessedHeatmap
} from "./shaders/heatmap.js";
import {
liquidMetalFragmentShader,
LiquidMetalShapes,
toProcessedLiquidMetal
} from "./shaders/liquid-metal.js";
import {
HalftoneDotsTypes,
HalftoneDotsGrids,
halftoneDotsFragmentShader
} from "./shaders/halftone-dots.js";
import {
HalftoneCmykTypes,
halftoneCmykFragmentShader
} from "./shaders/halftone-cmyk.js";
import {
gemSmokeMeta,
gemSmokeFragmentShader,
toProcessedGemSmoke,
GemSmokeShapes
} from "./shaders/gem-smoke.js";
import { getShaderColorFromString } from "./get-shader-color-from-string.js";
import { getShaderNoiseTexture } from "./get-shader-noise-texture.js";
import { emptyPixel } from "./empty-pixel.js";
export {
DitheringShapes,
DitheringTypes,
DotGridShapes,
GemSmokeShapes,
GlassDistortionShapes,
GlassGridShapes,
GrainGradientShapes,
HalftoneCmykTypes,
HalftoneDotsGrids,
HalftoneDotsTypes,
LiquidMetalShapes,
PulsingBorderAspectRatios,
ShaderFitOptions,
ShaderMount,
WarpPatterns,
colorPanelsFragmentShader,
colorPanelsMeta,
defaultObjectSizing,
defaultPatternSizing,
ditheringFragmentShader,
dotGridFragmentShader,
dotOrbitFragmentShader,
dotOrbitMeta,
emptyPixel,
flutedGlassFragmentShader,
gemSmokeFragmentShader,
gemSmokeMeta,
getShaderColorFromString,
getShaderNoiseTexture,
godRaysFragmentShader,
godRaysMeta,
grainGradientFragmentShader,
grainGradientMeta,
halftoneCmykFragmentShader,
halftoneDotsFragmentShader,
heatmapFragmentShader,
heatmapMeta,
imageDitheringFragmentShader,
isPaperShaderElement,
lensDistortionFragmentShader,
lensDistortionMeta,
liquidMetalFragmentShader,
meshGradientFragmentShader,
meshGradientMeta,
metaballsFragmentShader,
metaballsMeta,
neuroNoiseFragmentShader,
paperTextureFragmentShader,
perlinNoiseFragmentShader,
pulsingBorderFragmentShader,
pulsingBorderMeta,
simplexNoiseFragmentShader,
simplexNoiseMeta,
smokeRingFragmentShader,
smokeRingMeta,
spiralFragmentShader,
staticMeshGradientFragmentShader,
staticMeshGradientMeta,
staticRadialGradientFragmentShader,
staticRadialGradientMeta,
swirlFragmentShader,
swirlMeta,
toProcessedGemSmoke,
toProcessedHeatmap,
toProcessedLiquidMetal,
voronoiFragmentShader,
voronoiMeta,
warpFragmentShader,
warpMeta,
waterFragmentShader,
wavesFragmentShader
};
//# sourceMappingURL=index.js.map
+7
View File
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../src/index.ts"],
"sourcesContent": ["/** The core Shader Mount class. Pass it a parent element and a fragment shader to get started. */\nexport { ShaderMount, isPaperShaderElement } from './shader-mount.js';\nexport type {\n PaperShaderElement,\n ShaderMotionParams,\n ShaderMountUniforms,\n ShaderPreset,\n ImageShaderPreset,\n} from './shader-mount.js';\n\n/** Shader sizing options and uniforms */\nexport {\n defaultObjectSizing,\n defaultPatternSizing,\n ShaderFitOptions,\n type ShaderFit,\n type ShaderSizingParams,\n type ShaderSizingUniforms,\n} from './shader-sizing.js';\n\n/** A flowing interplay of color spots, moving along distinct trajectories and transformed by organic distortion */\nexport {\n meshGradientFragmentShader,\n meshGradientMeta,\n type MeshGradientParams,\n type MeshGradientUniforms,\n} from './shaders/mesh-gradient.js';\n\n/** Radial multi-colored gradient shaped with layered noise for a natural, smoky aesthetic */\nexport {\n smokeRingMeta,\n smokeRingFragmentShader,\n type SmokeRingParams,\n type SmokeRingUniforms,\n} from './shaders/smoke-ring.js';\n\n/** A glowing, web-like structure of fluid lines and soft intersections. Great for creating atmospheric, organic-yet-futuristic visuals */\nexport { neuroNoiseFragmentShader, type NeuroNoiseParams, type NeuroNoiseUniforms } from './shaders/neuro-noise.js';\n\n/** Animated multi-color dots pattern with each dot orbiting around its cell center. Supports up to 40 colors and various shape and motion controls. Great for playful, dynamic backgrounds and UI textures */\nexport {\n dotOrbitMeta,\n dotOrbitFragmentShader,\n type DotOrbitParams,\n type DotOrbitUniforms,\n} from './shaders/dot-orbit.js';\n\n/** Static grid pattern made of circles, diamonds, squares or triangles */\nexport {\n dotGridFragmentShader,\n DotGridShapes,\n type DotGridShape,\n type DotGridParams,\n type DotGridUniforms,\n} from './shaders/dot-grid.js';\n\n/** A multi-color gradient mapped into smooth, animated curves, delivering a sleek, futuristic visual */\nexport {\n simplexNoiseMeta,\n simplexNoiseFragmentShader,\n type SimplexNoiseParams,\n type SimplexNoiseUniforms,\n} from './shaders/simplex-noise.js';\n\n/** Up to 20 gooey blobs moving around the center and merging into smooth organic shapes */\nexport {\n metaballsMeta,\n metaballsFragmentShader,\n type MetaballsParams,\n type MetaballsUniforms,\n} from './shaders/metaballs.js';\n\n/** Animated 3D Perlin noise with exposed controls */\nexport { perlinNoiseFragmentShader, type PerlinNoiseParams, type PerlinNoiseUniforms } from './shaders/perlin-noise.js';\n\n/** Anti-aliased animated Voronoi pattern with smooth and customizable edges */\nexport { voronoiMeta, voronoiFragmentShader, type VoronoiParams, type VoronoiUniforms } from './shaders/voronoi.js';\n\n/** Static line pattern configurable into textures ranging from sharp zigzags to smooth flowing waves */\nexport { wavesFragmentShader, type WavesParams, type WavesUniforms } from './shaders/waves.js';\n\n/** Animated color fields warped by noise and swirls, applied over base patterns (checks, stripes, or split edge). Blends up to 10 colors with adjustable distribution, softness, distortion, and swirl. Great for fluid, smoky, or marbled effects */\nexport {\n warpMeta,\n warpFragmentShader,\n WarpPatterns,\n type WarpParams,\n type WarpUniforms,\n type WarpPattern,\n} from './shaders/warp.js';\n\n/** Animated rays of light radiating from the center, blended with up to 5 colors. The rays are adjustable by size, density, brightness and center glow. Great for dramatic backgrounds, logo reveals, and VFX like energy bursts or sun shafts */\nexport { godRaysMeta, godRaysFragmentShader, type GodRaysParams, type GodRaysUniforms } from './shaders/god-rays.js';\n\n/** A single-colored animated spiral that morphs across a wide range of shapes - from crisp, thin-lined geometry to flowing whirlpool forms and wavy, abstract rings */\nexport { spiralFragmentShader, type SpiralParams, type SpiralUniforms } from './shaders/spiral.js';\n\n/** Animated bands of color twisting and bending, producing spirals, arcs, and flowing circular patterns */\nexport { swirlMeta, swirlFragmentShader, type SwirlParams, type SwirlUniforms } from './shaders/swirl.js';\n\n/** Animated 2-color dithering over with multiple pattern sources (noise, warp, dots, waves, ripple, swirl, sphere) */\nexport {\n ditheringFragmentShader,\n DitheringShapes,\n DitheringTypes,\n type DitheringParams,\n type DitheringUniforms,\n type DitheringShape,\n type DitheringType,\n} from './shaders/dithering.js';\n\n/** Multi-color gradients with grainy, noise-textured distortion available in 7 animated abstract forms */\nexport {\n grainGradientFragmentShader,\n grainGradientMeta,\n GrainGradientShapes,\n type GrainGradientParams,\n type GrainGradientUniforms,\n type GrainGradientShape,\n} from './shaders/grain-gradient.js';\n\n/** Luminous trails of color merging into a glowing Line truncated
"mappings": ";;;;;AACA,SAAS,aAAa,4BAA4B;AAUlD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAGP,SAAS,gCAAgF;AAGzF;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAGP,SAAS,iCAAmF;AAG5F,SAAS,aAAa,6BAAuE;AAG7F,SAAS,2BAAiE;AAG1E;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAGP,SAAS,aAAa,6BAAuE;AAG7F,SAAS,4BAAoE;AAG7E,SAAS,WAAW,2BAAiE;AAGrF;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,OAGK;AAGP,SAAS,2BAAiE;AAG1E;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AAGP;AAAA,EACE;AAAA,OAGK;AAGP,SAAS,8BAA8B,0BAAkF;AAGzH;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAGP,SAAS,gCAAgC;AACzC,SAAS,6BAA6B;AACtC,SAAS,kBAAkB;",
"names": []
}
+6
View File
@@ -0,0 +1,6 @@
export declare const ShaderColorSpaces: {
readonly rgb: 0;
readonly oklch: 1;
};
export type ShaderColorSpace = keyof typeof ShaderColorSpaces;
export declare const declareOklchTransforms = "\n\n// magic numbers (and magic could be better tbh)\n#define OKLCH_CHROMA_THRESHOLD .001\n#define OKLCH_HUE_NEUTRALIZER - 2.\n\nvec3 srgbToLinear(vec3 srgb) {\n return pow(srgb, vec3(2.2));\n}\n\nvec3 linearToSrgb(vec3 linear) {\n return pow(linear, vec3(1.0 / 2.2));\n}\n\nvec3 LrgbToOklab(vec3 rgb) {\n float L = pow(0.4122214708 * rgb.r + 0.5363325363 * rgb.g + 0.0514459929 * rgb.b, 1.0 / 3.0);\n float M = pow(0.2119034982 * rgb.r + 0.6806995451 * rgb.g + 0.1073969566 * rgb.b, 1.0 / 3.0);\n float S = pow(0.0883024619 * rgb.r + 0.2817188376 * rgb.g + 0.6299787005 * rgb.b, 1.0 / 3.0);\n return vec3(\n 0.2104542553 * L + 0.793617785 * M - 0.0040720468 * S,\n 1.9779984951 * L - 2.428592205 * M + 0.4505937099 * S,\n 0.0259040371 * L + 0.7827717662 * M - 0.808675766 * S\n );\n}\n\nvec3 OklabToLrgb(vec3 oklab) {\n float L = oklab.x;\n float a = oklab.y;\n float b = oklab.z;\n\n float l_ = L + 0.3963377774 * a + 0.2158037573 * b;\n float m_ = L - 0.1055613458 * a - 0.0638541728 * b;\n float s_ = L - 0.0894841775 * a - 1.291485548 * b;\n\n float l = l_ * l_ * l_;\n float m = m_ * m_ * m_;\n float s = s_ * s_ * s_;\n\n return vec3(\n 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,\n -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,\n -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s\n );\n}\n\nvec3 oklabToOklch(vec3 oklab) {\n float C = length(oklab.yz);\n float H = atan(oklab.z, oklab.y);\n if (C < OKLCH_CHROMA_THRESHOLD) {\n H = OKLCH_HUE_NEUTRALIZER;\n }\n return vec3(oklab.x, C, H);\n}\n\nvec3 oklchToOklab(vec3 oklch) {\n float a = oklch.y * cos(oklch.z);\n float b = oklch.y * sin(oklch.z);\n return vec3(oklch.x, a, b);\n}\n\nfloat mixHue(float h1, float h2, float mixer) {\n float delta = mod(h2 - h1 + PI, TWO_PI) - PI;\n return h1 + mixer * delta;\n}\n\nvec3 srgbToOklab(vec3 rgb) {\n return oklabToOklch(LrgbToOklab(srgbToLinear(rgb)));\n}\n\nvec3 oklabToSrgb(vec3 oklab) {\n return linearToSrgb(OklabToLrgb(oklchToOklab(oklab)));\n}\n\nvec3 mixOklabVector(vec3 color1, vec3 color2, float mixer) {\n color1.x = mix(color1.x, color2.x, mixer);\n color1.y = mix(color1.y, color2.y, mixer);\n if (color1.y > OKLCH_CHROMA_THRESHOLD && color2.y > OKLCH_CHROMA_THRESHOLD) {\n color1.z = mixHue(color1.z, color2.z, mixer);\n }\n return color1;\n}\n";
+96
View File
@@ -0,0 +1,96 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
const ShaderColorSpaces = {
rgb: 0,
oklch: 1
};
const declareOklchTransforms = `
// magic numbers (and magic could be better tbh)
#define OKLCH_CHROMA_THRESHOLD .001
#define OKLCH_HUE_NEUTRALIZER - 2.
vec3 srgbToLinear(vec3 srgb) {
return pow(srgb, vec3(2.2));
}
vec3 linearToSrgb(vec3 linear) {
return pow(linear, vec3(1.0 / 2.2));
}
vec3 LrgbToOklab(vec3 rgb) {
float L = pow(0.4122214708 * rgb.r + 0.5363325363 * rgb.g + 0.0514459929 * rgb.b, 1.0 / 3.0);
float M = pow(0.2119034982 * rgb.r + 0.6806995451 * rgb.g + 0.1073969566 * rgb.b, 1.0 / 3.0);
float S = pow(0.0883024619 * rgb.r + 0.2817188376 * rgb.g + 0.6299787005 * rgb.b, 1.0 / 3.0);
return vec3(
0.2104542553 * L + 0.793617785 * M - 0.0040720468 * S,
1.9779984951 * L - 2.428592205 * M + 0.4505937099 * S,
0.0259040371 * L + 0.7827717662 * M - 0.808675766 * S
);
}
vec3 OklabToLrgb(vec3 oklab) {
float L = oklab.x;
float a = oklab.y;
float b = oklab.z;
float l_ = L + 0.3963377774 * a + 0.2158037573 * b;
float m_ = L - 0.1055613458 * a - 0.0638541728 * b;
float s_ = L - 0.0894841775 * a - 1.291485548 * b;
float l = l_ * l_ * l_;
float m = m_ * m_ * m_;
float s = s_ * s_ * s_;
return vec3(
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
);
}
vec3 oklabToOklch(vec3 oklab) {
float C = length(oklab.yz);
float H = atan(oklab.z, oklab.y);
if (C < OKLCH_CHROMA_THRESHOLD) {
H = OKLCH_HUE_NEUTRALIZER;
}
return vec3(oklab.x, C, H);
}
vec3 oklchToOklab(vec3 oklch) {
float a = oklch.y * cos(oklch.z);
float b = oklch.y * sin(oklch.z);
return vec3(oklch.x, a, b);
}
float mixHue(float h1, float h2, float mixer) {
float delta = mod(h2 - h1 + PI, TWO_PI) - PI;
return h1 + mixer * delta;
}
vec3 srgbToOklab(vec3 rgb) {
return oklabToOklch(LrgbToOklab(srgbToLinear(rgb)));
}
vec3 oklabToSrgb(vec3 oklab) {
return linearToSrgb(OklabToLrgb(oklchToOklab(oklab)));
}
vec3 mixOklabVector(vec3 color1, vec3 color2, float mixer) {
color1.x = mix(color1.x, color2.x, mixer);
color1.y = mix(color1.y, color2.y, mixer);
if (color1.y > OKLCH_CHROMA_THRESHOLD && color2.y > OKLCH_CHROMA_THRESHOLD) {
color1.z = mixHue(color1.z, color2.z, mixer);
}
return color1;
}
`;
export {
ShaderColorSpaces,
declareOklchTransforms
};
//# sourceMappingURL=shader-color-spaces.js.map
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../src/shader-color-spaces.ts"],
"sourcesContent": ["export const ShaderColorSpaces = {\n rgb: 0,\n oklch: 1,\n} as const;\n\nexport type ShaderColorSpace = keyof typeof ShaderColorSpaces;\n\nexport const declareOklchTransforms = `\n\n// magic numbers (and magic could be better tbh)\n#define OKLCH_CHROMA_THRESHOLD .001\n#define OKLCH_HUE_NEUTRALIZER - 2.\n\nvec3 srgbToLinear(vec3 srgb) {\n return pow(srgb, vec3(2.2));\n}\n\nvec3 linearToSrgb(vec3 linear) {\n return pow(linear, vec3(1.0 / 2.2));\n}\n\nvec3 LrgbToOklab(vec3 rgb) {\n float L = pow(0.4122214708 * rgb.r + 0.5363325363 * rgb.g + 0.0514459929 * rgb.b, 1.0 / 3.0);\n float M = pow(0.2119034982 * rgb.r + 0.6806995451 * rgb.g + 0.1073969566 * rgb.b, 1.0 / 3.0);\n float S = pow(0.0883024619 * rgb.r + 0.2817188376 * rgb.g + 0.6299787005 * rgb.b, 1.0 / 3.0);\n return vec3(\n 0.2104542553 * L + 0.793617785 * M - 0.0040720468 * S,\n 1.9779984951 * L - 2.428592205 * M + 0.4505937099 * S,\n 0.0259040371 * L + 0.7827717662 * M - 0.808675766 * S\n );\n}\n\nvec3 OklabToLrgb(vec3 oklab) {\n float L = oklab.x;\n float a = oklab.y;\n float b = oklab.z;\n\n float l_ = L + 0.3963377774 * a + 0.2158037573 * b;\n float m_ = L - 0.1055613458 * a - 0.0638541728 * b;\n float s_ = L - 0.0894841775 * a - 1.291485548 * b;\n\n float l = l_ * l_ * l_;\n float m = m_ * m_ * m_;\n float s = s_ * s_ * s_;\n\n return vec3(\n 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,\n -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,\n -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s\n );\n}\n\nvec3 oklabToOklch(vec3 oklab) {\n float C = length(oklab.yz);\n float H = atan(oklab.z, oklab.y);\n if (C < OKLCH_CHROMA_THRESHOLD) {\n H = OKLCH_HUE_NEUTRALIZER;\n }\n return vec3(oklab.x, C, H);\n}\n\nvec3 oklchToOklab(vec3 oklch) {\n float a = oklch.y * cos(oklch.z);\n float b = oklch.y * sin(oklch.z);\n return vec3(oklch.x, a, b);\n}\n\nfloat mixHue(float h1, float h2, float mixer) {\n float delta = mod(h2 - h1 + PI, TWO_PI) - PI;\n return h1 + mixer * delta;\n}\n\nvec3 srgbToOklab(vec3 rgb) {\n return oklabToOklch(LrgbToOklab(srgbToLinear(rgb)));\n}\n\nvec3 oklabToSrgb(vec3 oklab) {\n return linearToSrgb(OklabToLrgb(oklchToOklab(oklab)));\n}\n\nvec3 mixOklabVector(vec3 color1, vec3 color2, float mixer) {\n color1.x = mix(color1.x, color2.x, mixer);\n color1.y = mix(color1.y, color2.y, mixer);\n if (color1.y > OKLCH_CHROMA_THRESHOLD && color2.y > OKLCH_CHROMA_THRESHOLD) {\n color1.z = mixHue(color1.z, color2.z, mixer);\n }\n return color1;\n}\n`;\n"],
"mappings": ";;;;;AAAO,MAAM,oBAAoB;AAAA,EAC/B,KAAK;AAAA,EACL,OAAO;AACT;AAIO,MAAM,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;",
"names": []
}
+140
View File
@@ -0,0 +1,140 @@
export declare class ShaderMount {
parentElement: PaperShaderElement;
canvasElement: HTMLCanvasElement;
private gl;
private program;
private uniformLocations;
/** The fragment shader that we are using */
private fragmentShader;
/** Stores the RAF for the render loop */
private rafId;
/** Time of the last rendered frame */
private lastRenderTime;
/** Total time that we have played any animation, passed as a uniform to the shader for time-based VFX */
private currentFrame;
/** The speed that we progress through animation time (multiplies by delta time every update). Allows negatives to play in reverse. If set to 0, rAF will stop entirely so static shaders have no recurring performance costs */
private speed;
/** Actual speed used that accounts for visibility: we pause the shader if the tab is hidden or the element out of the viewport */
private currentSpeed;
/** Uniforms that are provided by the user for the specific shader being mounted (not including uniforms that this Mount adds, like time and resolution) */
private providedUniforms;
/** Names of the uniforms that should have mipmaps generated for them */
private mipmaps;
/** Just a sanity check to make sure frames don't run after we're disposed */
private hasBeenDisposed;
/** If the resolution of the canvas has changed since the last render */
private resolutionChanged;
/** Store textures that are provided by the user */
private textures;
private minPixelRatio;
private maxPixelCount;
private isSafari;
private uniformCache;
private textureUnitMap;
private ownerDocument;
constructor(
/** The div you'd like to mount the shader to. The shader will match its size. */
parentElement: HTMLElement, fragmentShader: string, uniforms: ShaderMountUniforms, webGlContextAttributes?: WebGLContextAttributes,
/** The speed of the animation, or 0 to stop it. Supports negative values to play in reverse. */
speed?: number,
/** Pass a frame to offset the starting u_time value and give deterministic results*/
frame?: number,
/**
* The minimum pixel ratio to render at, defaults to 2.
* May be reduced to improve performance or increased together with `maxPixelCount` to improve antialiasing.
*/
minPixelRatio?: number,
/**
* The maximum amount of physical device pixels to render for the shader,
* by default it's 1920 * 1080 * 2x dpi (per each side) = 8,294,400 pixels of a 4K screen.
* Actual DOM size of the canvas can be larger, it will just lose quality after this.
*
* May be reduced to improve performance or increased to improve quality on high-resolution screens.
*/
maxPixelCount?: number,
/** Names of the uniforms that should have mipmaps generated for them */
mipmaps?: string[]);
private initProgram;
private setupPositionAttribute;
private setupUniforms;
/**
* The scale that we should render at.
* - Used to target 2x rendering even on 1x screens for better antialiasing
* - Prevents the virtual resolution from going beyond the maximum resolution
* - Accounts for the page zoom level so we render in physical device pixels rather than CSS pixels
*/
private renderScale;
private parentWidth;
private parentHeight;
private parentDevicePixelWidth;
private parentDevicePixelHeight;
private devicePixelsSupported;
private intersectionObserver;
private isInViewport;
private resizeObserver;
private setupResizeObserver;
private setupIntersectionObserver;
private handleVisualViewportChange;
/** Resize handler for when the container div changes size or the max pixel count changes and we want to resize our canvas to match */
private handleResize;
private render;
private requestRender;
/** Creates a texture from an image and sets it into a uniform value */
private setTextureUniform;
/** Utility: recursive equality test for all the uniforms */
private areUniformValuesEqual;
/** Sets the provided uniform values into the WebGL program, can be a partial list of uniforms that have changed */
private setUniformValues;
/** Gets the current total animation time from 0ms */
getCurrentFrame: () => number;
/** Set a frame to get a deterministic result, frames are literally just milliseconds from zero since the animation started */
setFrame: (newFrame: number) => void;
/** Set an animation speed (or 0 to stop animation) */
setSpeed: (newSpeed?: number) => void;
/** Apply the target speed, pausing (0) while the tab is hidden or the element is out of the viewport */
private updateCurrentSpeed;
private setCurrentSpeed;
/** Set the maximum pixel count for the shader, this will limit the number of pixels that will be rendered */
setMaxPixelCount: (newMaxPixelCount?: number) => void;
/** Set the minimum pixel ratio for the shader */
setMinPixelRatio: (newMinPixelRatio?: number) => void;
/** Update the uniforms that are provided by the outside shader, can be a partial set with only the uniforms that have changed */
setUniforms: (newUniforms: ShaderMountUniforms) => void;
private handleDocumentVisibilityChange;
/** Dispose of the shader mount, cleaning up all of the WebGL resources */
dispose: () => void;
}
/** The parent `<div>` element that has a ShaderMount available on it */
export interface PaperShaderElement extends HTMLElement {
paperShaderMount: ShaderMount | undefined;
}
/** Check if an element is a Paper shader element */
export declare function isPaperShaderElement(element: HTMLElement): element is PaperShaderElement;
/**
* Uniform types that we support to be auto-mapped into the fragment shader
*
* We accept undefined as a convenience for server rendering, when some things may be undefined
* We just skip setting the uniform if it's undefined. This allows the shader mount to still take up space during server rendering
*/
export interface ShaderMountUniforms {
[key: string]: boolean | number | number[] | number[][] | HTMLImageElement | undefined;
}
export interface ShaderMotionParams {
speed?: number;
frame?: number;
}
export type ShaderPreset<T> = {
name: string;
params: Required<T>;
};
export type ImageShaderPreset<T> = {
name: string;
/**
* Params for the shader excluding the image.
* Image is excluded as it isn't considered a preset,
* e.g. when switching between presets it shouldn't switch the image.
*
* While we exclude images from presets they should still be set with a default prop value so the code-first usage of shaders remains great.
*/
params: Required<Omit<T, 'image'>>;
};
+510
View File
@@ -0,0 +1,510 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
import { vertexShaderSource } from "./vertex-shader.js";
const DEFAULT_MAX_PIXEL_COUNT = 1920 * 1080 * 4;
class ShaderMount {
parentElement;
canvasElement;
gl;
program = null;
uniformLocations = {};
/** The fragment shader that we are using */
fragmentShader;
/** Stores the RAF for the render loop */
rafId = null;
/** Time of the last rendered frame */
lastRenderTime = 0;
/** Total time that we have played any animation, passed as a uniform to the shader for time-based VFX */
currentFrame = 0;
/** The speed that we progress through animation time (multiplies by delta time every update). Allows negatives to play in reverse. If set to 0, rAF will stop entirely so static shaders have no recurring performance costs */
speed = 0;
/** Actual speed used that accounts for visibility: we pause the shader if the tab is hidden or the element out of the viewport */
currentSpeed = 0;
/** Uniforms that are provided by the user for the specific shader being mounted (not including uniforms that this Mount adds, like time and resolution) */
providedUniforms;
/** Names of the uniforms that should have mipmaps generated for them */
mipmaps = [];
/** Just a sanity check to make sure frames don't run after we're disposed */
hasBeenDisposed = false;
/** If the resolution of the canvas has changed since the last render */
resolutionChanged = true;
/** Store textures that are provided by the user */
textures = /* @__PURE__ */ new Map();
minPixelRatio;
maxPixelCount;
isSafari = isSafari();
uniformCache = {};
textureUnitMap = /* @__PURE__ */ new Map();
ownerDocument;
constructor(parentElement, fragmentShader, uniforms, webGlContextAttributes, speed = 0, frame = 0, minPixelRatio = 2, maxPixelCount = DEFAULT_MAX_PIXEL_COUNT, mipmaps = []) {
if (parentElement?.nodeType === 1) {
this.parentElement = parentElement;
} else {
throw new Error("Paper Shaders: parent element must be an HTMLElement");
}
this.ownerDocument = parentElement.ownerDocument;
if (!this.ownerDocument.querySelector("style[data-paper-shader]")) {
const styleElement = this.ownerDocument.createElement("style");
styleElement.innerHTML = defaultStyle;
styleElement.setAttribute("data-paper-shader", "");
this.ownerDocument.head.prepend(styleElement);
}
const canvasElement = this.ownerDocument.createElement("canvas");
this.canvasElement = canvasElement;
this.parentElement.prepend(canvasElement);
this.fragmentShader = fragmentShader;
this.providedUniforms = uniforms;
this.mipmaps = mipmaps;
this.currentFrame = frame;
this.minPixelRatio = minPixelRatio;
this.maxPixelCount = maxPixelCount;
const gl = canvasElement.getContext("webgl2", webGlContextAttributes);
if (!gl) {
throw new Error("Paper Shaders: WebGL is not supported in this browser");
}
this.gl = gl;
this.initProgram();
this.setupPositionAttribute();
this.setupUniforms();
this.setUniformValues(this.providedUniforms);
this.setupResizeObserver();
visualViewport?.addEventListener("resize", this.handleVisualViewportChange);
this.setupIntersectionObserver();
this.setSpeed(speed);
this.parentElement.setAttribute("data-paper-shader", "");
this.parentElement.paperShaderMount = this;
this.ownerDocument.addEventListener("visibilitychange", this.handleDocumentVisibilityChange);
}
initProgram = () => {
const program = createProgram(this.gl, vertexShaderSource, this.fragmentShader);
if (!program) return;
this.program = program;
};
setupPositionAttribute = () => {
const positionAttributeLocation = this.gl.getAttribLocation(this.program, "a_position");
const positionBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, positionBuffer);
const positions = [-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1];
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(positions), this.gl.STATIC_DRAW);
this.gl.enableVertexAttribArray(positionAttributeLocation);
this.gl.vertexAttribPointer(positionAttributeLocation, 2, this.gl.FLOAT, false, 0, 0);
};
setupUniforms = () => {
const uniformLocations = {
u_time: this.gl.getUniformLocation(this.program, "u_time"),
u_pixelRatio: this.gl.getUniformLocation(this.program, "u_pixelRatio"),
u_resolution: this.gl.getUniformLocation(this.program, "u_resolution")
};
Object.entries(this.providedUniforms).forEach(([key, value]) => {
uniformLocations[key] = this.gl.getUniformLocation(this.program, key);
if (value instanceof HTMLImageElement) {
const aspectRatioUniformName = `${key}AspectRatio`;
uniformLocations[aspectRatioUniformName] = this.gl.getUniformLocation(this.program, aspectRatioUniformName);
}
});
this.uniformLocations = uniformLocations;
};
/**
* The scale that we should render at.
* - Used to target 2x rendering even on 1x screens for better antialiasing
* - Prevents the virtual resolution from going beyond the maximum resolution
* - Accounts for the page zoom level so we render in physical device pixels rather than CSS pixels
*/
renderScale = 1;
parentWidth = 0;
parentHeight = 0;
parentDevicePixelWidth = 0;
parentDevicePixelHeight = 0;
devicePixelsSupported = false;
intersectionObserver = null;
isInViewport = true;
resizeObserver = null;
setupResizeObserver = () => {
this.resizeObserver = new ResizeObserver(([entry]) => {
if (entry?.borderBoxSize[0]) {
const physicalPixelSize = entry.devicePixelContentBoxSize?.[0];
if (physicalPixelSize !== void 0) {
this.devicePixelsSupported = true;
this.parentDevicePixelWidth = physicalPixelSize.inlineSize;
this.parentDevicePixelHeight = physicalPixelSize.blockSize;
}
this.parentWidth = entry.borderBoxSize[0].inlineSize;
this.parentHeight = entry.borderBoxSize[0].blockSize;
}
this.handleResize();
});
this.resizeObserver.observe(this.parentElement);
};
setupIntersectionObserver = () => {
const view = this.ownerDocument.defaultView;
if (!view?.IntersectionObserver) return;
this.intersectionObserver = new view.IntersectionObserver(([entry]) => {
this.isInViewport = entry?.isIntersecting ?? true;
this.updateCurrentSpeed();
});
this.intersectionObserver.observe(this.parentElement);
};
// Visual viewport resize handler, mainly used to react to browser zoom changes.
// Resize observer by itself does not react to pinch zoom, and although it usually
// reacts to classic browser zoom, it's not guaranteed in edge cases.
// Since timing between visual viewport changes and resize observer is complex
// and because we'd like to know the device pixel sizes of elements, we just restart
// the observer to get a guaranteed fresh callback regardless if it would have triggered or not.
handleVisualViewportChange = () => {
this.resizeObserver?.disconnect();
this.setupResizeObserver();
};
/** Resize handler for when the container div changes size or the max pixel count changes and we want to resize our canvas to match */
handleResize = () => {
let targetPixelWidth = 0;
let targetPixelHeight = 0;
const dpr = Math.max(1, window.devicePixelRatio);
const pinchZoom = visualViewport?.scale ?? 1;
if (this.devicePixelsSupported) {
const scaleToMeetMinPixelRatio = Math.max(1, this.minPixelRatio / dpr);
targetPixelWidth = this.parentDevicePixelWidth * scaleToMeetMinPixelRatio * pinchZoom;
targetPixelHeight = this.parentDevicePixelHeight * scaleToMeetMinPixelRatio * pinchZoom;
} else {
let targetRenderScale = Math.max(dpr, this.minPixelRatio) * pinchZoom;
if (this.isSafari) {
const zoomLevel = bestGuessBrowserZoom(this.ownerDocument);
targetRenderScale *= Math.max(1, zoomLevel);
}
targetPixelWidth = Math.round(this.parentWidth) * targetRenderScale;
targetPixelHeight = Math.round(this.parentHeight) * targetRenderScale;
}
const maxPixelCountHeadroom = Math.sqrt(this.maxPixelCount) / Math.sqrt(targetPixelWidth * targetPixelHeight);
const scaleToMeetMaxPixelCount = Math.min(1, maxPixelCountHeadroom);
const newWidth = Math.round(targetPixelWidth * scaleToMeetMaxPixelCount);
const newHeight = Math.round(targetPixelHeight * scaleToMeetMaxPixelCount);
const newRenderScale = newWidth / Math.round(this.parentWidth);
if (this.canvasElement.width !== newWidth || this.canvasElement.height !== newHeight || this.renderScale !== newRenderScale) {
this.renderScale = newRenderScale;
this.canvasElement.width = newWidth;
this.canvasElement.height = newHeight;
this.resolutionChanged = true;
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
this.render(performance.now());
}
};
render = (currentTime) => {
if (this.hasBeenDisposed) return;
if (this.program === null) {
console.warn("Tried to render before program or gl was initialized");
return;
}
const dt = currentTime - this.lastRenderTime;
this.lastRenderTime = currentTime;
if (this.currentSpeed !== 0) {
this.currentFrame += dt * this.currentSpeed;
}
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
this.gl.useProgram(this.program);
this.gl.uniform1f(this.uniformLocations.u_time, this.currentFrame * 1e-3);
if (this.resolutionChanged) {
this.gl.uniform2f(this.uniformLocations.u_resolution, this.gl.canvas.width, this.gl.canvas.height);
this.gl.uniform1f(this.uniformLocations.u_pixelRatio, this.renderScale);
this.resolutionChanged = false;
}
this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
if (this.currentSpeed !== 0) {
this.requestRender();
} else {
this.rafId = null;
}
};
requestRender = () => {
if (this.rafId !== null) {
cancelAnimationFrame(this.rafId);
}
this.rafId = requestAnimationFrame(this.render);
};
/** Creates a texture from an image and sets it into a uniform value */
setTextureUniform = (uniformName, image) => {
if (!image.complete || image.naturalWidth === 0) {
throw new Error(`Paper Shaders: image for uniform ${uniformName} must be fully loaded`);
}
const existingTexture = this.textures.get(uniformName);
if (existingTexture) {
this.gl.deleteTexture(existingTexture);
}
if (!this.textureUnitMap.has(uniformName)) {
this.textureUnitMap.set(uniformName, this.textureUnitMap.size);
}
const textureUnit = this.textureUnitMap.get(uniformName);
this.gl.activeTexture(this.gl.TEXTURE0 + textureUnit);
const texture = this.gl.createTexture();
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, image);
if (this.mipmaps.includes(uniformName)) {
this.gl.generateMipmap(this.gl.TEXTURE_2D);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR_MIPMAP_LINEAR);
}
const error = this.gl.getError();
if (error !== this.gl.NO_ERROR || texture === null) {
console.error("Paper Shaders: WebGL error when uploading texture:", error);
return;
}
this.textures.set(uniformName, texture);
const location = this.uniformLocations[uniformName];
if (location) {
this.gl.uniform1i(location, textureUnit);
const aspectRatioUniformName = `${uniformName}AspectRatio`;
const aspectRatioLocation = this.uniformLocations[aspectRatioUniformName];
if (aspectRatioLocation) {
const aspectRatio = image.naturalWidth / image.naturalHeight;
this.gl.uniform1f(aspectRatioLocation, aspectRatio);
}
}
};
/** Utility: recursive equality test for all the uniforms */
areUniformValuesEqual = (a, b) => {
if (a === b) return true;
if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
return a.every((val, i) => this.areUniformValuesEqual(val, b[i]));
}
return false;
};
/** Sets the provided uniform values into the WebGL program, can be a partial list of uniforms that have changed */
setUniformValues = (updatedUniforms) => {
this.gl.useProgram(this.program);
Object.entries(updatedUniforms).forEach(([key, value]) => {
let cacheValue = value;
if (value instanceof HTMLImageElement) {
cacheValue = `${value.src.slice(0, 200)}|${value.naturalWidth}x${value.naturalHeight}`;
}
if (this.areUniformValuesEqual(this.uniformCache[key], cacheValue)) return;
this.uniformCache[key] = cacheValue;
const location = this.uniformLocations[key];
if (!location) {
console.warn(`Uniform location for ${key} not found`);
return;
}
if (value instanceof HTMLImageElement) {
this.setTextureUniform(key, value);
} else if (Array.isArray(value)) {
let flatArray = null;
let valueLength = null;
if (value[0] !== void 0 && Array.isArray(value[0])) {
const firstChildLength = value[0].length;
if (value.every((arr) => arr.length === firstChildLength)) {
flatArray = value.flat();
valueLength = firstChildLength;
} else {
console.warn(`All child arrays must be the same length for ${key}`);
return;
}
} else {
flatArray = value;
valueLength = flatArray.length;
}
switch (valueLength) {
case 2:
this.gl.uniform2fv(location, flatArray);
break;
case 3:
this.gl.uniform3fv(location, flatArray);
break;
case 4:
this.gl.uniform4fv(location, flatArray);
break;
case 9:
this.gl.uniformMatrix3fv(location, false, flatArray);
break;
case 16:
this.gl.uniformMatrix4fv(location, false, flatArray);
break;
default:
console.warn(`Unsupported uniform array length: ${valueLength}`);
}
} else if (typeof value === "number") {
this.gl.uniform1f(location, value);
} else if (typeof value === "boolean") {
this.gl.uniform1i(location, value ? 1 : 0);
} else {
console.warn(`Unsupported uniform type for ${key}: ${typeof value}`);
}
});
};
/** Gets the current total animation time from 0ms */
getCurrentFrame = () => {
return this.currentFrame;
};
/** Set a frame to get a deterministic result, frames are literally just milliseconds from zero since the animation started */
setFrame = (newFrame) => {
this.currentFrame = newFrame;
this.lastRenderTime = performance.now();
this.render(performance.now());
};
/** Set an animation speed (or 0 to stop animation) */
setSpeed = (newSpeed = 1) => {
this.speed = newSpeed;
this.updateCurrentSpeed();
};
/** Apply the target speed, pausing (0) while the tab is hidden or the element is out of the viewport */
updateCurrentSpeed = () => {
this.setCurrentSpeed(this.ownerDocument.hidden || !this.isInViewport ? 0 : this.speed);
};
setCurrentSpeed = (newSpeed) => {
this.currentSpeed = newSpeed;
if (this.rafId === null && newSpeed !== 0) {
this.lastRenderTime = performance.now();
this.rafId = requestAnimationFrame(this.render);
}
if (this.rafId !== null && newSpeed === 0) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
};
/** Set the maximum pixel count for the shader, this will limit the number of pixels that will be rendered */
setMaxPixelCount = (newMaxPixelCount = DEFAULT_MAX_PIXEL_COUNT) => {
this.maxPixelCount = newMaxPixelCount;
this.handleResize();
};
/** Set the minimum pixel ratio for the shader */
setMinPixelRatio = (newMinPixelRatio = 2) => {
this.minPixelRatio = newMinPixelRatio;
this.handleResize();
};
/** Update the uniforms that are provided by the outside shader, can be a partial set with only the uniforms that have changed */
setUniforms = (newUniforms) => {
this.setUniformValues(newUniforms);
this.providedUniforms = { ...this.providedUniforms, ...newUniforms };
this.render(performance.now());
};
handleDocumentVisibilityChange = () => {
this.updateCurrentSpeed();
};
/** Dispose of the shader mount, cleaning up all of the WebGL resources */
dispose = () => {
this.hasBeenDisposed = true;
if (this.rafId !== null) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
if (this.gl && this.program) {
this.textures.forEach((texture) => {
this.gl.deleteTexture(texture);
});
this.textures.clear();
this.gl.deleteProgram(this.program);
this.program = null;
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, null);
this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, null);
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
this.gl.getError();
}
if (this.resizeObserver) {
this.resizeObserver.disconnect();
this.resizeObserver = null;
}
if (this.intersectionObserver) {
this.intersectionObserver.disconnect();
this.intersectionObserver = null;
}
visualViewport?.removeEventListener("resize", this.handleVisualViewportChange);
this.ownerDocument.removeEventListener("visibilitychange", this.handleDocumentVisibilityChange);
this.uniformLocations = {};
this.canvasElement.remove();
delete this.parentElement.paperShaderMount;
};
}
function createShader(gl, type, source) {
const shader = gl.createShader(type);
if (!shader) return null;
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
function createProgram(gl, vertexShaderSource2, fragmentShaderSource) {
const format = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT);
const precision = format ? format.precision : null;
if (precision && precision < 23) {
vertexShaderSource2 = vertexShaderSource2.replace(/precision\s+(lowp|mediump)\s+float;/g, "precision highp float;");
fragmentShaderSource = fragmentShaderSource.replace(/precision\s+(lowp|mediump)\s+float/g, "precision highp float").replace(/\b(uniform|varying|attribute)\s+(lowp|mediump)\s+(\w+)/g, "$1 highp $3");
}
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource2);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
if (!vertexShader || !fragmentShader) return null;
const program = gl.createProgram();
if (!program) return null;
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error("Unable to initialize the shader program: " + gl.getProgramInfoLog(program));
gl.deleteProgram(program);
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
return null;
}
gl.detachShader(program, vertexShader);
gl.detachShader(program, fragmentShader);
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
return program;
}
const defaultStyle = `@layer paper-shaders {
:where([data-paper-shader]) {
isolation: isolate;
position: relative;
& canvas {
contain: strict;
display: block;
position: absolute;
inset: 0;
z-index: -1;
width: 100%;
height: 100%;
border-radius: inherit;
corner-shape: inherit;
}
}
}`;
function isPaperShaderElement(element) {
return "paperShaderMount" in element;
}
function isSafari() {
const ua = navigator.userAgent.toLowerCase();
return ua.includes("safari") && !ua.includes("chrome") && !ua.includes("android");
}
function bestGuessBrowserZoom(doc) {
const viewportScale = visualViewport?.scale ?? 1;
const viewportWidth = visualViewport?.width ?? window.innerWidth;
const scrollbarWidth = window.innerWidth - doc.documentElement.clientWidth;
const innerWidth = viewportScale * viewportWidth + scrollbarWidth;
const ratio = outerWidth / innerWidth;
const zoomPercentageRounded = Math.round(100 * ratio);
if (zoomPercentageRounded % 5 === 0) {
return zoomPercentageRounded / 100;
}
if (zoomPercentageRounded === 33) {
return 1 / 3;
}
if (zoomPercentageRounded === 67) {
return 2 / 3;
}
if (zoomPercentageRounded === 133) {
return 4 / 3;
}
return ratio;
}
export {
ShaderMount,
isPaperShaderElement
};
//# sourceMappingURL=shader-mount.js.map
+7
View File
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../src/shader-mount.ts"],
"sourcesContent": ["import { vertexShaderSource } from './vertex-shader.js';\n\nconst DEFAULT_MAX_PIXEL_COUNT: number = 1920 * 1080 * 4;\n\nexport class ShaderMount {\n public parentElement: PaperShaderElement;\n public canvasElement: HTMLCanvasElement;\n private gl: WebGL2RenderingContext;\n private program: WebGLProgram | null = null;\n private uniformLocations: Record<string, WebGLUniformLocation | null> = {};\n /** The fragment shader that we are using */\n private fragmentShader: string;\n /** Stores the RAF for the render loop */\n private rafId: number | null = null;\n /** Time of the last rendered frame */\n private lastRenderTime = 0;\n /** Total time that we have played any animation, passed as a uniform to the shader for time-based VFX */\n private currentFrame = 0;\n /** The speed that we progress through animation time (multiplies by delta time every update). Allows negatives to play in reverse. If set to 0, rAF will stop entirely so static shaders have no recurring performance costs */\n private speed = 0;\n /** Actual speed used that accounts for visibility: we pause the shader if the tab is hidden or the element out of the viewport */\n private currentSpeed = 0;\n /** Uniforms that are provided by the user for the specific shader being mounted (not including uniforms that this Mount adds, like time and resolution) */\n private providedUniforms: ShaderMountUniforms;\n /** Names of the uniforms that should have mipmaps generated for them */\n private mipmaps: string[] = [];\n /** Just a sanity check to make sure frames don't run after we're disposed */\n private hasBeenDisposed = false;\n /** If the resolution of the canvas has changed since the last render */\n private resolutionChanged = true;\n /** Store textures that are provided by the user */\n private textures: Map<string, WebGLTexture> = new Map();\n private minPixelRatio;\n private maxPixelCount;\n private isSafari = isSafari();\n private uniformCache: Record<string, unknown> = {};\n private textureUnitMap: Map<string, number> = new Map();\n private ownerDocument: Document;\n\n constructor(\n /** The div you'd like to mount the shader to. The shader will match its size. */\n parentElement: HTMLElement,\n fragmentShader: string,\n uniforms: ShaderMountUniforms,\n webGlContextAttributes?: WebGLContextAttributes,\n /** The speed of the animation, or 0 to stop it. Supports negative values to play in reverse. */\n speed = 0,\n /** Pass a frame to offset the starting u_time value and give deterministic results*/\n frame = 0,\n /**\n * The minimum pixel ratio to render at, defaults to 2.\n * May be reduced to improve performance or increased together with `maxPixelCount` to improve antialiasing.\n */\n minPixelRatio = 2,\n /**\n * The maximum amount of physical device pixels to render for the shader,\n * by default it's 1920 * 1080 * 2x dpi (per each side) = 8,294,400 pixels of a 4K screen.\n * Actual DOM size of the canvas can be larger, it will just lose quality after this.\n *\n * May be reduced to improve performance or increased to improve quality on high-resolution screens.\n */\n maxPixelCount: number = DEFAULT_MAX_PIXEL_COUNT,\n /** Names of the uniforms that should have mipmaps generated for them */\n mipmaps: string[] = []\n ) {\n // nodeType check instead of `instanceof` to work across document boundaries (iframes, PiP windows)\n if (parentElement?.nodeType === 1) {\n this.parentElement = parentElement as PaperShaderElement;\n } else {\n throw new Error('Paper Shaders: parent element must be an HTMLElement');\n }\n\n this.ownerDocument = parentElement.ownerDocument;\n\n if (!this.ownerDocument.querySelector('style[data-paper-shader]')) {\n const styleElement = this.ownerDocument.createElement('style');\n styleElement.innerHTML = defaultStyle;\n styleElement.setAttribute('data-paper-shader', '');\n this.ownerDocument.head.prepend(styleElement);\n }\n\n // Create the canvas element and mount it into the provided element\n const canvasElement = this.ownerDocument.createElement('canvas');\n this.canvasElement = canvasElement;\n this.parentElement.prepend(canvasElement);\n this.fragmentShader = fragmentShader;\n this.providedUniforms = uniforms;\n this.mipmaps = mipmaps;\n // Base our starting animation time on the provided frame value\n this.currentFrame = frame;\n this.minPixelRatio = minPixelRatio;\n this.maxPixelCount = maxPixelCount;\n\n const gl = canvasElement.getContext('webgl2', webGlContextAttributes);\n if (!gl) {\n throw new Error('Paper Shaders: WebGL is not supported in this browser');\n }\n this.gl = gl;\n\n this.initProgram();\n this.setupPositionAttribute();\n // Grab the locations of the uniforms in the fragment shader\n this.setupUniforms();\n // Put the user provided values into the unLine truncated
"mappings": "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 truncated
"names": ["vertexShaderSource"]
}
+30
View File
@@ -0,0 +1,30 @@
export interface ShaderSizingUniforms {
u_fit: (typeof ShaderFitOptions)[ShaderFit];
u_scale: number;
u_rotation: number;
u_originX: number;
u_originY: number;
u_offsetX: number;
u_offsetY: number;
u_worldWidth: number;
u_worldHeight: number;
}
export interface ShaderSizingParams {
fit?: 'none' | 'contain' | 'cover';
scale?: number;
rotation?: number;
originX?: number;
originY?: number;
offsetX?: number;
offsetY?: number;
worldWidth?: number;
worldHeight?: number;
}
export declare const defaultObjectSizing: Required<ShaderSizingParams>;
export declare const defaultPatternSizing: Required<ShaderSizingParams>;
export declare const ShaderFitOptions: {
readonly none: 0;
readonly contain: 1;
readonly cover: 2;
};
export type ShaderFit = keyof typeof ShaderFitOptions;
+38
View File
@@ -0,0 +1,38 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
const defaultObjectSizing = {
fit: "contain",
scale: 1,
rotation: 0,
offsetX: 0,
offsetY: 0,
originX: 0.5,
originY: 0.5,
worldWidth: 0,
worldHeight: 0
};
const defaultPatternSizing = {
fit: "none",
scale: 1,
rotation: 0,
offsetX: 0,
offsetY: 0,
originX: 0.5,
originY: 0.5,
worldWidth: 0,
worldHeight: 0
};
const ShaderFitOptions = {
none: 0,
contain: 1,
cover: 2
};
export {
ShaderFitOptions,
defaultObjectSizing,
defaultPatternSizing
};
//# sourceMappingURL=shader-sizing.js.map
+7
View File
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../src/shader-sizing.ts"],
"sourcesContent": ["export interface ShaderSizingUniforms {\n u_fit: (typeof ShaderFitOptions)[ShaderFit];\n u_scale: number;\n u_rotation: number;\n u_originX: number;\n u_originY: number;\n u_offsetX: number;\n u_offsetY: number;\n u_worldWidth: number;\n u_worldHeight: number;\n}\n\nexport interface ShaderSizingParams {\n fit?: 'none' | 'contain' | 'cover';\n scale?: number;\n rotation?: number;\n originX?: number;\n originY?: number;\n offsetX?: number;\n offsetY?: number;\n worldWidth?: number;\n worldHeight?: number;\n}\n\nexport const defaultObjectSizing: Required<ShaderSizingParams> = {\n fit: 'contain',\n scale: 1,\n rotation: 0,\n offsetX: 0,\n offsetY: 0,\n originX: 0.5,\n originY: 0.5,\n worldWidth: 0,\n worldHeight: 0,\n};\n\nexport const defaultPatternSizing: Required<ShaderSizingParams> = {\n fit: 'none',\n scale: 1,\n rotation: 0,\n offsetX: 0,\n offsetY: 0,\n originX: 0.5,\n originY: 0.5,\n worldWidth: 0,\n worldHeight: 0,\n};\n\nexport const ShaderFitOptions = {\n none: 0,\n contain: 1,\n cover: 2,\n} as const;\n\nexport type ShaderFit = keyof typeof ShaderFitOptions;\n"],
"mappings": ";;;;;AAwBO,MAAM,sBAAoD;AAAA,EAC/D,KAAK;AAAA,EACL,OAAO;AAAA,EACP,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,aAAa;AACf;AAEO,MAAM,uBAAqD;AAAA,EAChE,KAAK;AAAA,EACL,OAAO;AAAA,EACP,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,aAAa;AACf;AAEO,MAAM,mBAAmB;AAAA,EAC9B,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AACT;",
"names": []
}
+10
View File
@@ -0,0 +1,10 @@
export declare const declarePI = "\n#define TWO_PI 6.28318530718\n#define PI 3.14159265358979323846\n";
export declare const rotation2 = "\nvec2 rotate(vec2 uv, float th) {\n return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv;\n}\n";
export declare const proceduralHash11 = "\n float hash11(float p) {\n p = fract(p * 0.3183099) + 0.1;\n p *= p + 19.19;\n return fract(p * p);\n }\n";
export declare const proceduralHash21 = "\n float hash21(vec2 p) {\n p = fract(p * vec2(0.3183099, 0.3678794)) + 0.1;\n p += dot(p, p + 19.19);\n return fract(p.x * p.y);\n }\n";
export declare const proceduralHash22 = "\n vec2 hash22(vec2 p) {\n p = fract(p * vec2(0.3183099, 0.3678794)) + 0.1;\n p += dot(p, p.yx + 19.19);\n return fract(vec2(p.x * p.y, p.x + p.y));\n }\n";
export declare const textureRandomizerR = "\n float randomR(vec2 p) {\n vec2 uv = floor(p) / 100. + .5;\n return texture(u_noiseTexture, fract(uv)).r;\n }\n";
export declare const textureRandomizerGB = "\n vec2 randomGB(vec2 p) {\n vec2 uv = floor(p) / 100. + .5;\n return texture(u_noiseTexture, fract(uv)).gb;\n }\n";
export declare const colorBandingFix = "\n color += 1. / 256. * (fract(sin(dot(.014 * gl_FragCoord.xy, vec2(12.9898, 78.233))) * 43758.5453123) - .5);\n";
export declare const simplexNoise = "\nvec3 permute(vec3 x) { return mod(((x * 34.0) + 1.0) * x, 289.0); }\nfloat snoise(vec2 v) {\n const vec4 C = vec4(0.211324865405187, 0.366025403784439,\n -0.577350269189626, 0.024390243902439);\n vec2 i = floor(v + dot(v, C.yy));\n vec2 x0 = v - i + dot(i, C.xx);\n vec2 i1;\n i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n vec4 x12 = x0.xyxy + C.xxzz;\n x12.xy -= i1;\n i = mod(i, 289.0);\n vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0))\n + i.x + vec3(0.0, i1.x, 1.0));\n vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy),\n dot(x12.zw, x12.zw)), 0.0);\n m = m * m;\n m = m * m;\n vec3 x = 2.0 * fract(p * C.www) - 1.0;\n vec3 h = abs(x) - 0.5;\n vec3 ox = floor(x + 0.5);\n vec3 a0 = x - ox;\n m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);\n vec3 g;\n g.x = a0.x * x0.x + h.x * x0.y;\n g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n return 130.0 * dot(m, g);\n}\n";
export declare const fiberNoise = "\nfloat fiberRandom(vec2 p) {\n vec2 uv = floor(p) / 100.;\n return texture(u_noiseTexture, fract(uv)).b;\n}\n\nfloat fiberValueNoise(vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = fiberRandom(i);\n float b = fiberRandom(i + vec2(1.0, 0.0));\n float c = fiberRandom(i + vec2(0.0, 1.0));\n float d = fiberRandom(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n float x1 = mix(a, b, u.x);\n float x2 = mix(c, d, u.x);\n return mix(x1, x2, u.y);\n}\n\nfloat fiberNoiseFbm(in vec2 n, vec2 seedOffset) {\n float total = 0.0, amplitude = 1.;\n for (int i = 0; i < 4; i++) {\n n = rotate(n, .7);\n total += fiberValueNoise(n + seedOffset) * amplitude;\n n *= 2.;\n amplitude *= 0.6;\n }\n return total;\n}\n\nfloat fiberNoise(vec2 uv, vec2 seedOffset) {\n float epsilon = 0.001;\n float n1 = fiberNoiseFbm(uv + vec2(epsilon, 0.0), seedOffset);\n float n2 = fiberNoiseFbm(uv - vec2(epsilon, 0.0), seedOffset);\n float n3 = fiberNoiseFbm(uv + vec2(0.0, epsilon), seedOffset);\n float n4 = fiberNoiseFbm(uv - vec2(0.0, epsilon), seedOffset);\n return length(vec2(n1 - n2, n3 - n4)) / (2.0 * epsilon);\n}\n";
+131
View File
@@ -0,0 +1,131 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
const declarePI = `
#define TWO_PI 6.28318530718
#define PI 3.14159265358979323846
`;
const rotation2 = `
vec2 rotate(vec2 uv, float th) {
return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv;
}
`;
const proceduralHash11 = `
float hash11(float p) {
p = fract(p * 0.3183099) + 0.1;
p *= p + 19.19;
return fract(p * p);
}
`;
const proceduralHash21 = `
float hash21(vec2 p) {
p = fract(p * vec2(0.3183099, 0.3678794)) + 0.1;
p += dot(p, p + 19.19);
return fract(p.x * p.y);
}
`;
const proceduralHash22 = `
vec2 hash22(vec2 p) {
p = fract(p * vec2(0.3183099, 0.3678794)) + 0.1;
p += dot(p, p.yx + 19.19);
return fract(vec2(p.x * p.y, p.x + p.y));
}
`;
const textureRandomizerR = `
float randomR(vec2 p) {
vec2 uv = floor(p) / 100. + .5;
return texture(u_noiseTexture, fract(uv)).r;
}
`;
const textureRandomizerGB = `
vec2 randomGB(vec2 p) {
vec2 uv = floor(p) / 100. + .5;
return texture(u_noiseTexture, fract(uv)).gb;
}
`;
const colorBandingFix = `
color += 1. / 256. * (fract(sin(dot(.014 * gl_FragCoord.xy, vec2(12.9898, 78.233))) * 43758.5453123) - .5);
`;
const simplexNoise = `
vec3 permute(vec3 x) { return mod(((x * 34.0) + 1.0) * x, 289.0); }
float snoise(vec2 v) {
const vec4 C = vec4(0.211324865405187, 0.366025403784439,
-0.577350269189626, 0.024390243902439);
vec2 i = floor(v + dot(v, C.yy));
vec2 x0 = v - i + dot(i, C.xx);
vec2 i1;
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
i = mod(i, 289.0);
vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0))
+ i.x + vec3(0.0, i1.x, 1.0));
vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy),
dot(x12.zw, x12.zw)), 0.0);
m = m * m;
m = m * m;
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 ox = floor(x + 0.5);
vec3 a0 = x - ox;
m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);
vec3 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);
}
`;
const fiberNoise = `
float fiberRandom(vec2 p) {
vec2 uv = floor(p) / 100.;
return texture(u_noiseTexture, fract(uv)).b;
}
float fiberValueNoise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = fiberRandom(i);
float b = fiberRandom(i + vec2(1.0, 0.0));
float c = fiberRandom(i + vec2(0.0, 1.0));
float d = fiberRandom(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
float x1 = mix(a, b, u.x);
float x2 = mix(c, d, u.x);
return mix(x1, x2, u.y);
}
float fiberNoiseFbm(in vec2 n, vec2 seedOffset) {
float total = 0.0, amplitude = 1.;
for (int i = 0; i < 4; i++) {
n = rotate(n, .7);
total += fiberValueNoise(n + seedOffset) * amplitude;
n *= 2.;
amplitude *= 0.6;
}
return total;
}
float fiberNoise(vec2 uv, vec2 seedOffset) {
float epsilon = 0.001;
float n1 = fiberNoiseFbm(uv + vec2(epsilon, 0.0), seedOffset);
float n2 = fiberNoiseFbm(uv - vec2(epsilon, 0.0), seedOffset);
float n3 = fiberNoiseFbm(uv + vec2(0.0, epsilon), seedOffset);
float n4 = fiberNoiseFbm(uv - vec2(0.0, epsilon), seedOffset);
return length(vec2(n1 - n2, n3 - n4)) / (2.0 * epsilon);
}
`;
export {
colorBandingFix,
declarePI,
fiberNoise,
proceduralHash11,
proceduralHash21,
proceduralHash22,
rotation2,
simplexNoise,
textureRandomizerGB,
textureRandomizerR
};
//# sourceMappingURL=shader-utils.js.map
+7
View File
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../src/shader-utils.ts"],
"sourcesContent": ["// language=GLSL\nexport const declarePI = `\n#define TWO_PI 6.28318530718\n#define PI 3.14159265358979323846\n`;\n\n// language=GLSL\nexport const rotation2 = `\nvec2 rotate(vec2 uv, float th) {\n return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv;\n}\n`;\n\n// language=GLSL\nexport const proceduralHash11 = `\n float hash11(float p) {\n p = fract(p * 0.3183099) + 0.1;\n p *= p + 19.19;\n return fract(p * p);\n }\n`;\n\n// language=GLSL\nexport const proceduralHash21 = `\n float hash21(vec2 p) {\n p = fract(p * vec2(0.3183099, 0.3678794)) + 0.1;\n p += dot(p, p + 19.19);\n return fract(p.x * p.y);\n }\n`;\n\n// language=GLSL\nexport const proceduralHash22 = `\n vec2 hash22(vec2 p) {\n p = fract(p * vec2(0.3183099, 0.3678794)) + 0.1;\n p += dot(p, p.yx + 19.19);\n return fract(vec2(p.x * p.y, p.x + p.y));\n }\n`;\n\n// language=GLSL\nexport const textureRandomizerR = `\n float randomR(vec2 p) {\n vec2 uv = floor(p) / 100. + .5;\n return texture(u_noiseTexture, fract(uv)).r;\n }\n`;\n\n// language=GLSL\nexport const textureRandomizerGB = `\n vec2 randomGB(vec2 p) {\n vec2 uv = floor(p) / 100. + .5;\n return texture(u_noiseTexture, fract(uv)).gb;\n }\n`;\n\n// language=GLSL\nexport const colorBandingFix = `\n color += 1. / 256. * (fract(sin(dot(.014 * gl_FragCoord.xy, vec2(12.9898, 78.233))) * 43758.5453123) - .5);\n`;\n\n// language=GLSL\nexport const simplexNoise = `\nvec3 permute(vec3 x) { return mod(((x * 34.0) + 1.0) * x, 289.0); }\nfloat snoise(vec2 v) {\n const vec4 C = vec4(0.211324865405187, 0.366025403784439,\n -0.577350269189626, 0.024390243902439);\n vec2 i = floor(v + dot(v, C.yy));\n vec2 x0 = v - i + dot(i, C.xx);\n vec2 i1;\n i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n vec4 x12 = x0.xyxy + C.xxzz;\n x12.xy -= i1;\n i = mod(i, 289.0);\n vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0))\n + i.x + vec3(0.0, i1.x, 1.0));\n vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy),\n dot(x12.zw, x12.zw)), 0.0);\n m = m * m;\n m = m * m;\n vec3 x = 2.0 * fract(p * C.www) - 1.0;\n vec3 h = abs(x) - 0.5;\n vec3 ox = floor(x + 0.5);\n vec3 a0 = x - ox;\n m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);\n vec3 g;\n g.x = a0.x * x0.x + h.x * x0.y;\n g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n return 130.0 * dot(m, g);\n}\n`;\n\n// language=GLSL\nexport const fiberNoise = `\nfloat fiberRandom(vec2 p) {\n vec2 uv = floor(p) / 100.;\n return texture(u_noiseTexture, fract(uv)).b;\n}\n\nfloat fiberValueNoise(vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = fiberRandom(i);\n float b = fiberRandom(i + vec2(1.0, 0.0));\n float c = fiberRandom(i + vec2(0.0, 1.0));\n float d = fiberRandom(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n float x1 = mix(a, b, u.x);\n float x2 = mix(c, d, u.x);\n return mix(x1, x2, u.y);\n}\n\nfloat fiberNoiseFbm(in vec2 n, vec2 seedOffset) {\n float total = 0.0, amplitude = 1.;\n for (int i = 0; i < 4; i++) {\n n = rotate(n, .7);\n total += fiberValueNoise(n + seedOffset) * amplitude;\n n *= 2.;\n amplitude *= 0.6;\n }\n return total;\n}\n\nfloat fiberNoise(vec2 uv, vec2 seedOffset) {\n float epsilon = 0.001;\n float n1 = fiberNoiseFbm(uv + vec2(epsilon, 0.0), seedOffset);\n float n2 = fiberNoiseFbm(uv - vec2(epsilon, 0.0), seedOffset);\n float n3 = fiberNoiseFbm(uv + vec2(0.0, epsilon), seedOffset);\n float n4 = fiberNoiseFbm(uv - vec2(0.0, epsilon), seedOffset);\n return length(vec2(n1 - n2, n3 - n4)) / (2.0 * epsilon);\n}\n`;\n"],
"mappings": ";;;;;AACO,MAAM,YAAY;AAAA;AAAA;AAAA;AAMlB,MAAM,YAAY;AAAA;AAAA;AAAA;AAAA;AAOlB,MAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASzB,MAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASzB,MAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASzB,MAAM,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAQ3B,MAAM,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAQ5B,MAAM,kBAAkB;AAAA;AAAA;AAKxB,MAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+BrB,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;",
"names": []
}
+70
View File
@@ -0,0 +1,70 @@
import type { vec4 } from '../types.js';
import type { ShaderMotionParams } from '../shader-mount.js';
import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';
export declare const colorPanelsMeta: {
readonly maxColorCount: 7;
};
/**
* Pseudo-3D semi-transparent panels rotating around a central axis
*
* Fragment shader uniforms:
* - u_time (float): Animation time
* - u_scale (float): Overall zoom level, used for anti-aliasing calculations
* - u_colors (vec4[]): Up to 7 RGBA colors used to color the panels
* - u_colorsCount (float): Number of active colors
* - u_colorBack (vec4): Background color in RGBA
* - u_density (float): Angle between every 2 panels (0.25 to 7)
* - u_angle1 (float): Skew angle applied to all panes (-1 to 1)
* - u_angle2 (float): Skew angle applied to all panes (-1 to 1)
* - u_length (float): Panel length relative to total height (0 to 3)
* - u_edges (bool): Color highlight on the panels edges
* - u_blur (float): Side blur, 0 for sharp edges (0 to 0.5)
* - u_fadeIn (float): Transparency near central axis (0 to 1)
* - u_fadeOut (float): Transparency near viewer (0 to 1)
* - u_gradient (float): Color mixing within a panel, 0 = solid, 1 = gradient (0 to 1)
*
* Vertex shader outputs (used in fragment shader):
* - v_objectUV (vec2): Object box UV coordinates with global sizing (scale, rotation, offsets, etc) applied
*
* Vertex shader uniforms:
* - u_resolution (vec2): Canvas resolution in pixels
* - u_pixelRatio (float): Device pixel ratio
* - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)
* - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)
* - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas
* - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas
* - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)
* - u_scale (float): Overall zoom level of the graphics (0.01 to 4)
* - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
* - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)
* - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)
*
*/
export declare const colorPanelsFragmentShader: string;
export interface ColorPanelsUniforms extends ShaderSizingUniforms {
u_colors: vec4[];
u_colorsCount: number;
u_colorBack: [number, number, number, number];
u_angle1: number;
u_angle2: number;
u_length: number;
u_edges: boolean;
u_blur: number;
u_fadeIn: number;
u_fadeOut: number;
u_density: number;
u_gradient: number;
}
export interface ColorPanelsParams extends ShaderSizingParams, ShaderMotionParams {
colors?: string[];
colorBack?: string;
angle1?: number;
angle2?: number;
length?: number;
edges?: boolean;
blur?: number;
fadeIn?: number;
fadeOut?: number;
density?: number;
gradient?: number;
}
+224
View File
@@ -0,0 +1,224 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
import {} from "../shader-sizing.js";
import { declarePI, colorBandingFix } from "../shader-utils.js";
const colorPanelsMeta = {
maxColorCount: 7
};
const colorPanelsFragmentShader = `#version 300 es
precision lowp float;
uniform float u_time;
uniform mediump float u_scale;
uniform vec4 u_colors[${colorPanelsMeta.maxColorCount}];
uniform float u_colorsCount;
uniform vec4 u_colorBack;
uniform float u_density;
uniform float u_angle1;
uniform float u_angle2;
uniform float u_length;
uniform bool u_edges;
uniform float u_blur;
uniform float u_fadeIn;
uniform float u_fadeOut;
uniform float u_gradient;
in vec2 v_objectUV;
out vec4 fragColor;
${declarePI}
const float zLimit = .5;
vec2 getPanel(float angle, vec2 uv, float invLength, float aa) {
float sinA = sin(angle);
float cosA = cos(angle);
float denom = sinA - uv.y * cosA;
if (abs(denom) < .01) return vec2(0.);
float z = uv.y / denom;
if (z <= 0. || z > zLimit) return vec2(0.);
float zRatio = z / zLimit;
float panelMap = 1. - zRatio;
float x = uv.x * (cosA * z + 1.) * invLength;
float zOffset = zRatio - .5;
float left = -.5 + zOffset * u_angle1;
float right = .5 - zOffset * u_angle2;
float blurX = aa + 2. * panelMap * u_blur;
float leftEdge1 = left - blurX;
float leftEdge2 = left + .25 * blurX;
float rightEdge1 = right - .25 * blurX;
float rightEdge2 = right + blurX;
float panel = smoothstep(leftEdge1, leftEdge2, x) * (1.0 - smoothstep(rightEdge1, rightEdge2, x));
panel *= mix(0., panel, smoothstep(0., .01 / max(u_scale, 1e-6), panelMap));
float midScreen = abs(sinA);
if (u_edges == true) {
panelMap = mix(.99, panelMap, panel * clamp(panelMap / (.15 * (1. - pow(midScreen, .1))), 0.0, 1.0));
} else if (midScreen < .07) {
panel *= (midScreen * 15.);
}
return vec2(panel, panelMap);
}
vec4 blendColor(vec4 colorA, float panelMask, float panelMap) {
float fade = 1. - smoothstep(.97 - .97 * u_fadeIn, 1., panelMap);
fade *= smoothstep(-.2 * (1. - u_fadeOut), u_fadeOut, panelMap);
vec3 blendedRGB = mix(vec3(0.), colorA.rgb, fade);
float blendedAlpha = mix(0., colorA.a, fade);
return vec4(blendedRGB, blendedAlpha) * panelMask;
}
void main() {
vec2 uv = v_objectUV;
uv *= 1.25;
float t = .02 * u_time;
t = fract(t);
bool reverseTime = (t < 0.5);
vec3 color = vec3(0.);
float opacity = 0.;
float aa = .005 / u_scale;
int colorsCount = int(u_colorsCount);
vec4 premultipliedColors[${colorPanelsMeta.maxColorCount}];
for (int i = 0; i < ${colorPanelsMeta.maxColorCount}; i++) {
if (i >= colorsCount) break;
vec4 c = u_colors[i];
c.rgb *= c.a;
premultipliedColors[i] = c;
}
float invLength = 1.5 / max(u_length, .001);
float totalColorWeight = 0.;
int panelsNumber = 12;
float densityNormalizer = 1.;
if (colorsCount == 4) {
panelsNumber = 16;
densityNormalizer = 1.34;
} else if (colorsCount == 5) {
panelsNumber = 20;
densityNormalizer = 1.67;
} else if (colorsCount == 7) {
panelsNumber = 14;
densityNormalizer = 1.17;
}
float fPanelsNumber = float(panelsNumber);
float totalPanelsShape = 0.;
float panelGrad = 1. - clamp(u_gradient, 0., 1.);
for (int set = 0; set < 2; set++) {
bool isForward = (set == 0 && !reverseTime) || (set == 1 && reverseTime);
if (!isForward) continue;
for (int i = 0; i <= 20; i++) {
if (i >= panelsNumber) break;
int idx = panelsNumber - 1 - i;
float offset = float(idx) / fPanelsNumber;
if (set == 1) {
offset += .5;
}
float densityFract = densityNormalizer * fract(t + offset);
float angleNorm = densityFract / u_density;
if (densityFract >= .5 || angleNorm >= .3) continue;
float smoothDensity = clamp((.5 - densityFract) / .1, 0., 1.) * clamp(densityFract / .01, 0., 1.);
float smoothAngle = clamp((.3 - angleNorm) / .05, 0., 1.);
if (smoothDensity * smoothAngle < .001) continue;
if (angleNorm > .5) {
angleNorm = 0.5;
}
vec2 panel = getPanel(angleNorm * TWO_PI + PI, uv, invLength, aa);
if (panel[0] <= .001) continue;
float panelMask = panel[0] * smoothDensity * smoothAngle;
float panelMap = panel[1];
int colorIdx = idx % colorsCount;
int nextColorIdx = (idx + 1) % colorsCount;
vec4 colorA = premultipliedColors[colorIdx];
vec4 colorB = premultipliedColors[nextColorIdx];
colorA = mix(colorA, colorB, max(0., smoothstep(.0, .45, panelMap) - panelGrad));
vec4 blended = blendColor(colorA, panelMask, panelMap);
color = blended.rgb + color * (1. - blended.a);
opacity = blended.a + opacity * (1. - blended.a);
}
for (int i = 0; i <= 20; i++) {
if (i >= panelsNumber) break;
int idx = panelsNumber - 1 - i;
float offset = float(idx) / fPanelsNumber;
if (set == 0) {
offset += .5;
}
float densityFract = densityNormalizer * fract(-t + offset);
float angleNorm = -densityFract / u_density;
if (densityFract >= .5 || angleNorm < -.3) continue;
float smoothDensity = clamp((.5 - densityFract) / .1, 0., 1.) * clamp(densityFract / .01, 0., 1.);
float smoothAngle = clamp((angleNorm + .3) / .05, 0., 1.);
if (smoothDensity * smoothAngle < .001) continue;
vec2 panel = getPanel(angleNorm * TWO_PI + PI, uv, invLength, aa);
float panelMask = panel[0] * smoothDensity * smoothAngle;
if (panelMask <= .001) continue;
float panelMap = panel[1];
int colorIdx = (colorsCount - (idx % colorsCount)) % colorsCount;
if (colorIdx < 0) colorIdx += colorsCount;
int nextColorIdx = (colorIdx + 1) % colorsCount;
vec4 colorA = premultipliedColors[colorIdx];
vec4 colorB = premultipliedColors[nextColorIdx];
colorA = mix(colorA, colorB, max(0., smoothstep(.0, .45, panelMap) - panelGrad));
vec4 blended = blendColor(colorA, panelMask, panelMap);
color = blended.rgb + color * (1. - blended.a);
opacity = blended.a + opacity * (1. - blended.a);
}
}
vec3 bgColor = u_colorBack.rgb * u_colorBack.a;
color = color + bgColor * (1.0 - opacity);
opacity = opacity + u_colorBack.a * (1.0 - opacity);
${colorBandingFix}
fragColor = vec4(color, opacity);
}
`;
export {
colorPanelsFragmentShader,
colorPanelsMeta
};
//# sourceMappingURL=color-panels.js.map
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../src/shaders/color-panels.ts"],
"sourcesContent": ["import type { vec4 } from '../types.js';\nimport type { ShaderMotionParams } from '../shader-mount.js';\nimport { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';\nimport { declarePI, colorBandingFix } from '../shader-utils.js';\n\nexport const colorPanelsMeta = {\n maxColorCount: 7,\n} as const;\n\n/**\n * Pseudo-3D semi-transparent panels rotating around a central axis\n *\n * Fragment shader uniforms:\n * - u_time (float): Animation time\n * - u_scale (float): Overall zoom level, used for anti-aliasing calculations\n * - u_colors (vec4[]): Up to 7 RGBA colors used to color the panels\n * - u_colorsCount (float): Number of active colors\n * - u_colorBack (vec4): Background color in RGBA\n * - u_density (float): Angle between every 2 panels (0.25 to 7)\n * - u_angle1 (float): Skew angle applied to all panes (-1 to 1)\n * - u_angle2 (float): Skew angle applied to all panes (-1 to 1)\n * - u_length (float): Panel length relative to total height (0 to 3)\n * - u_edges (bool): Color highlight on the panels edges\n * - u_blur (float): Side blur, 0 for sharp edges (0 to 0.5)\n * - u_fadeIn (float): Transparency near central axis (0 to 1)\n * - u_fadeOut (float): Transparency near viewer (0 to 1)\n * - u_gradient (float): Color mixing within a panel, 0 = solid, 1 = gradient (0 to 1)\n *\n * Vertex shader outputs (used in fragment shader):\n * - v_objectUV (vec2): Object box UV coordinates with global sizing (scale, rotation, offsets, etc) applied\n *\n * Vertex shader uniforms:\n * - u_resolution (vec2): Canvas resolution in pixels\n * - u_pixelRatio (float): Device pixel ratio\n * - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)\n * - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)\n * - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas\n * - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas\n * - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)\n * - u_scale (float): Overall zoom level of the graphics (0.01 to 4)\n * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)\n * - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)\n * - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)\n *\n */\n\n// language=GLSL\nexport const colorPanelsFragmentShader: string = `#version 300 es\nprecision lowp float;\n\nuniform float u_time;\nuniform mediump float u_scale;\n\nuniform vec4 u_colors[${ colorPanelsMeta.maxColorCount }];\nuniform float u_colorsCount;\nuniform vec4 u_colorBack;\nuniform float u_density;\nuniform float u_angle1;\nuniform float u_angle2;\nuniform float u_length;\nuniform bool u_edges;\nuniform float u_blur;\nuniform float u_fadeIn;\nuniform float u_fadeOut;\nuniform float u_gradient;\n\nin vec2 v_objectUV;\n\nout vec4 fragColor;\n\n${ declarePI }\n\nconst float zLimit = .5;\n\nvec2 getPanel(float angle, vec2 uv, float invLength, float aa) {\n float sinA = sin(angle);\n float cosA = cos(angle);\n\n float denom = sinA - uv.y * cosA;\n if (abs(denom) < .01) return vec2(0.);\n\n float z = uv.y / denom;\n\n if (z <= 0. || z > zLimit) return vec2(0.);\n\n float zRatio = z / zLimit;\n float panelMap = 1. - zRatio;\n float x = uv.x * (cosA * z + 1.) * invLength;\n\n float zOffset = zRatio - .5;\n float left = -.5 + zOffset * u_angle1;\n float right = .5 - zOffset * u_angle2;\n float blurX = aa + 2. * panelMap * u_blur;\n\n float leftEdge1 = left - blurX;\n float leftEdge2 = left + .25 * blurX;\n float rightEdge1 = right - .25 * blurX;\n float rightEdge2 = right + blurX;\n\n float panel = smoothstep(leftEdge1, leftEdge2, x) * (1.0 - smoothstep(rightEdge1, rightEdge2, x));\n panel *= mix(0., panel, smoothstep(0., .01 / max(u_scale, 1e-6), panelMap));\n\n float midScreen = abs(sinA);\n if (u_edges == true) {\n panelMap = mix(.99, panelMap, panel * clamp(panelMap / (.15 * (1. - pow(midScreen, .1))), 0.0, 1.0));\n } else if (midScreen < .07) {\n panel *= (midScreen * 15.);\n }\n\n return vec2(panel, panelMap);\n}\n\nvec4 blendColor(vec4 colorA, float panelMask, float panelMap) {\n float fade = 1. - smoothstep(.97 - .97 * u_fadeIn, 1., panelMap);\n\n fade *= smoothstep(-.2 * (1. - u_fadeOut), u_fadeOut, panelMap);\n\n vec3 blendedRGB = mix(vec3(0.), colorA.rgb, fade);\n float blendedAlpha = mix(0., colorA.a, fade);\n\n return vec4(blendedRGB, blendedAlpha) * panelMask;\n}\n\nvoid main() {\n vec2 uv = v_objectUV;\n uv *= 1.25;\n\n float t = .02 * u_time;\n t = fract(t);\n bool reverseTime = (t < 0.5);\n\n vec3 color = vec3(0.);\n float opacity = 0.;\n\n float aa = .005 / u_scale;\n int colorsCount = int(u_colorsCount);\n\n vec4 premultipliedColors[${ colorPanelsMeta.maxColorCount }];\n for (int i = 0; i < ${ colorPanelsMeta.maxColorCount }; i+Line truncated
"mappings": ";;;;;AAEA,eAAmE;AACnE,SAAS,WAAW,uBAAuB;AAEpC,MAAM,kBAAkB;AAAA,EAC7B,eAAe;AACjB;AAwCO,MAAM,4BAAoC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAMxB,gBAAgB,aAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBpD,SAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAmEiB,gBAAgB,aAAc;AAAA,wBACnC,gBAAgB,aAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAiHlD,eAAgB;AAAA;AAAA;AAAA;AAAA;",
"names": []
}
+62
View File
@@ -0,0 +1,62 @@
import type { ShaderMotionParams } from '../shader-mount.js';
import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';
/**
* Animated 2-color dithering over multiple pattern sources (noise, warp, dots, waves, ripple, swirl, sphere).
*
* SIZING NOTE: This shader performs sizing in the fragment shader (not vertex shader) to keep
* u_pxSize in consistent actual pixels. The pixel grid is computed from gl_FragCoord before any
* transforms, so scaling/rotating only affects the underlying pattern shape.
* No vertex shader outputs (v_objectUV, v_patternUV, etc.) are used.
*
* Fragment shader uniforms:
* - u_time (float): Animation time
* - u_resolution (vec2): Canvas resolution in pixels
* - u_pixelRatio (float): Device pixel ratio
* - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)
* - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)
* - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas
* - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas
* - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)
* - u_scale (float): Overall zoom level of the graphics (0.01 to 4)
* - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
* - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)
* - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)
* - u_colorBack (vec4): Background color in RGBA
* - u_colorFront (vec4): Foreground (ink) color in RGBA
* - u_shape (float): Shape pattern type (1 = simplex, 2 = warp, 3 = dots, 4 = wave, 5 = ripple, 6 = swirl, 7 = sphere)
* - u_type (float): Dithering type (1 = random, 2 = 2x2 Bayer, 3 = 4x4 Bayer, 4 = 8x8 Bayer)
* - u_pxSize (float): Pixel size of dithering grid (0.5 to 20)
*
* */
export declare const ditheringFragmentShader: string;
export interface DitheringUniforms extends ShaderSizingUniforms {
u_colorBack: [number, number, number, number];
u_colorFront: [number, number, number, number];
u_shape: (typeof DitheringShapes)[DitheringShape];
u_type: (typeof DitheringTypes)[DitheringType];
u_pxSize: number;
}
export interface DitheringParams extends ShaderSizingParams, ShaderMotionParams {
colorBack?: string;
colorFront?: string;
shape?: DitheringShape;
type?: DitheringType;
size?: number;
}
export declare const DitheringShapes: {
readonly simplex: 1;
readonly warp: 2;
readonly dots: 3;
readonly wave: 4;
readonly ripple: 5;
readonly swirl: 6;
readonly sphere: 7;
};
export type DitheringShape = keyof typeof DitheringShapes;
export declare const DitheringTypes: {
readonly random: 1;
readonly '2x2': 2;
readonly '4x4': 3;
readonly '8x8': 4;
};
export type DitheringType = keyof typeof DitheringTypes;
+271
View File
@@ -0,0 +1,271 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
import {
} from "../shader-sizing.js";
import { simplexNoise, declarePI, proceduralHash11, proceduralHash21 } from "../shader-utils.js";
const ditheringFragmentShader = `#version 300 es
precision mediump float;
uniform float u_time;
uniform vec2 u_resolution;
uniform float u_pixelRatio;
uniform float u_originX;
uniform float u_originY;
uniform float u_worldWidth;
uniform float u_worldHeight;
uniform float u_fit;
uniform float u_scale;
uniform float u_rotation;
uniform float u_offsetX;
uniform float u_offsetY;
uniform float u_pxSize;
uniform vec4 u_colorBack;
uniform vec4 u_colorFront;
uniform float u_shape;
uniform float u_type;
out vec4 fragColor;
${simplexNoise}
${declarePI}
${proceduralHash11}
${proceduralHash21}
float getSimplexNoise(vec2 uv, float t) {
float noise = .5 * snoise(uv - vec2(0., .3 * t));
noise += .5 * snoise(2. * uv + vec2(0., .32 * t));
return noise;
}
const int bayer2x2[4] = int[4](0, 2, 3, 1);
const int bayer4x4[16] = int[16](
0, 8, 2, 10,
12, 4, 14, 6,
3, 11, 1, 9,
15, 7, 13, 5
);
const int bayer8x8[64] = int[64](
0, 32, 8, 40, 2, 34, 10, 42,
48, 16, 56, 24, 50, 18, 58, 26,
12, 44, 4, 36, 14, 46, 6, 38,
60, 28, 52, 20, 62, 30, 54, 22,
3, 35, 11, 43, 1, 33, 9, 41,
51, 19, 59, 27, 49, 17, 57, 25,
15, 47, 7, 39, 13, 45, 5, 37,
63, 31, 55, 23, 61, 29, 53, 21
);
float getBayerValue(vec2 uv, int size) {
ivec2 pos = ivec2(fract(uv / float(size)) * float(size));
int index = pos.y * size + pos.x;
if (size == 2) {
return float(bayer2x2[index]) / 4.0;
} else if (size == 4) {
return float(bayer4x4[index]) / 16.0;
} else if (size == 8) {
return float(bayer8x8[index]) / 64.0;
}
return 0.0;
}
void main() {
float t = .5 * u_time;
float pxSize = u_pxSize * u_pixelRatio;
vec2 pxSizeUV = gl_FragCoord.xy - .5 * u_resolution;
pxSizeUV /= pxSize;
vec2 canvasPixelizedUV = (floor(pxSizeUV) + .5) * pxSize;
vec2 normalizedUV = canvasPixelizedUV / u_resolution;
vec2 ditheringNoiseUV = canvasPixelizedUV;
vec2 shapeUV = normalizedUV;
vec2 boxOrigin = vec2(.5 - u_originX, u_originY - .5);
vec2 givenBoxSize = vec2(u_worldWidth, u_worldHeight);
givenBoxSize = max(givenBoxSize, vec2(1.)) * u_pixelRatio;
float r = u_rotation * PI / 180.;
mat2 graphicRotation = mat2(cos(r), sin(r), -sin(r), cos(r));
vec2 graphicOffset = vec2(-u_offsetX, u_offsetY);
float patternBoxRatio = givenBoxSize.x / givenBoxSize.y;
vec2 boxSize = vec2(
(u_worldWidth == 0.) ? u_resolution.x : givenBoxSize.x,
(u_worldHeight == 0.) ? u_resolution.y : givenBoxSize.y
);
if (u_shape > 3.5) {
vec2 objectBoxSize = vec2(0.);
// fit = none
objectBoxSize.x = min(boxSize.x, boxSize.y);
if (u_fit == 1.) { // fit = contain
objectBoxSize.x = min(u_resolution.x, u_resolution.y);
} else if (u_fit == 2.) { // fit = cover
objectBoxSize.x = max(u_resolution.x, u_resolution.y);
}
objectBoxSize.y = objectBoxSize.x;
vec2 objectWorldScale = u_resolution.xy / objectBoxSize;
shapeUV *= objectWorldScale;
shapeUV += boxOrigin * (objectWorldScale - 1.);
shapeUV += vec2(-u_offsetX, u_offsetY);
shapeUV /= u_scale;
shapeUV = graphicRotation * shapeUV;
} else {
vec2 patternBoxSize = vec2(0.);
// fit = none
patternBoxSize.x = patternBoxRatio * min(boxSize.x / patternBoxRatio, boxSize.y);
float patternWorldNoFitBoxWidth = patternBoxSize.x;
if (u_fit == 1.) { // fit = contain
patternBoxSize.x = patternBoxRatio * min(u_resolution.x / patternBoxRatio, u_resolution.y);
} else if (u_fit == 2.) { // fit = cover
patternBoxSize.x = patternBoxRatio * max(u_resolution.x / patternBoxRatio, u_resolution.y);
}
patternBoxSize.y = patternBoxSize.x / patternBoxRatio;
vec2 patternWorldScale = u_resolution.xy / patternBoxSize;
shapeUV += vec2(-u_offsetX, u_offsetY) / patternWorldScale;
shapeUV += boxOrigin;
shapeUV -= boxOrigin / patternWorldScale;
shapeUV *= u_resolution.xy;
shapeUV /= u_pixelRatio;
if (u_fit > 0.) {
shapeUV *= (patternWorldNoFitBoxWidth / patternBoxSize.x);
}
shapeUV /= u_scale;
shapeUV = graphicRotation * shapeUV;
shapeUV += boxOrigin / patternWorldScale;
shapeUV -= boxOrigin;
shapeUV += .5;
}
float shape = 0.;
if (u_shape < 1.5) {
// Simplex noise
shapeUV *= .001;
shape = 0.5 + 0.5 * getSimplexNoise(shapeUV, t);
shape = smoothstep(0.3, 0.9, shape);
} else if (u_shape < 2.5) {
// Warp
shapeUV *= .003;
for (float i = 1.0; i < 6.0; i++) {
shapeUV.x += 0.6 / i * cos(i * 2.5 * shapeUV.y + t);
shapeUV.y += 0.6 / i * cos(i * 1.5 * shapeUV.x + t);
}
shape = .15 / max(0.001, abs(sin(t - shapeUV.y - shapeUV.x)));
shape = smoothstep(0.02, 1., shape);
} else if (u_shape < 3.5) {
// Dots
shapeUV *= .05;
float stripeIdx = floor(2. * shapeUV.x / TWO_PI);
float rand = hash11(stripeIdx * 10.);
rand = sign(rand - .5) * pow(.1 + abs(rand), .4);
shape = sin(shapeUV.x) * cos(shapeUV.y - 5. * rand * t);
shape = pow(abs(shape), 6.);
} else if (u_shape < 4.5) {
// Sine wave
shapeUV *= 4.;
float wave = cos(.5 * shapeUV.x - 2. * t) * sin(1.5 * shapeUV.x + t) * (.75 + .25 * cos(3. * t));
shape = 1. - smoothstep(-1., 1., shapeUV.y + wave);
} else if (u_shape < 5.5) {
// Ripple
float dist = length(shapeUV);
float waves = sin(pow(dist, 1.7) * 7. - 3. * t) * .5 + .5;
shape = waves;
} else if (u_shape < 6.5) {
// Swirl
float l = length(shapeUV);
float angle = 6. * atan(shapeUV.y, shapeUV.x) + 4. * t;
float twist = 1.2;
float offset = 1. / pow(max(l, 1e-6), twist) + angle / TWO_PI;
float mid = smoothstep(0., 1., pow(l, twist));
shape = mix(0., fract(offset), mid);
} else {
// Sphere
shapeUV *= 2.;
float d = 1. - pow(length(shapeUV), 2.);
vec3 pos = vec3(shapeUV, sqrt(max(0., d)));
vec3 lightPos = normalize(vec3(cos(1.5 * t), .8, sin(1.25 * t)));
shape = .5 + .5 * dot(lightPos, pos);
shape *= step(0., d);
}
int type = int(floor(u_type));
float dithering = 0.0;
switch (type) {
case 1: {
dithering = step(hash21(ditheringNoiseUV), shape);
} break;
case 2:
dithering = getBayerValue(pxSizeUV, 2);
break;
case 3:
dithering = getBayerValue(pxSizeUV, 4);
break;
default :
dithering = getBayerValue(pxSizeUV, 8);
break;
}
dithering -= .5;
float res = step(.5, shape + dithering);
vec3 fgColor = u_colorFront.rgb * u_colorFront.a;
float fgOpacity = u_colorFront.a;
vec3 bgColor = u_colorBack.rgb * u_colorBack.a;
float bgOpacity = u_colorBack.a;
vec3 color = fgColor * res;
float opacity = fgOpacity * res;
color += bgColor * (1. - opacity);
opacity += bgOpacity * (1. - opacity);
fragColor = vec4(color, opacity);
}
`;
const DitheringShapes = {
simplex: 1,
warp: 2,
dots: 3,
wave: 4,
ripple: 5,
swirl: 6,
sphere: 7
};
const DitheringTypes = {
"random": 1,
"2x2": 2,
"4x4": 3,
"8x8": 4
};
export {
DitheringShapes,
DitheringTypes,
ditheringFragmentShader
};
//# sourceMappingURL=dithering.js.map
+7
View File
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../src/shaders/dithering.ts"],
"sourcesContent": ["import type { ShaderMotionParams } from '../shader-mount.js';\nimport {\n type ShaderSizingParams,\n type ShaderSizingUniforms,\n} from '../shader-sizing.js';\nimport { simplexNoise, declarePI, proceduralHash11, proceduralHash21 } from '../shader-utils.js';\n\n/**\n * Animated 2-color dithering over multiple pattern sources (noise, warp, dots, waves, ripple, swirl, sphere).\n *\n * SIZING NOTE: This shader performs sizing in the fragment shader (not vertex shader) to keep\n * u_pxSize in consistent actual pixels. The pixel grid is computed from gl_FragCoord before any\n * transforms, so scaling/rotating only affects the underlying pattern shape.\n * No vertex shader outputs (v_objectUV, v_patternUV, etc.) are used.\n *\n * Fragment shader uniforms:\n * - u_time (float): Animation time\n * - u_resolution (vec2): Canvas resolution in pixels\n * - u_pixelRatio (float): Device pixel ratio\n * - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)\n * - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)\n * - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas\n * - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas\n * - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)\n * - u_scale (float): Overall zoom level of the graphics (0.01 to 4)\n * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)\n * - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)\n * - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)\n * - u_colorBack (vec4): Background color in RGBA\n * - u_colorFront (vec4): Foreground (ink) color in RGBA\n * - u_shape (float): Shape pattern type (1 = simplex, 2 = warp, 3 = dots, 4 = wave, 5 = ripple, 6 = swirl, 7 = sphere)\n * - u_type (float): Dithering type (1 = random, 2 = 2x2 Bayer, 3 = 4x4 Bayer, 4 = 8x8 Bayer)\n * - u_pxSize (float): Pixel size of dithering grid (0.5 to 20)\n *\n * */\n\n// language=GLSL\nexport const ditheringFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform float u_time;\n\nuniform vec2 u_resolution;\nuniform float u_pixelRatio;\nuniform float u_originX;\nuniform float u_originY;\nuniform float u_worldWidth;\nuniform float u_worldHeight;\nuniform float u_fit;\nuniform float u_scale;\nuniform float u_rotation;\nuniform float u_offsetX;\nuniform float u_offsetY;\n\nuniform float u_pxSize;\nuniform vec4 u_colorBack;\nuniform vec4 u_colorFront;\nuniform float u_shape;\nuniform float u_type;\n\nout vec4 fragColor;\n\n${ simplexNoise }\n${ declarePI }\n${ proceduralHash11 }\n${ proceduralHash21 }\n\nfloat getSimplexNoise(vec2 uv, float t) {\n float noise = .5 * snoise(uv - vec2(0., .3 * t));\n noise += .5 * snoise(2. * uv + vec2(0., .32 * t));\n\n return noise;\n}\n\nconst int bayer2x2[4] = int[4](0, 2, 3, 1);\nconst int bayer4x4[16] = int[16](\n0, 8, 2, 10,\n12, 4, 14, 6,\n3, 11, 1, 9,\n15, 7, 13, 5\n);\n\nconst int bayer8x8[64] = int[64](\n0, 32, 8, 40, 2, 34, 10, 42,\n48, 16, 56, 24, 50, 18, 58, 26,\n12, 44, 4, 36, 14, 46, 6, 38,\n60, 28, 52, 20, 62, 30, 54, 22,\n3, 35, 11, 43, 1, 33, 9, 41,\n51, 19, 59, 27, 49, 17, 57, 25,\n15, 47, 7, 39, 13, 45, 5, 37,\n63, 31, 55, 23, 61, 29, 53, 21\n);\n\nfloat getBayerValue(vec2 uv, int size) {\n ivec2 pos = ivec2(fract(uv / float(size)) * float(size));\n int index = pos.y * size + pos.x;\n\n if (size == 2) {\n return float(bayer2x2[index]) / 4.0;\n } else if (size == 4) {\n return float(bayer4x4[index]) / 16.0;\n } else if (size == 8) {\n return float(bayer8x8[index]) / 64.0;\n }\n return 0.0;\n}\n\n\nvoid main() {\n float t = .5 * u_time;\n\n float pxSize = u_pxSize * u_pixelRatio;\n vec2 pxSizeUV = gl_FragCoord.xy - .5 * u_resolution;\n pxSizeUV /= pxSize;\n vec2 canvasPixelizedUV = (floor(pxSizeUV) + .5) * pxSize;\n vec2 normalizedUV = canvasPixelizedUV / u_resolution;\n\n vec2 ditheringNoiseUV = canvasPixelizedUV;\n vec2 shapeUV = normalizedUV;\n\n vec2 boxOrigin = vec2(.5 - u_originX, u_originY - .5);\n vec2 givenBoxSize = vec2(u_worldWidth, u_worldHeight);\n givenBoxSize = max(givenBoxSize, vec2(1.)) * u_pixelRatio;\n float r = u_rotation * PI / 180.;\n mat2 graphicRotation = mat2(cos(r), sin(r), -sin(r), cos(r));\n vec2 graphicOffset = vec2(-u_offsetX, u_offsetY);\n\n float patternBoxRatio = givenBoxSize.x / givenBoxSize.y;\n vec2 boxSize = vec2(\n (u_worldWidth == 0.) ? u_resolution.x : givenBoxSize.x,\n (u_worldHeight == 0.) ? u_resolution.y : givenBoxSize.y\n );\n \n if (u_shape > 3.5) {\n vec2 objectBoxSize = vec2(0.);\n // fit = none\n objectBoxSize.x = min(boxSize.x, boxSize.y);\n if (u_fit == 1.) { // fit = contain\n objectBoxSize.x = min(u_resolution.x, u_resolution.y);\n } else if (u_fit == 2.) { // fit = cover\n objectBoxSize.x = max(u_resolutioLine truncated
"mappings": ";;;;;AACA;AAAA,OAGO;AACP,SAAS,cAAc,WAAW,kBAAkB,wBAAwB;AAgCrE,MAAM,0BAAkC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyB5C,YAAa;AAAA,EACb,SAAU;AAAA,EACV,gBAAiB;AAAA,EACjB,gBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuOb,MAAM,kBAAkB;AAAA,EAC7B,SAAS;AAAA,EACT,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AACV;AAIO,MAAM,iBAAiB;AAAA,EAC5B,UAAU;AAAA,EACV,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACT;",
"names": []
}
+65
View File
@@ -0,0 +1,65 @@
import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';
/**
* Static grid pattern made of circles, diamonds, squares or triangles.
*
* Fragment shader uniforms:
* - u_colorBack (vec4): Background color in RGBA
* - u_colorFill (vec4): Shape fill color in RGBA
* - u_colorStroke (vec4): Shape stroke color in RGBA
* - u_dotSize (float): Base size of each shape in pixels (1 to 100)
* - u_gapX (float): Pattern horizontal spacing in pixels (2 to 500)
* - u_gapY (float): Pattern vertical spacing in pixels (2 to 500)
* - u_strokeWidth (float): Outline stroke width in pixels (0 to 50)
* - u_sizeRange (float): Random variation in shape size, 0 = uniform, higher = random up to base size (0 to 1)
* - u_opacityRange (float): Random variation in shape opacity, 0 = opaque, higher = semi-transparent (0 to 1)
* - u_shape (float): Shape type (0 = circle, 1 = diamond, 2 = square, 3 = triangle)
*
* Vertex shader outputs (used in fragment shader):
* - v_patternUV (vec2): UV coordinates in pixels (scaled by 0.01 for precision), with scale, rotation and offset applied
*
* Vertex shader uniforms:
* - u_resolution (vec2): Canvas resolution in pixels
* - u_pixelRatio (float): Device pixel ratio
* - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)
* - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)
* - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas
* - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas
* - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)
* - u_scale (float): Overall zoom level of the graphics (0.01 to 4)
* - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
* - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)
* - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)
*
*/
export declare const dotGridFragmentShader: string;
export interface DotGridUniforms extends ShaderSizingUniforms {
u_colorBack: [number, number, number, number];
u_colorFill: [number, number, number, number];
u_colorStroke: [number, number, number, number];
u_dotSize: number;
u_gapX: number;
u_gapY: number;
u_strokeWidth: number;
u_sizeRange: number;
u_opacityRange: number;
u_shape: (typeof DotGridShapes)[DotGridShape];
}
export interface DotGridParams extends ShaderSizingParams {
colorBack?: string;
colorFill?: string;
colorStroke?: string;
size?: number;
gapX?: number;
gapY?: number;
strokeWidth?: number;
sizeRange?: number;
opacityRange?: number;
shape?: DotGridShape;
}
export declare const DotGridShapes: {
readonly circle: 0;
readonly diamond: 1;
readonly square: 2;
readonly triangle: 3;
};
export type DotGridShape = keyof typeof DotGridShapes;
+110
View File
@@ -0,0 +1,110 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
import {} from "../shader-sizing.js";
import { declarePI, simplexNoise } from "../shader-utils.js";
const dotGridFragmentShader = `#version 300 es
precision mediump float;
uniform vec4 u_colorBack;
uniform vec4 u_colorFill;
uniform vec4 u_colorStroke;
uniform float u_dotSize;
uniform float u_gapX;
uniform float u_gapY;
uniform float u_strokeWidth;
uniform float u_sizeRange;
uniform float u_opacityRange;
uniform float u_shape;
in vec2 v_patternUV;
out vec4 fragColor;
${declarePI}
${simplexNoise}
float polygon(vec2 p, float N, float rot) {
float a = atan(p.x, p.y) + rot;
float r = TWO_PI / float(N);
return cos(floor(.5 + a / r) * r - a) * length(p);
}
void main() {
// x100 is a default multiplier between vertex and fragmant shaders
// we use it to avoid UV presision issues
vec2 shape_uv = 100. * v_patternUV;
vec2 gap = max(abs(vec2(u_gapX, u_gapY)), vec2(1e-6));
vec2 grid = fract(shape_uv / gap) + 1e-4;
vec2 grid_idx = floor(shape_uv / gap);
float sizeRandomizer = .5 + .8 * snoise(2. * vec2(grid_idx.x * 100., grid_idx.y));
float opacity_randomizer = .5 + .7 * snoise(2. * vec2(grid_idx.y, grid_idx.x));
vec2 center = vec2(0.5) - 1e-3;
vec2 p = (grid - center) * vec2(u_gapX, u_gapY);
float baseSize = u_dotSize * (1. - sizeRandomizer * u_sizeRange);
float strokeWidth = u_strokeWidth * (1. - sizeRandomizer * u_sizeRange);
float dist;
if (u_shape < 0.5) {
// Circle
dist = length(p);
} else if (u_shape < 1.5) {
// Diamond
strokeWidth *= 1.5;
dist = polygon(1.5 * p, 4., .25 * PI);
} else if (u_shape < 2.5) {
// Square
dist = polygon(1.03 * p, 4., 1e-3);
} else {
// Triangle
strokeWidth *= 1.5;
p = p * 2. - 1.;
p *= .9;
p.y = 1. - p.y;
p.y -= .75 * baseSize;
dist = polygon(p, 3., 1e-3);
}
float edgeWidth = fwidth(dist);
float shapeOuter = 1. - smoothstep(baseSize - edgeWidth, baseSize + edgeWidth, dist - strokeWidth);
float shapeInner = 1. - smoothstep(baseSize - edgeWidth, baseSize + edgeWidth, dist);
float stroke = shapeOuter - shapeInner;
float dotOpacity = max(0., 1. - opacity_randomizer * u_opacityRange);
stroke *= dotOpacity;
shapeInner *= dotOpacity;
stroke *= u_colorStroke.a;
shapeInner *= u_colorFill.a;
vec3 color = vec3(0.);
color += stroke * u_colorStroke.rgb;
color += shapeInner * u_colorFill.rgb;
color += (1. - shapeInner - stroke) * u_colorBack.rgb * u_colorBack.a;
float opacity = 0.;
opacity += stroke;
opacity += shapeInner;
opacity += (1. - opacity) * u_colorBack.a;
fragColor = vec4(color, opacity);
}
`;
const DotGridShapes = {
circle: 0,
diamond: 1,
square: 2,
triangle: 3
};
export {
DotGridShapes,
dotGridFragmentShader
};
//# sourceMappingURL=dot-grid.js.map
+7
View File
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../src/shaders/dot-grid.ts"],
"sourcesContent": ["import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';\nimport { declarePI, simplexNoise } from '../shader-utils.js';\n\n/**\n * Static grid pattern made of circles, diamonds, squares or triangles.\n *\n * Fragment shader uniforms:\n * - u_colorBack (vec4): Background color in RGBA\n * - u_colorFill (vec4): Shape fill color in RGBA\n * - u_colorStroke (vec4): Shape stroke color in RGBA\n * - u_dotSize (float): Base size of each shape in pixels (1 to 100)\n * - u_gapX (float): Pattern horizontal spacing in pixels (2 to 500)\n * - u_gapY (float): Pattern vertical spacing in pixels (2 to 500)\n * - u_strokeWidth (float): Outline stroke width in pixels (0 to 50)\n * - u_sizeRange (float): Random variation in shape size, 0 = uniform, higher = random up to base size (0 to 1)\n * - u_opacityRange (float): Random variation in shape opacity, 0 = opaque, higher = semi-transparent (0 to 1)\n * - u_shape (float): Shape type (0 = circle, 1 = diamond, 2 = square, 3 = triangle)\n *\n * Vertex shader outputs (used in fragment shader):\n * - v_patternUV (vec2): UV coordinates in pixels (scaled by 0.01 for precision), with scale, rotation and offset applied\n *\n * Vertex shader uniforms:\n * - u_resolution (vec2): Canvas resolution in pixels\n * - u_pixelRatio (float): Device pixel ratio\n * - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)\n * - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)\n * - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas\n * - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas\n * - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)\n * - u_scale (float): Overall zoom level of the graphics (0.01 to 4)\n * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)\n * - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)\n * - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)\n *\n */\n\n// language=GLSL\nexport const dotGridFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform vec4 u_colorBack;\nuniform vec4 u_colorFill;\nuniform vec4 u_colorStroke;\nuniform float u_dotSize;\nuniform float u_gapX;\nuniform float u_gapY;\nuniform float u_strokeWidth;\nuniform float u_sizeRange;\nuniform float u_opacityRange;\nuniform float u_shape;\n\nin vec2 v_patternUV;\n\nout vec4 fragColor;\n\n${ declarePI }\n${ simplexNoise }\n\nfloat polygon(vec2 p, float N, float rot) {\n float a = atan(p.x, p.y) + rot;\n float r = TWO_PI / float(N);\n\n return cos(floor(.5 + a / r) * r - a) * length(p);\n}\n\nvoid main() {\n\n // x100 is a default multiplier between vertex and fragmant shaders\n // we use it to avoid UV presision issues\n vec2 shape_uv = 100. * v_patternUV;\n\n vec2 gap = max(abs(vec2(u_gapX, u_gapY)), vec2(1e-6));\n vec2 grid = fract(shape_uv / gap) + 1e-4;\n vec2 grid_idx = floor(shape_uv / gap);\n float sizeRandomizer = .5 + .8 * snoise(2. * vec2(grid_idx.x * 100., grid_idx.y));\n float opacity_randomizer = .5 + .7 * snoise(2. * vec2(grid_idx.y, grid_idx.x));\n\n vec2 center = vec2(0.5) - 1e-3;\n vec2 p = (grid - center) * vec2(u_gapX, u_gapY);\n\n float baseSize = u_dotSize * (1. - sizeRandomizer * u_sizeRange);\n float strokeWidth = u_strokeWidth * (1. - sizeRandomizer * u_sizeRange);\n\n float dist;\n if (u_shape < 0.5) {\n // Circle\n dist = length(p);\n } else if (u_shape < 1.5) {\n // Diamond\n strokeWidth *= 1.5;\n dist = polygon(1.5 * p, 4., .25 * PI);\n } else if (u_shape < 2.5) {\n // Square\n dist = polygon(1.03 * p, 4., 1e-3);\n } else {\n // Triangle\n strokeWidth *= 1.5;\n p = p * 2. - 1.;\n p *= .9;\n p.y = 1. - p.y;\n p.y -= .75 * baseSize;\n dist = polygon(p, 3., 1e-3);\n }\n\n float edgeWidth = fwidth(dist);\n float shapeOuter = 1. - smoothstep(baseSize - edgeWidth, baseSize + edgeWidth, dist - strokeWidth);\n float shapeInner = 1. - smoothstep(baseSize - edgeWidth, baseSize + edgeWidth, dist);\n float stroke = shapeOuter - shapeInner;\n\n float dotOpacity = max(0., 1. - opacity_randomizer * u_opacityRange);\n stroke *= dotOpacity;\n shapeInner *= dotOpacity;\n\n stroke *= u_colorStroke.a;\n shapeInner *= u_colorFill.a;\n\n vec3 color = vec3(0.);\n color += stroke * u_colorStroke.rgb;\n color += shapeInner * u_colorFill.rgb;\n color += (1. - shapeInner - stroke) * u_colorBack.rgb * u_colorBack.a;\n\n float opacity = 0.;\n opacity += stroke;\n opacity += shapeInner;\n opacity += (1. - opacity) * u_colorBack.a;\n\n fragColor = vec4(color, opacity);\n}\n`;\n\nexport interface DotGridUniforms extends ShaderSizingUniforms {\n u_colorBack: [number, number, number, number];\n u_colorFill: [number, number, number, number];\n u_colorStroke: [number, number, number,Line truncated
"mappings": ";;;;;AAAA,eAAmE;AACnE,SAAS,WAAW,oBAAoB;AAoCjC,MAAM,wBAAgC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkB1C,SAAU;AAAA,EACV,YAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoGT,MAAM,gBAAgB;AAAA,EAC3B,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,UAAU;AACZ;",
"names": []
}
+57
View File
@@ -0,0 +1,57 @@
import type { vec4 } from '../types.js';
import type { ShaderMotionParams } from '../shader-mount.js';
import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';
export declare const dotOrbitMeta: {
readonly maxColorCount: 10;
};
/**
* Animated multi-color dots pattern with each dot orbiting around its cell center.
* Supports up to 10 colors and various shape and motion controls.
*
* Fragment shader uniforms:
* - u_time (float): Animation time
* - u_colorBack (vec4): Background color in RGBA
* - u_colors (vec4[]): Up to 10 base colors in RGBA
* - u_colorsCount (float): Number of active colors
* - u_stepsPerColor (float): Number of extra colors between base colors, 1 = N colors, 2 = 2×N, etc. (1 to 4)
* - u_size (float): Dot radius relative to cell size (0 to 1)
* - u_sizeRange (float): Random variation in shape size, 0 = uniform, higher = random up to base size (0 to 1)
* - u_spreading (float): Maximum orbit distance around cell center (0 to 1)
* - u_noiseTexture (sampler2D): Pre-computed randomizer source texture
*
* Vertex shader outputs (used in fragment shader):
* - v_patternUV (vec2): UV coordinates in pixels (scaled by 0.01 for precision), with rotation and offset applied
*
* Vertex shader uniforms:
* - u_resolution (vec2): Canvas resolution in pixels
* - u_pixelRatio (float): Device pixel ratio
* - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)
* - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)
* - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas
* - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas
* - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)
* - u_scale (float): Overall zoom level of the graphics (0.01 to 4)
* - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
* - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)
* - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)
*
*/
export declare const dotOrbitFragmentShader: string;
export interface DotOrbitUniforms extends ShaderSizingUniforms {
u_colorBack: [number, number, number, number];
u_colors: vec4[];
u_colorsCount: number;
u_size: number;
u_sizeRange: number;
u_spreading: number;
u_stepsPerColor: number;
u_noiseTexture?: HTMLImageElement;
}
export interface DotOrbitParams extends ShaderSizingParams, ShaderMotionParams {
colorBack?: string;
colors?: string[];
size?: number;
sizeRange?: number;
spreading?: number;
stepsPerColor?: number;
}
+123
View File
@@ -0,0 +1,123 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
import {} from "../shader-sizing.js";
import { declarePI, rotation2, textureRandomizerR, textureRandomizerGB } from "../shader-utils.js";
const dotOrbitMeta = {
maxColorCount: 10
};
const dotOrbitFragmentShader = `#version 300 es
precision mediump float;
uniform float u_time;
uniform sampler2D u_noiseTexture;
uniform vec4 u_colorBack;
uniform vec4 u_colors[${dotOrbitMeta.maxColorCount}];
uniform float u_colorsCount;
uniform float u_stepsPerColor;
uniform float u_size;
uniform float u_sizeRange;
uniform float u_spreading;
in vec2 v_patternUV;
out vec4 fragColor;
${declarePI}
${rotation2}
${textureRandomizerR}
${textureRandomizerGB}
vec3 voronoiShape(vec2 uv, float time) {
vec2 i_uv = floor(uv);
vec2 f_uv = fract(uv);
float spreading = .25 * clamp(u_spreading, 0., 1.);
float minDist = 1.;
vec2 randomizer = vec2(0.);
for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
vec2 tileOffset = vec2(float(x), float(y));
vec2 rand = randomGB(i_uv + tileOffset);
vec2 cellCenter = vec2(.5 + 1e-4);
cellCenter += spreading * cos(time + TWO_PI * rand);
cellCenter -= .5;
cellCenter = rotate(cellCenter, randomR(vec2(rand.x, rand.y)) + .1 * time);
cellCenter += .5;
float dist = length(tileOffset + cellCenter - f_uv);
if (dist < minDist) {
minDist = dist;
randomizer = rand;
}
}
}
return vec3(minDist, randomizer);
}
void main() {
vec2 shape_uv = v_patternUV;
shape_uv *= 1.5;
const float firstFrameOffset = -10.;
float t = u_time + firstFrameOffset;
vec3 voronoi = voronoiShape(shape_uv, t) + 1e-4;
float radius = .25 * clamp(u_size, 0., 1.) - .5 * clamp(u_sizeRange, 0., 1.) * voronoi[2];
float dist = voronoi[0];
float edgeWidth = fwidth(dist);
float dots = 1. - smoothstep(radius - edgeWidth, radius + edgeWidth, dist);
float shape = voronoi[1];
float mixer = shape * (u_colorsCount - 1.);
mixer = (shape - .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 < ${dotOrbitMeta.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 * dots;
float opacity = gradient.a * dots;
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 {
dotOrbitFragmentShader,
dotOrbitMeta
};
//# sourceMappingURL=dot-orbit.js.map
+7
View File
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../src/shaders/dot-orbit.ts"],
"sourcesContent": ["import type { vec4 } from '../types.js';\nimport type { ShaderMotionParams } from '../shader-mount.js';\nimport { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';\nimport { declarePI, rotation2, textureRandomizerR, textureRandomizerGB } from '../shader-utils.js';\n\nexport const dotOrbitMeta = {\n maxColorCount: 10,\n} as const;\n\n/**\n * Animated multi-color dots pattern with each dot orbiting around its cell center.\n * Supports up to 10 colors and various shape and motion controls.\n *\n * Fragment shader uniforms:\n * - u_time (float): Animation time\n * - u_colorBack (vec4): Background color in RGBA\n * - u_colors (vec4[]): Up to 10 base colors in RGBA\n * - u_colorsCount (float): Number of active colors\n * - u_stepsPerColor (float): Number of extra colors between base colors, 1 = N colors, 2 = 2\u00D7N, etc. (1 to 4)\n * - u_size (float): Dot radius relative to cell size (0 to 1)\n * - u_sizeRange (float): Random variation in shape size, 0 = uniform, higher = random up to base size (0 to 1)\n * - u_spreading (float): Maximum orbit distance around cell center (0 to 1)\n * - u_noiseTexture (sampler2D): Pre-computed randomizer source texture\n *\n * Vertex shader outputs (used in fragment shader):\n * - v_patternUV (vec2): UV coordinates in pixels (scaled by 0.01 for precision), with rotation and offset applied\n *\n * Vertex shader uniforms:\n * - u_resolution (vec2): Canvas resolution in pixels\n * - u_pixelRatio (float): Device pixel ratio\n * - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)\n * - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)\n * - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas\n * - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas\n * - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)\n * - u_scale (float): Overall zoom level of the graphics (0.01 to 4)\n * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)\n * - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)\n * - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)\n *\n */\n\n// language=GLSL\nexport const dotOrbitFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform float u_time;\n\nuniform sampler2D u_noiseTexture;\n\nuniform vec4 u_colorBack;\nuniform vec4 u_colors[${ dotOrbitMeta.maxColorCount }];\nuniform float u_colorsCount;\nuniform float u_stepsPerColor;\nuniform float u_size;\nuniform float u_sizeRange;\nuniform float u_spreading;\n\nin vec2 v_patternUV;\n\nout vec4 fragColor;\n\n${ declarePI }\n${ rotation2 }\n${ textureRandomizerR }\n${ textureRandomizerGB }\n\n\nvec3 voronoiShape(vec2 uv, float time) {\n vec2 i_uv = floor(uv);\n vec2 f_uv = fract(uv);\n\n float spreading = .25 * clamp(u_spreading, 0., 1.);\n\n float minDist = 1.;\n vec2 randomizer = vec2(0.);\n for (int y = -1; y <= 1; y++) {\n for (int x = -1; x <= 1; x++) {\n vec2 tileOffset = vec2(float(x), float(y));\n vec2 rand = randomGB(i_uv + tileOffset);\n vec2 cellCenter = vec2(.5 + 1e-4);\n cellCenter += spreading * cos(time + TWO_PI * rand);\n cellCenter -= .5;\n cellCenter = rotate(cellCenter, randomR(vec2(rand.x, rand.y)) + .1 * time);\n cellCenter += .5;\n float dist = length(tileOffset + cellCenter - f_uv);\n if (dist < minDist) {\n minDist = dist;\n randomizer = rand;\n }\n }\n }\n\n return vec3(minDist, randomizer);\n}\n\nvoid main() {\n\n vec2 shape_uv = v_patternUV;\n shape_uv *= 1.5;\n\n const float firstFrameOffset = -10.;\n float t = u_time + firstFrameOffset;\n\n vec3 voronoi = voronoiShape(shape_uv, t) + 1e-4;\n\n float radius = .25 * clamp(u_size, 0., 1.) - .5 * clamp(u_sizeRange, 0., 1.) * voronoi[2];\n float dist = voronoi[0];\n float edgeWidth = fwidth(dist);\n float dots = 1. - smoothstep(radius - edgeWidth, radius + edgeWidth, dist);\n\n float shape = voronoi[1];\n\n float mixer = shape * (u_colorsCount - 1.);\n mixer = (shape - .5 / u_colorsCount) * u_colorsCount;\n float steps = max(1., u_stepsPerColor);\n\n vec4 gradient = u_colors[0];\n gradient.rgb *= gradient.a;\n for (int i = 1; i < ${ dotOrbitMeta.maxColorCount }; i++) {\n if (i >= int(u_colorsCount)) break;\n float localT = clamp(mixer - float(i - 1), 0.0, 1.0);\n localT = round(localT * steps) / steps;\n vec4 c = u_colors[i];\n c.rgb *= c.a;\n gradient = mix(gradient, c, localT);\n }\n\n if ((mixer < 0.) || (mixer > (u_colorsCount - 1.))) {\n float localT = mixer + 1.;\n if (mixer > (u_colorsCount - 1.)) {\n localT = mixer - (u_colorsCount - 1.);\n }\n localT = round(localT * steps) / steps;\n vec4 cFst = u_colors[0];\n cFst.rgb *= cFst.a;\n vec4 cLast = u_colors[int(u_colorLine truncated
"mappings": ";;;;;AAEA,eAAmE;AACnE,SAAS,WAAW,WAAW,oBAAoB,2BAA2B;AAEvE,MAAM,eAAe;AAAA,EAC1B,eAAe;AACjB;AAoCO,MAAM,yBAAiC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAQrB,aAAa,aAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWjD,SAAU;AAAA,EACV,SAAU;AAAA,EACV,kBAAmB;AAAA,EACnB,mBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAsDE,aAAa,aAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;",
"names": []
}
+114
View File
@@ -0,0 +1,114 @@
import type { ShaderMotionParams } from '../shader-mount.js';
import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';
/**
* Fluted glass image filter that transforms an image into streaked, ribbed distortions,
* giving a mix of clarity and obscurity.
*
* Fragment shader uniforms:
* - u_resolution (vec2): Canvas resolution in pixels
* - u_pixelRatio (float): Device pixel ratio
* - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
* - u_image (sampler2D): Source image texture
* - u_imageAspectRatio (float): Aspect ratio of the source image
* - u_colorBack (vec4): Background color in RGBA
* - u_colorShadow (vec4): Shadows color in RGBA
* - u_colorHighlight (vec4): Highlights color in RGBA
* - u_shadows (float): Color gradient added over image and background, following distortion shape (0 to 1)
* - u_highlights (float): Thin strokes along distortion shape, useful for antialiasing on small grid (0 to 1)
* - u_size (float): Size of the distortion shape grid (0 to 1)
* - u_shape (float): Grid shape (1 = lines, 2 = linesIrregular, 3 = wave, 4 = zigzag, 5 = pattern)
* - u_angle (float): Direction of the grid relative to the image in degrees (0 to 180)
* - u_distortionShape (float): Shape of distortion (1 = prism, 2 = lens, 3 = contour, 4 = cascade, 5 = flat)
* - u_distortion (float): Power of distortion applied within each stripe (0 to 1)
* - u_shift (float): Texture shift in direction opposite to the grid (-1 to 1)
* - u_stretch (float): Extra distortion along the grid lines (0 to 1)
* - u_blur (float): One-directional blur over the image and extra blur around edges (0 to 1)
* - u_edges (float): Glass distortion and softness on the image edges (0 to 1)
* - u_marginLeft (float): Distance from the left edge to the effect (0 to 1)
* - u_marginRight (float): Distance from the right edge to the effect (0 to 1)
* - u_marginTop (float): Distance from the top edge to the effect (0 to 1)
* - u_marginBottom (float): Distance from the bottom edge to the effect (0 to 1)
* - u_grainMixer (float): Strength of grain distortion applied to shape edges (0 to 1)
* - u_grainOverlay (float): Post-processing black/white grain overlay (0 to 1)
*
* Vertex shader outputs (used in fragment shader):
* - v_imageUV (vec2): Image UV coordinates with global sizing (rotation, scale, offset, etc) applied
*
* Vertex shader uniforms:
* - u_resolution (vec2): Canvas resolution in pixels
* - u_pixelRatio (float): Device pixel ratio
* - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)
* - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)
* - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)
* - u_scale (float): Overall zoom level of the graphics (0.01 to 4)
* - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
* - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)
* - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)
* - u_imageAspectRatio (float): Aspect ratio of the source image
*
*/
export declare const flutedGlassFragmentShader: string;
export interface FlutedGlassUniforms extends ShaderSizingUniforms {
u_image: HTMLImageElement | string | undefined;
u_colorBack: [number, number, number, number];
u_colorShadow: [number, number, number, number];
u_colorHighlight: [number, number, number, number];
u_shadows: number;
u_size: number;
u_angle: number;
u_distortion: number;
u_shift: number;
u_blur: number;
u_edges: number;
u_marginLeft: number;
u_marginRight: number;
u_marginTop: number;
u_marginBottom: number;
u_stretch: number;
u_distortionShape: (typeof GlassDistortionShapes)[GlassDistortionShape];
u_highlights: number;
u_shape: (typeof GlassGridShapes)[GlassGridShape];
u_grainMixer: number;
u_grainOverlay: number;
u_noiseTexture?: HTMLImageElement;
}
export interface FlutedGlassParams extends ShaderSizingParams, ShaderMotionParams {
image?: HTMLImageElement | string;
colorBack?: string;
colorShadow?: string;
colorHighlight?: string;
shadows?: number;
size?: number;
angle?: number;
distortion?: number;
shift?: number;
blur?: number;
edges?: number;
margin?: number;
marginLeft?: number;
marginRight?: number;
marginTop?: number;
marginBottom?: number;
stretch?: number;
distortionShape?: GlassDistortionShape;
highlights?: number;
shape?: GlassGridShape;
grainMixer?: number;
grainOverlay?: number;
}
export declare const GlassGridShapes: {
readonly lines: 1;
readonly linesIrregular: 2;
readonly wave: 3;
readonly zigzag: 4;
readonly pattern: 5;
};
export declare const GlassDistortionShapes: {
readonly prism: 1;
readonly lens: 2;
readonly contour: 3;
readonly cascade: 4;
readonly flat: 5;
};
export type GlassDistortionShape = keyof typeof GlassDistortionShapes;
export type GlassGridShape = keyof typeof GlassGridShapes;
+358
View File
@@ -0,0 +1,358 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
import {} from "../shader-sizing.js";
import { declarePI, rotation2, proceduralHash21 } from "../shader-utils.js";
const flutedGlassFragmentShader = `#version 300 es
precision mediump float;
uniform vec2 u_resolution;
uniform float u_pixelRatio;
uniform float u_rotation;
uniform vec4 u_colorBack;
uniform vec4 u_colorShadow;
uniform vec4 u_colorHighlight;
uniform sampler2D u_image;
uniform float u_imageAspectRatio;
uniform float u_size;
uniform float u_shadows;
uniform float u_angle;
uniform float u_stretch;
uniform float u_shape;
uniform float u_distortion;
uniform float u_highlights;
uniform float u_distortionShape;
uniform float u_shift;
uniform float u_blur;
uniform float u_edges;
uniform float u_marginLeft;
uniform float u_marginRight;
uniform float u_marginTop;
uniform float u_marginBottom;
uniform float u_grainMixer;
uniform float u_grainOverlay;
in vec2 v_imageUV;
out vec4 fragColor;
${declarePI}
${rotation2}
${proceduralHash21}
float valueNoise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = hash21(i);
float b = hash21(i + vec2(1.0, 0.0));
float c = hash21(i + vec2(0.0, 1.0));
float d = hash21(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
float x1 = mix(a, b, u.x);
float x2 = mix(c, d, u.x);
return mix(x1, x2, u.y);
}
float getUvFrame(vec2 uv, float softness) {
float aax = 2. * fwidth(uv.x);
float aay = 2. * fwidth(uv.y);
float left = smoothstep(0., aax + softness, uv.x);
float right = 1. - smoothstep(1. - softness - aax, 1., uv.x);
float bottom = smoothstep(0., aay + softness, uv.y);
float top = 1. - smoothstep(1. - softness - aay, 1., uv.y);
return left * right * bottom * top;
}
const int MAX_RADIUS = 50;
vec4 samplePremultiplied(sampler2D tex, vec2 uv) {
vec4 c = texture(tex, uv);
c.rgb *= c.a;
return c;
}
vec4 getBlur(sampler2D tex, vec2 uv, vec2 texelSize, vec2 dir, float sigma) {
if (sigma <= .5) return texture(tex, uv);
int radius = int(min(float(MAX_RADIUS), ceil(3.0 * sigma)));
float twoSigma2 = 2.0 * sigma * sigma;
float gaussianNorm = 1.0 / sqrt(TWO_PI * sigma * sigma);
vec4 sum = samplePremultiplied(tex, uv) * gaussianNorm;
float weightSum = gaussianNorm;
for (int i = 1; i <= MAX_RADIUS; i++) {
if (i > radius) break;
float x = float(i);
float w = exp(-(x * x) / twoSigma2) * gaussianNorm;
vec2 offset = dir * texelSize * x;
vec4 s1 = samplePremultiplied(tex, uv + offset);
vec4 s2 = samplePremultiplied(tex, uv - offset);
sum += (s1 + s2) * w;
weightSum += 2.0 * w;
}
vec4 result = sum / weightSum;
if (result.a > 0.) {
result.rgb /= result.a;
}
return result;
}
vec2 rotateAspect(vec2 p, float a, float aspect) {
p.x *= aspect;
p = rotate(p, a);
p.x /= aspect;
return p;
}
float smoothFract(float x) {
float f = fract(x);
float w = fwidth(x);
float edge = abs(f - 0.5) - 0.5;
float band = smoothstep(-w, w, edge);
return mix(f, 1.0 - f, band);
}
void main() {
float patternRotation = -u_angle * PI / 180.;
float patternSize = mix(200., 5., u_size);
vec2 uv = v_imageUV;
vec2 uvMask = gl_FragCoord.xy / u_resolution.xy;
vec2 sw = vec2(.005);
vec4 margins = vec4(u_marginLeft, u_marginTop, u_marginRight, u_marginBottom);
float mask =
smoothstep(margins[0], margins[0] + sw.x, uvMask.x + sw.x) *
smoothstep(margins[2], margins[2] + sw.x, 1.0 - uvMask.x + sw.x) *
smoothstep(margins[1], margins[1] + sw.y, uvMask.y + sw.y) *
smoothstep(margins[3], margins[3] + sw.y, 1.0 - uvMask.y + sw.y);
float maskOuter =
smoothstep(margins[0] - sw.x, margins[0], uvMask.x + sw.x) *
smoothstep(margins[2] - sw.x, margins[2], 1.0 - uvMask.x + sw.x) *
smoothstep(margins[1] - sw.y, margins[1], uvMask.y + sw.y) *
smoothstep(margins[3] - sw.y, margins[3], 1.0 - uvMask.y + sw.y);
float maskStroke = maskOuter - mask;
float maskInner =
smoothstep(margins[0] - 2. * sw.x, margins[0], uvMask.x) *
smoothstep(margins[2] - 2. * sw.x, margins[2], 1.0 - uvMask.x) *
smoothstep(margins[1] - 2. * sw.y, margins[1], uvMask.y) *
smoothstep(margins[3] - 2. * sw.y, margins[3], 1.0 - uvMask.y);
float maskStrokeInner = maskInner - mask;
uv -= .5;
uv *= patternSize;
uv = rotateAspect(uv, patternRotation, u_imageAspectRatio);
float curve = 0.;
float patternY = uv.y / u_imageAspectRatio;
if (u_shape > 4.5) {
// pattern
curve = .5 + .5 * sin(.5 * PI * uv.x) * cos(.5 * PI * patternY);
} else if (u_shape > 3.5) {
// zigzag
curve = 10. * abs(fract(.1 * patternY) - .5);
} else if (u_shape > 2.5) {
// wave
curve = 4. * sin(.23 * patternY);
} else if (u_shape > 1.5) {
// lines irregular
curve = .5 + .5 * sin(.5 * uv.x) * sin(1.7 * uv.x);
} else {
// lines
}
vec2 UvToFract = uv + curve;
vec2 fractOrigUV = fract(uv);
vec2 floorOrigUV = floor(uv);
float x = smoothFract(UvToFract.x);
float xNonSmooth = fract(UvToFract.x) + .0001;
float highlightsWidth = 2. * max(.001, fwidth(UvToFract.x));
highlightsWidth += 2. * maskStrokeInner;
float highlights = smoothstep(0., highlightsWidth, xNonSmooth);
highlights *= smoothstep(1., 1. - highlightsWidth, xNonSmooth);
highlights = 1. - highlights;
highlights *= u_highlights;
highlights = clamp(highlights, 0., 1.);
highlights *= mask;
float shadows = pow(x, 1.3);
float distortion = 0.;
float fadeX = 1.;
float frameFade = 0.;
float aa = fwidth(xNonSmooth);
aa = max(aa, fwidth(uv.x));
aa = max(aa, fwidth(UvToFract.x));
aa = max(aa, .0001);
if (u_distortionShape == 1.) {
distortion = -pow(1.5 * x, 3.);
distortion += (.5 - u_shift);
frameFade = pow(1.5 * x, 3.);
aa = max(.2, aa);
aa += mix(.2, 0., u_size);
fadeX = smoothstep(0., aa, xNonSmooth) * smoothstep(1., 1. - aa, xNonSmooth);
distortion = mix(.5, distortion, fadeX);
} else if (u_distortionShape == 2.) {
distortion = 2. * pow(x, 2.);
distortion -= (.5 + u_shift);
frameFade = pow(abs(x - .5), 4.);
aa = max(.2, aa);
aa += mix(.2, 0., u_size);
fadeX = smoothstep(0., aa, xNonSmooth) * smoothstep(1., 1. - aa, xNonSmooth);
distortion = mix(.5, distortion, fadeX);
frameFade = mix(1., frameFade, .5 * fadeX);
} else if (u_distortionShape == 3.) {
distortion = pow(2. * (xNonSmooth - .5), 6.);
distortion -= .25;
distortion -= u_shift;
frameFade = 1. - 2. * pow(abs(x - .4), 2.);
aa = .15;
aa += mix(.1, 0., u_size);
fadeX = smoothstep(0., aa, xNonSmooth) * smoothstep(1., 1. - aa, xNonSmooth);
frameFade = mix(1., frameFade, fadeX);
} else if (u_distortionShape == 4.) {
x = xNonSmooth;
distortion = sin((x + .25) * TWO_PI);
shadows = .5 + .5 * asin(distortion) / (.5 * PI);
distortion *= .5;
distortion -= u_shift;
frameFade = .5 + .5 * sin(x * TWO_PI);
} else if (u_distortionShape == 5.) {
distortion -= pow(abs(x), .2) * x;
distortion += .33;
distortion -= 3. * u_shift;
distortion *= .33;
frameFade = .3 * (smoothstep(.0, 1., x));
shadows = pow(x, 2.5);
aa = max(.1, aa);
aa += mix(.1, 0., u_size);
fadeX = smoothstep(0., aa, xNonSmooth) * smoothstep(1., 1. - aa, xNonSmooth);
distortion *= fadeX;
}
vec2 dudx = dFdx(v_imageUV);
vec2 dudy = dFdy(v_imageUV);
vec2 grainUV = v_imageUV - .5;
grainUV *= (.8 / vec2(length(dudx), length(dudy)));
grainUV += .5;
float grain = valueNoise(grainUV);
grain = smoothstep(.4, .7, grain);
grain *= u_grainMixer;
distortion = mix(distortion, 0., grain);
shadows = min(shadows, 1.);
shadows += maskStrokeInner;
shadows *= mask;
shadows = min(shadows, 1.);
shadows *= pow(u_shadows, 2.);
shadows = clamp(shadows, 0., 1.);
distortion *= 3. * u_distortion;
frameFade *= u_distortion;
fractOrigUV.x += distortion;
floorOrigUV = rotateAspect(floorOrigUV, -patternRotation, u_imageAspectRatio);
fractOrigUV = rotateAspect(fractOrigUV, -patternRotation, u_imageAspectRatio);
uv = (floorOrigUV + fractOrigUV) / patternSize;
uv += pow(maskStroke, 4.);
uv += vec2(.5);
uv = mix(v_imageUV, uv, smoothstep(0., .7, mask));
float blur = mix(0., 50., u_blur);
blur = mix(0., blur, smoothstep(.5, 1., mask));
float edgeDistortion = mix(.0, .04, u_edges);
edgeDistortion += .06 * frameFade * u_edges;
edgeDistortion *= mask;
float frame = getUvFrame(uv, edgeDistortion);
float stretch = 1. - smoothstep(0., .5, xNonSmooth) * smoothstep(1., 1. - .5, xNonSmooth);
stretch = pow(stretch, 2.);
stretch *= mask;
stretch *= getUvFrame(uv, .1 + .05 * mask * frameFade);
uv.y = mix(uv.y, .5, u_stretch * stretch);
vec4 image = getBlur(u_image, uv, 1. / u_resolution / u_pixelRatio, vec2(0., 1.), blur);
image.rgb *= image.a;
vec4 backColor = u_colorBack;
backColor.rgb *= backColor.a;
vec4 highlightColor = u_colorHighlight;
highlightColor.rgb *= highlightColor.a;
vec4 shadowColor = u_colorShadow;
vec3 color = highlightColor.rgb * highlights;
float opacity = highlightColor.a * highlights;
shadows = mix(shadows * shadowColor.a, 0., highlights);
color = mix(color, shadowColor.rgb * shadowColor.a, .5 * shadows);
color += .5 * pow(shadows, .5) * shadowColor.rgb;
opacity += shadows;
color = clamp(color, vec3(0.), vec3(1.));
opacity = clamp(opacity, 0., 1.);
color += image.rgb * (1. - opacity) * frame;
opacity += image.a * (1. - opacity) * frame;
color += backColor.rgb * (1. - opacity);
opacity += backColor.a * (1. - opacity);
float grainOverlay = valueNoise(rotate(grainUV, 1.) + vec2(3.));
grainOverlay = mix(grainOverlay, valueNoise(rotate(grainUV, 2.) + vec2(-1.)), .5);
grainOverlay = pow(grainOverlay, 1.3);
float grainOverlayV = grainOverlay * 2. - 1.;
vec3 grainOverlayColor = vec3(step(0., grainOverlayV));
float grainOverlayStrength = u_grainOverlay * abs(grainOverlayV);
grainOverlayStrength = pow(grainOverlayStrength, .8);
grainOverlayStrength *= mask;
color = mix(color, grainOverlayColor, .35 * grainOverlayStrength);
opacity += .5 * grainOverlayStrength;
opacity = clamp(opacity, 0., 1.);
fragColor = vec4(color, opacity);
}
`;
const GlassGridShapes = {
lines: 1,
linesIrregular: 2,
wave: 3,
zigzag: 4,
pattern: 5
};
const GlassDistortionShapes = {
prism: 1,
lens: 2,
contour: 3,
cascade: 4,
flat: 5
};
export {
GlassDistortionShapes,
GlassGridShapes,
flutedGlassFragmentShader
};
//# sourceMappingURL=fluted-glass.js.map
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../src/shaders/fluted-glass.ts"],
"sourcesContent": ["import type { ShaderMotionParams } from '../shader-mount.js';\nimport { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';\nimport { declarePI, rotation2, proceduralHash21 } from '../shader-utils.js';\n\n/**\n * Fluted glass image filter that transforms an image into streaked, ribbed distortions,\n * giving a mix of clarity and obscurity.\n *\n * Fragment shader uniforms:\n * - u_resolution (vec2): Canvas resolution in pixels\n * - u_pixelRatio (float): Device pixel ratio\n * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)\n * - u_image (sampler2D): Source image texture\n * - u_imageAspectRatio (float): Aspect ratio of the source image\n * - u_colorBack (vec4): Background color in RGBA\n * - u_colorShadow (vec4): Shadows color in RGBA\n * - u_colorHighlight (vec4): Highlights color in RGBA\n * - u_shadows (float): Color gradient added over image and background, following distortion shape (0 to 1)\n * - u_highlights (float): Thin strokes along distortion shape, useful for antialiasing on small grid (0 to 1)\n * - u_size (float): Size of the distortion shape grid (0 to 1)\n * - u_shape (float): Grid shape (1 = lines, 2 = linesIrregular, 3 = wave, 4 = zigzag, 5 = pattern)\n * - u_angle (float): Direction of the grid relative to the image in degrees (0 to 180)\n * - u_distortionShape (float): Shape of distortion (1 = prism, 2 = lens, 3 = contour, 4 = cascade, 5 = flat)\n * - u_distortion (float): Power of distortion applied within each stripe (0 to 1)\n * - u_shift (float): Texture shift in direction opposite to the grid (-1 to 1)\n * - u_stretch (float): Extra distortion along the grid lines (0 to 1)\n * - u_blur (float): One-directional blur over the image and extra blur around edges (0 to 1)\n * - u_edges (float): Glass distortion and softness on the image edges (0 to 1)\n * - u_marginLeft (float): Distance from the left edge to the effect (0 to 1)\n * - u_marginRight (float): Distance from the right edge to the effect (0 to 1)\n * - u_marginTop (float): Distance from the top edge to the effect (0 to 1)\n * - u_marginBottom (float): Distance from the bottom edge to the effect (0 to 1)\n * - u_grainMixer (float): Strength of grain distortion applied to shape edges (0 to 1)\n * - u_grainOverlay (float): Post-processing black/white grain overlay (0 to 1)\n *\n * Vertex shader outputs (used in fragment shader):\n * - v_imageUV (vec2): Image UV coordinates with global sizing (rotation, scale, offset, etc) applied\n *\n * Vertex shader uniforms:\n * - u_resolution (vec2): Canvas resolution in pixels\n * - u_pixelRatio (float): Device pixel ratio\n * - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)\n * - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)\n * - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)\n * - u_scale (float): Overall zoom level of the graphics (0.01 to 4)\n * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)\n * - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)\n * - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)\n * - u_imageAspectRatio (float): Aspect ratio of the source image\n *\n */\n\n// language=GLSL\nexport const flutedGlassFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform vec2 u_resolution;\nuniform float u_pixelRatio;\nuniform float u_rotation;\n\nuniform vec4 u_colorBack;\nuniform vec4 u_colorShadow;\nuniform vec4 u_colorHighlight;\n\nuniform sampler2D u_image;\nuniform float u_imageAspectRatio;\n\nuniform float u_size;\nuniform float u_shadows;\nuniform float u_angle;\nuniform float u_stretch;\nuniform float u_shape;\nuniform float u_distortion;\nuniform float u_highlights;\nuniform float u_distortionShape;\nuniform float u_shift;\nuniform float u_blur;\nuniform float u_edges;\nuniform float u_marginLeft;\nuniform float u_marginRight;\nuniform float u_marginTop;\nuniform float u_marginBottom;\nuniform float u_grainMixer;\nuniform float u_grainOverlay;\n\nin vec2 v_imageUV;\n\nout vec4 fragColor;\n\n${ declarePI }\n${ rotation2 }\n${ proceduralHash21 }\n\nfloat valueNoise(vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = hash21(i);\n float b = hash21(i + vec2(1.0, 0.0));\n float c = hash21(i + vec2(0.0, 1.0));\n float d = hash21(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n float x1 = mix(a, b, u.x);\n float x2 = mix(c, d, u.x);\n return mix(x1, x2, u.y);\n}\n\nfloat getUvFrame(vec2 uv, float softness) {\n float aax = 2. * fwidth(uv.x);\n float aay = 2. * fwidth(uv.y);\n float left = smoothstep(0., aax + softness, uv.x);\n float right = 1. - smoothstep(1. - softness - aax, 1., uv.x);\n float bottom = smoothstep(0., aay + softness, uv.y);\n float top = 1. - smoothstep(1. - softness - aay, 1., uv.y);\n return leLine truncated
"mappings": ";;;;;AACA,eAAmE;AACnE,SAAS,WAAW,WAAW,wBAAwB;AAmDhD,MAAM,4BAAoC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoC9C,SAAU;AAAA,EACV,SAAU;AAAA,EACV,gBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwVb,MAAM,kBAAkB;AAAA,EAC7B,OAAO;AAAA,EACP,gBAAgB;AAAA,EAChB,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,SAAS;AACX;AAEO,MAAM,wBAAwB;AAAA,EACnC,OAAO;AAAA,EACP,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AAAA,EACT,MAAM;AACR;",
"names": []
}
+94
View File
@@ -0,0 +1,94 @@
import type { vec4 } from '../types.js';
import type { ShaderMotionParams } from '../shader-mount.js';
import type { ShaderSizingParams, ShaderSizingUniforms } from '../shader-sizing.js';
export declare const gemSmokeMeta: {
readonly maxColorCount: 6;
};
/**
* Animated color fields placed over uploaded logo shape; gives the illusion of smoky noise
* behind the glassy shape.
*
* Fragment shader uniforms:
* - u_time (float): Animation time
* - u_resolution (vec2): Canvas resolution in pixels
* - u_image (sampler2D): Pre-processed source image texture (R = edge gradient, G = alpha)
* - u_imageAspectRatio (float): Aspect ratio of the source image
* - u_colors (vec4[]): Up to 6 smoke colors in RGBA
* - u_colorsCount (float): Number of active colors
* - u_colorBack (vec4): Background color in RGBA
* - u_innerDistortion (float): Power of smoke distortion inside the input shape (0 to 1)
* - u_outerDistortion (float): Power of smoke distortion outside the input shape (0 to 1)
* - u_outerGlow (float): Visibility of smoke shape outside the input shape (0 to 1)
* - u_innerGlow (float): Visibility of smoke shape inside the input shape (0 to 1)
* - u_colorInner (vec4): Additional color inside the input shape, mixing with smoke (RGBA)
* - u_offset (float): Vertical offset of smoke inside the shape (-1 to 1)
* - u_angle (float): Smoke direction in degrees (0 to 360)
* - u_size (float): Size of smoke shape relative to the image box (0 to 1)
*
* Vertex shader outputs (used in fragment shader):
* - v_imageUV (vec2): UV coordinates for sampling the source image, with fit, scale, rotation, and offset applied
* - v_objectUV (vec2): Normalized UV coordinates with scale, rotation, and offset applied
*
* Vertex shader uniforms:
* - u_resolution (vec2): Canvas resolution in pixels
* - u_pixelRatio (float): Device pixel ratio
* - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)
* - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)
* - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas
* - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas
* - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)
* - u_scale (float): Overall zoom level of the graphics (0.01 to 4)
* - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
* - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)
* - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)
* - u_imageAspectRatio (float): Aspect ratio of the source image
*
*/
export declare const gemSmokeFragmentShader: string;
export declare const POISSON_CONFIG_OPTIMIZED: {
measurePerformance: boolean;
workingSize: number;
iterations: number;
};
export declare function toProcessedGemSmoke(file: File | string): Promise<{
imageData: ImageData;
pngBlob: Blob;
}>;
export interface GemSmokeUniforms extends ShaderSizingUniforms {
u_colorBack: [number, number, number, number];
u_colors: vec4[];
u_colorsCount: number;
u_image: HTMLImageElement | string | undefined;
u_innerDistortion: number;
u_outerDistortion: number;
u_outerGlow: number;
u_innerGlow: number;
u_colorInner: [number, number, number, number];
u_offset: number;
u_angle: number;
u_size: number;
u_shape: (typeof GemSmokeShapes)[GemSmokeShape];
u_isImage: boolean;
}
export interface GemSmokeParams extends ShaderSizingParams, ShaderMotionParams {
colors?: string[];
colorBack?: string;
image?: HTMLImageElement | string | undefined;
innerDistortion?: number;
outerDistortion?: number;
outerGlow?: number;
innerGlow?: number;
colorInner?: string;
offset?: number;
angle?: number;
size?: number;
shape?: GemSmokeShape;
}
export declare const GemSmokeShapes: {
readonly none: 0;
readonly circle: 1;
readonly daisy: 2;
readonly diamond: 3;
readonly metaballs: 4;
};
export type GemSmokeShape = keyof typeof GemSmokeShapes;
+585
View File
@@ -0,0 +1,585 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
import { rotation2, declarePI } from "../shader-utils.js";
const gemSmokeMeta = {
maxColorCount: 6
};
const gemSmokeFragmentShader = `#version 300 es
precision mediump float;
in mediump vec2 v_imageUV;
in mediump vec2 v_objectUV;
in mediump vec2 v_responsiveUV;
in mediump vec2 v_responsiveBoxGivenSize;
out vec4 fragColor;
// Image
uniform sampler2D u_image;
uniform float u_imageAspectRatio;
// Canvas
uniform vec2 u_resolution;
uniform float u_time;
// Colors
uniform vec4 u_colors[${gemSmokeMeta.maxColorCount}];
uniform float u_colorsCount;
uniform vec4 u_colorBack;
uniform vec4 u_colorInner;
// Effect controls
uniform float u_innerDistortion;
uniform float u_outerDistortion;
uniform float u_outerGlow;
uniform float u_innerGlow;
uniform float u_offset;
uniform float u_angle;
uniform float u_size;
// Shape controls
uniform float u_shape;
uniform bool u_isImage;
${declarePI}
${rotation2}
// 9x9 Gaussian blur on R and G channels
vec2 gaussBlur9x9RG(sampler2D tex, vec2 uv, vec2 dudx, vec2 dudy, float radius) {
vec2 texel = 1.0 / vec2(textureSize(tex, 0));
vec2 r = max(radius, 0.0) * texel;
// Pascal's row 8: sum = 256, 2D norm = 65536
const float k[9] = float[9](1.0, 8.0, 28.0, 56.0, 70.0, 56.0, 28.0, 8.0, 1.0);
vec2 sum = vec2(0.0);
for (int j = -4; j <= 4; ++j) {
float wy = k[j + 4];
for (int i = -4; i <= 4; ++i) {
float w = k[i + 4] * wy;
vec2 off = vec2(float(i) * r.x, float(j) * r.y);
sum += w * texture(tex, uv + off).rg;
}
}
return sum / 65536.0;
}
float sst(float a, float b, float x) {
return smoothstep(a, b, x);
}
void main() {
float time = u_time;
float roundness = 0.;
float imgAlpha = 0.;
if (u_isImage == true) {
// Image sampling (UV scaled inward to account for padding)
vec2 imageUV = v_imageUV;
imageUV -= .5;
imageUV *= .95;
imageUV += .5;
vec2 dudx = dFdx(v_imageUV);
vec2 dudy = dFdy(v_imageUV);
// Blurred image: x = roundness, y = alpha
vec2 blurred = gaussBlur9x9RG(u_image, imageUV, dudx, dudy, 10.);
roundness = 1. - blurred.x;
vec2 texelA = 1.0 / vec2(textureSize(u_image, 0));
const float k3[3] = float[3](1.0, 2.0, 1.0);
for (int j = -1; j <= 1; ++j) {
for (int i = -1; i <= 1; ++i) {
imgAlpha += k3[i + 1] * k3[j + 1] * texture(u_image, imageUV + vec2(float(i) * texelA.x, float(j) * texelA.y)).g;
}
}
imgAlpha /= 16.0;
} else {
vec2 uv = v_objectUV + .5;
uv.y = 1. - uv.y;
float edge = 0.;
if (u_shape < 1.) {
// full-fill on canvas
vec2 borderUV = v_responsiveUV + .5;
vec2 mask = min(borderUV, 1. - borderUV);
vec2 pixel_thickness = min(250. / v_responsiveBoxGivenSize, vec2(.5));
float maskX = smoothstep(0.0, pixel_thickness.x, mask.x);
float maskY = smoothstep(0.0, pixel_thickness.y, mask.y);
maskX = pow(maskX, .25);
maskY = pow(maskY, .25);
edge = clamp(1. - maskX * maskY, 0., 1.);
} else if (u_shape < 2.) {
// circle
vec2 shapeUV = uv - .5;
shapeUV *= .67;
edge = pow(clamp(3. * length(shapeUV), 0., 1.), 18.);
} else if (u_shape < 3.) {
// daisy
vec2 shapeUV = uv - .5;
shapeUV *= 1.68;
float r = length(shapeUV) * 2.;
float a = atan(shapeUV.y, shapeUV.x) + .2;
r *= (1. + .05 * sin(3. * a + 2. * time));
float f = abs(cos(a * 3.));
edge = smoothstep(f, f + .7, r);
edge *= edge;
} else if (u_shape < 4.) {
// diamond
vec2 shapeUV = uv - .5;
shapeUV = rotate(shapeUV, .25 * PI);
shapeUV *= 1.42;
shapeUV += .5;
vec2 mask = min(shapeUV, 1. - shapeUV);
vec2 pixel_thickness = vec2(.15);
float maskX = smoothstep(0.0, pixel_thickness.x, mask.x);
float maskY = smoothstep(0.0, pixel_thickness.y, mask.y);
maskX = pow(maskX, .25);
maskY = pow(maskY, .25);
edge = clamp(1. - maskX * maskY, 0., 1.);
} else if (u_shape < 5.) {
// metaballs
vec2 shapeUV = uv - .5;
shapeUV *= 1.3;
edge = 0.;
for (int i = 0; i < 5; i++) {
float fi = float(i);
float speed = 1.5 + 2./3. * sin(fi * 12.345);
float angle = -fi * 1.5;
vec2 dir1 = vec2(cos(angle), sin(angle));
vec2 dir2 = vec2(cos(angle + 1.57), sin(angle + 1.));
vec2 traj = .4 * (dir1 * sin(time * speed + fi * 1.23) + dir2 * cos(time * (speed * 0.7) + fi * 2.17));
float d = length(shapeUV + traj);
edge += pow(1.0 - clamp(d, 0.0, 1.0), 4.0);
}
edge = 1. - smoothstep(.65, .9, edge);
edge = pow(edge, 4.);
}
imgAlpha = 1. - smoothstep(.9 - 2. * fwidth(edge), .9, edge);
roundness = 1. - edge;
}
// Smoke UV setup
vec2 smokeUV = v_objectUV;
smokeUV = rotate(smokeUV, u_angle * PI / 180.);
smokeUV *= mix(4., 1., u_size);
// Two swirl paths: inner (shape-masked) and outer (free), each with independent distortion
vec2 innerUV = smokeUV;
vec2 outerUV = smokeUV;
// Vertical displacement \u2014 applied independently to inner and outer
innerUV.y += u_innerDistortion * (1. - sst(0., 1., length(.4 * innerUV)));
innerUV.y -= .4 * u_innerDistortion;
innerUV.y += .7 * u_offset * roundness;
outerUV.y += u_outerDistortion * (1. - sst(0., 1., length(.4 * outerUV)));
outerUV.y -= .4 * u_outerDistortion;
float innerSwirl = u_innerDistortion * roundness;
float outerSwirl = u_outerDistortion;
for (int i = 1; i < 5; i++) {
float fi = float(i);
float stretchIn = max(length(dFdx(innerUV)), length(dFdy(innerUV)));
float dampenIn = 1. / (1. + stretchIn * 8.);
float sIn = innerSwirl * dampenIn;
innerUV.x += sIn / fi * cos(time + fi * 2.9 * innerUV.y);
innerUV.y += sIn / fi * cos(time + fi * 1.5 * innerUV.x);
float stretchOut = max(length(dFdx(outerUV)), length(dFdy(outerUV)));
float dampenOut = 1. / (1. + stretchOut * 8.);
float sOut = outerSwirl * dampenOut;
outerUV.x += sOut / fi * cos(time + fi * 2.9 * outerUV.y);
outerUV.y += sOut / fi * cos(time + fi * 1.5 * outerUV.x);
}
// Smoke shapes from swirl fields
float innerShape = exp(-1.5 * dot(innerUV, innerUV));
float outerShape = exp(-1.5 * dot(outerUV, outerUV));
// Visibility masks
float outerMask = pow(u_outerGlow, 2.) * (1. - imgAlpha);
float innerMask = (.01 + .99 * u_innerGlow) * imgAlpha;
innerShape *= innerMask;
outerShape *= outerMask;
// Color gradient
float mixer = (innerShape + outerShape) * u_colorsCount;
vec4 gradient = u_colors[0];
gradient.rgb *= gradient.a;
float smokeMask = 0.;
for (int i = 1; i < ${gemSmokeMeta.maxColorCount + 1}; i++) {
if (i > int(u_colorsCount)) break;
float m = sst(0., 1., clamp(mixer - float(i - 1), 0., 1.));
if (i == 1) smokeMask = m;
vec4 c = u_colors[i - 1];
c.rgb *= c.a;
gradient = mix(gradient, c, m);
}
// Compositing (premultiplied alpha, front-to-back)
vec3 color = gradient.rgb * smokeMask;
float opacity = gradient.a * smokeMask;
float innerOpacity = u_colorInner.a * imgAlpha;
vec3 innerColor = u_colorInner.rgb * innerOpacity;
color += innerColor * (1.0 - opacity);
opacity += innerOpacity * (1.0 - opacity);
vec3 backColor = u_colorBack.rgb * u_colorBack.a;
color += backColor * (1.0 - opacity);
opacity += u_colorBack.a * (1.0 - opacity);
fragColor = vec4(color, opacity);
}
`;
const POISSON_CONFIG_OPTIMIZED = {
measurePerformance: false,
// Set to true to see performance metrics
workingSize: 512,
// Size to solve Poisson at (will upscale to original size)
iterations: 32
// SOR converges ~2-20x faster than standard Gauss-Seidel
};
function toProcessedGemSmoke(file) {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const isBlob = typeof file === "string" && file.startsWith("blob:");
return new Promise((resolve, reject) => {
if (!file || !ctx) {
reject(new Error("Invalid file or canvas context"));
return;
}
const blobContentTypePromise = isBlob && fetch(file).then((res) => res.headers.get("Content-Type"));
const img = new Image();
img.crossOrigin = "anonymous";
const totalStartTime = performance.now();
img.onload = async () => {
let isSVG;
const blobContentType = await blobContentTypePromise;
if (blobContentType) {
isSVG = blobContentType === "image/svg+xml";
} else if (typeof file === "string") {
isSVG = file.endsWith(".svg") || file.startsWith("data:image/svg+xml");
} else {
isSVG = file.type === "image/svg+xml";
}
let originalWidth = img.width || img.naturalWidth;
let originalHeight = img.height || img.naturalHeight;
if (isSVG) {
const svgMaxSize = 4096;
const aspectRatio = originalWidth / originalHeight;
if (originalWidth > originalHeight) {
originalWidth = svgMaxSize;
originalHeight = svgMaxSize / aspectRatio;
} else {
originalHeight = svgMaxSize;
originalWidth = svgMaxSize * aspectRatio;
}
img.width = originalWidth;
img.height = originalHeight;
}
const minDimension = Math.min(originalWidth, originalHeight);
const targetSize = POISSON_CONFIG_OPTIMIZED.workingSize;
const scaleFactor = targetSize / minDimension;
const width = Math.round(originalWidth * scaleFactor);
const height = Math.round(originalHeight * scaleFactor);
if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {
console.log(`[Processing Mode]`);
console.log(` Original: ${originalWidth}\xD7${originalHeight}`);
console.log(` Working: ${width}\xD7${height} (${(scaleFactor * 100).toFixed(1)}% scale)`);
if (scaleFactor < 1) {
console.log(` Speedup: ~${Math.round(1 / (scaleFactor * scaleFactor))}\xD7`);
}
}
canvas.width = originalWidth;
canvas.height = originalHeight;
const paddingSize = 0.025;
const padX = Math.ceil(width * paddingSize);
const padY = Math.ceil(height * paddingSize);
const imgW = width - 2 * padX;
const imgH = height - 2 * padY;
const shapeCanvas = document.createElement("canvas");
shapeCanvas.width = width;
shapeCanvas.height = height;
const shapeCtx = shapeCanvas.getContext("2d");
shapeCtx.drawImage(img, padX, padY, imgW, imgH);
const startMask = performance.now();
const shapeImageData = shapeCtx.getImageData(0, 0, width, height);
const data = shapeImageData.data;
const shapeMask = new Uint8Array(width * height);
const boundaryMask = new Uint8Array(width * height);
let shapePixelCount = 0;
for (let i = 0, idx = 0; i < data.length; i += 4, idx++) {
const a = data[i + 3];
const isShape = a === 0 ? 0 : 1;
shapeMask[idx] = isShape;
shapePixelCount += isShape;
}
const boundaryIndices = [];
const interiorIndices = [];
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const idx = y * width + x;
if (!shapeMask[idx]) continue;
let isBoundary = false;
if (x === 0 || x === width - 1 || y === 0 || y === height - 1) {
isBoundary = true;
} else {
isBoundary = !shapeMask[idx - 1] || // left
!shapeMask[idx + 1] || // right
!shapeMask[idx - width] || // top
!shapeMask[idx + width] || // bottom
!shapeMask[idx - width - 1] || // top-left
!shapeMask[idx - width + 1] || // top-right
!shapeMask[idx + width - 1] || // bottom-left
!shapeMask[idx + width + 1];
}
if (isBoundary) {
boundaryMask[idx] = 1;
boundaryIndices.push(idx);
} else {
interiorIndices.push(idx);
}
}
}
if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {
console.log(`[Mask Building] Time: ${(performance.now() - startMask).toFixed(2)}ms`);
console.log(
` Shape pixels: ${shapePixelCount} / ${width * height} (${(shapePixelCount / (width * height) * 100).toFixed(1)}%)`
);
console.log(` Interior pixels: ${interiorIndices.length}`);
console.log(` Boundary pixels: ${boundaryIndices.length}`);
}
const sparseData = buildSparseData(
shapeMask,
boundaryMask,
new Uint32Array(interiorIndices),
new Uint32Array(boundaryIndices),
width,
height
);
const startSolve = performance.now();
const u = solvePoissonSparse(sparseData, shapeMask, boundaryMask, width, height);
if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {
console.log(`[Poisson Solve] Time: ${(performance.now() - startSolve).toFixed(2)}ms`);
}
let maxVal = 0;
let finalImageData;
for (let i = 0; i < interiorIndices.length; i++) {
const idx = interiorIndices[i];
if (u[idx] > maxVal) maxVal = u[idx];
}
const tempCanvas = document.createElement("canvas");
tempCanvas.width = width;
tempCanvas.height = height;
const tempCtx = tempCanvas.getContext("2d");
const tempImg = tempCtx.createImageData(width, height);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const idx = y * width + x;
const px = idx * 4;
if (!shapeMask[idx]) {
tempImg.data[px] = 255;
tempImg.data[px + 1] = 255;
tempImg.data[px + 2] = 255;
tempImg.data[px + 3] = 0;
} else {
const poissonRatio = u[idx] / maxVal;
let gray = 255 * (1 - poissonRatio);
tempImg.data[px] = gray;
tempImg.data[px + 1] = gray;
tempImg.data[px + 2] = gray;
tempImg.data[px + 3] = 255;
}
}
}
tempCtx.putImageData(tempImg, 0, 0);
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = "high";
ctx.drawImage(tempCanvas, 0, 0, width, height, 0, 0, originalWidth, originalHeight);
const outImg = ctx.getImageData(0, 0, originalWidth, originalHeight);
const origPadX = Math.ceil(originalWidth * paddingSize);
const origPadY = Math.ceil(originalHeight * paddingSize);
const originalCanvas = document.createElement("canvas");
originalCanvas.width = originalWidth;
originalCanvas.height = originalHeight;
const originalCtx = originalCanvas.getContext("2d");
originalCtx.drawImage(img, origPadX, origPadY, originalWidth - 2 * origPadX, originalHeight - 2 * origPadY);
const originalData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);
for (let i = 0; i < outImg.data.length; i += 4) {
const a = originalData.data[i + 3];
const upscaledAlpha = outImg.data[i + 3];
if (a === 0) {
outImg.data[i] = 255;
outImg.data[i + 1] = 0;
} else {
outImg.data[i] = upscaledAlpha === 0 ? 0 : outImg.data[i];
outImg.data[i + 1] = a;
}
outImg.data[i + 2] = 255;
outImg.data[i + 3] = 255;
}
ctx.putImageData(outImg, 0, 0);
finalImageData = outImg;
canvas.toBlob((blob) => {
if (!blob) {
reject(new Error("Failed to create PNG blob"));
return;
}
if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {
const totalTime = performance.now() - totalStartTime;
console.log(`[Total Processing Time] ${totalTime.toFixed(2)}ms`);
if (scaleFactor < 1) {
const estimatedFullResTime = totalTime * Math.pow(originalWidth * originalHeight / (width * height), 1.5);
console.log(`[Estimated time at full resolution] ~${estimatedFullResTime.toFixed(0)}ms`);
console.log(
`[Time saved] ~${(estimatedFullResTime - totalTime).toFixed(0)}ms (${Math.round(estimatedFullResTime / totalTime)}\xD7 faster)`
);
}
}
resolve({
imageData: finalImageData,
pngBlob: blob
});
}, "image/png");
};
img.onerror = () => reject(new Error("Failed to load image"));
img.src = typeof file === "string" ? file : URL.createObjectURL(file);
});
}
function buildSparseData(shapeMask, boundaryMask, interiorPixels, boundaryPixels, width, height) {
const pixelCount = interiorPixels.length;
const neighborIndices = new Int32Array(pixelCount * 4);
for (let i = 0; i < pixelCount; i++) {
const idx = interiorPixels[i];
const x = idx % width;
const y = Math.floor(idx / width);
neighborIndices[i * 4 + 0] = x < width - 1 && shapeMask[idx + 1] ? idx + 1 : -1;
neighborIndices[i * 4 + 1] = x > 0 && shapeMask[idx - 1] ? idx - 1 : -1;
neighborIndices[i * 4 + 2] = y > 0 && shapeMask[idx - width] ? idx - width : -1;
neighborIndices[i * 4 + 3] = y < height - 1 && shapeMask[idx + width] ? idx + width : -1;
}
return {
interiorPixels,
boundaryPixels,
pixelCount,
neighborIndices
};
}
function solvePoissonSparse(sparseData, shapeMask, boundaryMask, width, height) {
const ITERATIONS = POISSON_CONFIG_OPTIMIZED.iterations;
const C = 0.01;
const u = new Float32Array(width * height);
const { interiorPixels, neighborIndices, pixelCount } = sparseData;
const startTime = performance.now();
const omega = 1.9;
const redPixels = [];
const blackPixels = [];
for (let i = 0; i < pixelCount; i++) {
const idx = interiorPixels[i];
const x = idx % width;
const y = Math.floor(idx / width);
if ((x + y) % 2 === 0) {
redPixels.push(i);
} else {
blackPixels.push(i);
}
}
for (let iter = 0; iter < ITERATIONS; iter++) {
for (const i of redPixels) {
const idx = interiorPixels[i];
const eastIdx = neighborIndices[i * 4 + 0];
const westIdx = neighborIndices[i * 4 + 1];
const northIdx = neighborIndices[i * 4 + 2];
const southIdx = neighborIndices[i * 4 + 3];
let sumN = 0;
if (eastIdx >= 0) sumN += u[eastIdx];
if (westIdx >= 0) sumN += u[westIdx];
if (northIdx >= 0) sumN += u[northIdx];
if (southIdx >= 0) sumN += u[southIdx];
const newValue = (C + sumN) / 4;
u[idx] = omega * newValue + (1 - omega) * u[idx];
}
for (const i of blackPixels) {
const idx = interiorPixels[i];
const eastIdx = neighborIndices[i * 4 + 0];
const westIdx = neighborIndices[i * 4 + 1];
const northIdx = neighborIndices[i * 4 + 2];
const southIdx = neighborIndices[i * 4 + 3];
let sumN = 0;
if (eastIdx >= 0) sumN += u[eastIdx];
if (westIdx >= 0) sumN += u[westIdx];
if (northIdx >= 0) sumN += u[northIdx];
if (southIdx >= 0) sumN += u[southIdx];
const newValue = (C + sumN) / 4;
u[idx] = omega * newValue + (1 - omega) * u[idx];
}
}
const tmp = new Float32Array(width * height);
for (let smooth = 0; smooth < 3; smooth++) {
tmp.set(u);
for (let i = 0; i < pixelCount; i++) {
const idx = interiorPixels[i];
const eastIdx = neighborIndices[i * 4 + 0];
const westIdx = neighborIndices[i * 4 + 1];
const northIdx = neighborIndices[i * 4 + 2];
const southIdx = neighborIndices[i * 4 + 3];
let sum = 0;
let count = 0;
if (eastIdx >= 0) {
sum += tmp[eastIdx];
count++;
}
if (westIdx >= 0) {
sum += tmp[westIdx];
count++;
}
if (northIdx >= 0) {
sum += tmp[northIdx];
count++;
}
if (southIdx >= 0) {
sum += tmp[southIdx];
count++;
}
u[idx] = count > 0 ? (tmp[idx] + sum / count) * 0.5 : tmp[idx];
}
}
if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {
const elapsed = performance.now() - startTime;
console.log(`[Optimized Poisson Solver (SOR \u03C9=${omega})]`);
console.log(` Working size: ${width}\xD7${height}`);
console.log(` Iterations: ${ITERATIONS}`);
console.log(` Time: ${elapsed.toFixed(2)}ms`);
console.log(` Interior pixels processed: ${pixelCount}`);
console.log(` Speed: ${(ITERATIONS * pixelCount / (elapsed * 1e3)).toFixed(2)} Mpixels/sec`);
}
return u;
}
const GemSmokeShapes = {
none: 0,
circle: 1,
daisy: 2,
diamond: 3,
metaballs: 4
};
export {
GemSmokeShapes,
POISSON_CONFIG_OPTIMIZED,
gemSmokeFragmentShader,
gemSmokeMeta,
toProcessedGemSmoke
};
//# sourceMappingURL=gem-smoke.js.map
+7
View File
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../src/shaders/gem-smoke.ts"],
"sourcesContent": ["import type { vec4 } from '../types.js';\nimport type { ShaderMotionParams } from '../shader-mount.js';\nimport type { ShaderSizingParams, ShaderSizingUniforms } from '../shader-sizing.js';\nimport { rotation2, declarePI } from '../shader-utils.js';\n\nexport const gemSmokeMeta = {\n maxColorCount: 6,\n} as const;\n\n/**\n * Animated color fields placed over uploaded logo shape; gives the illusion of smoky noise\n * behind the glassy shape.\n *\n * Fragment shader uniforms:\n * - u_time (float): Animation time\n * - u_resolution (vec2): Canvas resolution in pixels\n * - u_image (sampler2D): Pre-processed source image texture (R = edge gradient, G = alpha)\n * - u_imageAspectRatio (float): Aspect ratio of the source image\n * - u_colors (vec4[]): Up to 6 smoke colors in RGBA\n * - u_colorsCount (float): Number of active colors\n * - u_colorBack (vec4): Background color in RGBA\n * - u_innerDistortion (float): Power of smoke distortion inside the input shape (0 to 1)\n * - u_outerDistortion (float): Power of smoke distortion outside the input shape (0 to 1)\n * - u_outerGlow (float): Visibility of smoke shape outside the input shape (0 to 1)\n * - u_innerGlow (float): Visibility of smoke shape inside the input shape (0 to 1)\n * - u_colorInner (vec4): Additional color inside the input shape, mixing with smoke (RGBA)\n * - u_offset (float): Vertical offset of smoke inside the shape (-1 to 1)\n * - u_angle (float): Smoke direction in degrees (0 to 360)\n * - u_size (float): Size of smoke shape relative to the image box (0 to 1)\n *\n * Vertex shader outputs (used in fragment shader):\n * - v_imageUV (vec2): UV coordinates for sampling the source image, with fit, scale, rotation, and offset applied\n * - v_objectUV (vec2): Normalized UV coordinates with scale, rotation, and offset applied\n *\n * Vertex shader uniforms:\n * - u_resolution (vec2): Canvas resolution in pixels\n * - u_pixelRatio (float): Device pixel ratio\n * - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)\n * - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)\n * - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas\n * - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas\n * - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)\n * - u_scale (float): Overall zoom level of the graphics (0.01 to 4)\n * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)\n * - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)\n * - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)\n * - u_imageAspectRatio (float): Aspect ratio of the source image\n *\n */\n\n// language=GLSL\nexport const gemSmokeFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nin mediump vec2 v_imageUV;\nin mediump vec2 v_objectUV;\nin mediump vec2 v_responsiveUV;\nin mediump vec2 v_responsiveBoxGivenSize;\nout vec4 fragColor;\n\n// Image\nuniform sampler2D u_image;\nuniform float u_imageAspectRatio;\n\n// Canvas\nuniform vec2 u_resolution;\nuniform float u_time;\n\n// Colors\nuniform vec4 u_colors[${gemSmokeMeta.maxColorCount}];\nuniform float u_colorsCount;\nuniform vec4 u_colorBack;\nuniform vec4 u_colorInner;\n\n// Effect controls\nuniform float u_innerDistortion;\nuniform float u_outerDistortion;\nuniform float u_outerGlow;\nuniform float u_innerGlow;\nuniform float u_offset;\nuniform float u_angle;\nuniform float u_size;\n\n// Shape controls\nuniform float u_shape;\nuniform bool u_isImage;\n\n${ declarePI }\n${ rotation2 }\n\n// 9x9 Gaussian blur on R and G channels\nvec2 gaussBlur9x9RG(sampler2D tex, vec2 uv, vec2 dudx, vec2 dudy, float radius) {\n vec2 texel = 1.0 / vec2(textureSize(tex, 0));\n vec2 r = max(radius, 0.0) * texel;\n // Pascal's row 8: sum = 256, 2D norm = 65536\n const float k[9] = float[9](1.0, 8.0, 28.0, 56.0, 70.0, 56.0, 28.0, 8.0, 1.0);\n vec2 sum = vec2(0.0);\n\n for (int j = -4; j <= 4; ++j) {\n float wy = k[j + 4];\n for (int i = -4; i <= 4; ++i) {\n float w = k[i + 4] * wy;\n vec2 off = vec2(float(i) * r.x, float(j) * r.y);\n sum += w * texture(tex, uv + off).rg;\n }\n }\n\n return sum / 65536.0;\n}\n\nfloat sst(float a, float b, float x) {\n return smoothstep(a, b, x);\n}\n\nvoid main() {\n float time = u_time;\n\n float roundness = 0.;\n float imgAlpha = 0.;\n\n if (u_isImage == true) {\n // Image sampling (UV scaled inward to account for padding)\n vec2 imageUV = v_imageUV;\n imageUV -= .5;\n imageUV *= .95;\n imageUV += .5;\n\n vec2 dudx = dFdx(v_imageUV);\n vec2 dudy = dFdy(v_imageUV);\n\n // Blurred image: x = roundness, y = alpha\n vec2 blurred = gaussBlur9x9RG(u_image, imageUV, dudx, dudy, 10.);\n roundness = 1. - blurred.x;\n vec2 texelA = 1.0 / vec2(textureSize(u_image, Line truncated
"mappings": "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 truncated
"names": []
}
+67
View File
@@ -0,0 +1,67 @@
import type { vec4 } from '../types.js';
import type { ShaderMotionParams } from '../shader-mount.js';
import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';
export declare const godRaysMeta: {
readonly maxColorCount: 5;
};
/**
* Animated rays of light radiating from the center, blended with up to 5 colors.
*
* Fragment shader uniforms:
* - u_time (float): Animation time
* - u_colorBack (vec4): Background color in RGBA
* - u_colorBloom (vec4): Color overlay blended with the rays in RGBA
* - u_colors (vec4[]): Up to 5 ray colors in RGBA
* - u_colorsCount (float): Number of active colors
* - u_bloom (float): Strength of the bloom/overlay effect, 0 = alpha blend, 1 = additive blend (0 to 1)
* - u_intensity (float): Visibility/strength of the rays (0 to 1)
* - u_density (float): The number of rays (0 to 1)
* - u_spotty (float): The length of the rays, higher = more spots/shorter rays (0 to 1)
* - u_midSize (float): Size of the circular glow shape in the center (0 to 1)
* - u_midIntensity (float): Brightness/intensity of the central glow (0 to 1)
* - u_noiseTexture (sampler2D): Pre-computed randomizer source texture
*
* Vertex shader outputs (used in fragment shader):
* - v_objectUV (vec2): Object box UV coordinates with global sizing (scale, rotation, offsets, etc) applied
*
* The rays are adjustable by size, density, brightness and center glow.
*
* Vertex shader uniforms:
* - u_resolution (vec2): Canvas resolution in pixels
* - u_pixelRatio (float): Device pixel ratio
* - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)
* - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)
* - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas
* - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas
* - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)
* - u_scale (float): Overall zoom level of the graphics (0.01 to 4)
* - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
* - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)
* - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)
*
*/
export declare const godRaysFragmentShader: string;
export interface GodRaysUniforms extends ShaderSizingUniforms {
u_colorBack: [number, number, number, number];
u_colorBloom: [number, number, number, number];
u_colors: vec4[];
u_colorsCount: number;
u_spotty: number;
u_midSize: number;
u_midIntensity: number;
u_density: number;
u_intensity: number;
u_bloom: number;
u_noiseTexture?: HTMLImageElement;
}
export interface GodRaysParams extends ShaderSizingParams, ShaderMotionParams {
colorBack?: string;
colorBloom?: string;
colors?: string[];
spotty?: number;
midSize?: number;
midIntensity?: number;
density?: number;
intensity?: number;
bloom?: number;
}
+133
View File
@@ -0,0 +1,133 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
import {} from "../shader-sizing.js";
import { declarePI, rotation2, textureRandomizerR, colorBandingFix, proceduralHash11 } from "../shader-utils.js";
const godRaysMeta = {
maxColorCount: 5
};
const godRaysFragmentShader = `#version 300 es
precision mediump float;
uniform float u_time;
uniform sampler2D u_noiseTexture;
uniform vec4 u_colorBack;
uniform vec4 u_colorBloom;
uniform vec4 u_colors[${godRaysMeta.maxColorCount}];
uniform float u_colorsCount;
uniform float u_density;
uniform float u_spotty;
uniform float u_midSize;
uniform float u_midIntensity;
uniform float u_intensity;
uniform float u_bloom;
in vec2 v_objectUV;
out vec4 fragColor;
${declarePI}
${rotation2}
${textureRandomizerR}
float valueNoise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = randomR(i);
float b = randomR(i + vec2(1.0, 0.0));
float c = randomR(i + vec2(0.0, 1.0));
float d = randomR(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
float x1 = mix(a, b, u.x);
float x2 = mix(c, d, u.x);
return mix(x1, x2, u.y);
}
${proceduralHash11}
float raysShape(vec2 uv, float r, float freq, float intensity, float radius) {
float a = atan(uv.y, uv.x);
vec2 left = vec2(a * freq, r);
vec2 right = vec2(fract(a / TWO_PI) * TWO_PI * freq, r);
float n_left = pow(valueNoise(left), intensity);
float n_right = pow(valueNoise(right), intensity);
float shape = mix(n_right, n_left, smoothstep(-.15, .15, uv.x));
return shape;
}
void main() {
vec2 shape_uv = v_objectUV;
float t = .2 * u_time;
float radius = length(shape_uv);
float spots = 6.5 * abs(u_spotty);
float intensity = 4. - 3. * clamp(u_intensity, 0., 1.);
float delta = 1. - smoothstep(0., 1., radius);
float midSize = 10. * abs(u_midSize);
float ms_lo = 0.02 * midSize;
float ms_hi = max(midSize, 1e-6);
float middleShape = pow(u_midIntensity, 0.3) * (1. - smoothstep(ms_lo, ms_hi, 3.0 * radius));
middleShape = pow(middleShape, 5.0);
vec3 accumColor = vec3(0.0);
float accumAlpha = 0.0;
for (int i = 0; i < ${godRaysMeta.maxColorCount}; i++) {
if (i >= int(u_colorsCount)) break;
vec2 rotatedUV = rotate(shape_uv, float(i) + 1.0);
float r1 = radius * (1.0 + 0.4 * float(i)) - 3.0 * t;
float r2 = 0.5 * radius * (1.0 + spots) - 2.0 * t;
float density = 6. * u_density + step(.5, u_density) * pow(4.5 * (u_density - .5), 4.);
float f = mix(1.0, 3.0 + 0.5 * float(i), hash11(float(i) * 15.)) * density;
float ray = raysShape(rotatedUV, r1, 5.0 * f, intensity, radius);
ray *= raysShape(rotatedUV, r2, 4.0 * f, intensity, radius);
ray += (1. + 4. * ray) * middleShape;
ray = clamp(ray, 0.0, 1.0);
float srcAlpha = u_colors[i].a * ray;
vec3 srcColor = u_colors[i].rgb * srcAlpha;
vec3 alphaBlendColor = accumColor + (1.0 - accumAlpha) * srcColor;
float alphaBlendAlpha = accumAlpha + (1.0 - accumAlpha) * srcAlpha;
vec3 addBlendColor = accumColor + srcColor;
float addBlendAlpha = accumAlpha + srcAlpha;
accumColor = mix(alphaBlendColor, addBlendColor, u_bloom);
accumAlpha = mix(alphaBlendAlpha, addBlendAlpha, u_bloom);
}
float overlayAlpha = u_colorBloom.a;
vec3 overlayColor = u_colorBloom.rgb * overlayAlpha;
vec3 colorWithOverlay = accumColor + accumAlpha * overlayColor;
accumColor = mix(accumColor, colorWithOverlay, u_bloom);
vec3 bgColor = u_colorBack.rgb * u_colorBack.a;
vec3 color = accumColor + (1. - accumAlpha) * bgColor;
float opacity = accumAlpha + (1. - accumAlpha) * u_colorBack.a;
color = clamp(color, 0., 1.);
opacity = clamp(opacity, 0., 1.);
${colorBandingFix}
fragColor = vec4(color, opacity);
}
`;
export {
godRaysFragmentShader,
godRaysMeta
};
//# sourceMappingURL=god-rays.js.map
+7
View File
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../src/shaders/god-rays.ts"],
"sourcesContent": ["import type { vec4 } from '../types.js';\nimport type { ShaderMotionParams } from '../shader-mount.js';\nimport { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';\nimport { declarePI, rotation2, textureRandomizerR, colorBandingFix, proceduralHash11 } from '../shader-utils.js';\n\nexport const godRaysMeta = {\n maxColorCount: 5,\n} as const;\n\n/**\n * Animated rays of light radiating from the center, blended with up to 5 colors.\n *\n * Fragment shader uniforms:\n * - u_time (float): Animation time\n * - u_colorBack (vec4): Background color in RGBA\n * - u_colorBloom (vec4): Color overlay blended with the rays in RGBA\n * - u_colors (vec4[]): Up to 5 ray colors in RGBA\n * - u_colorsCount (float): Number of active colors\n * - u_bloom (float): Strength of the bloom/overlay effect, 0 = alpha blend, 1 = additive blend (0 to 1)\n * - u_intensity (float): Visibility/strength of the rays (0 to 1)\n * - u_density (float): The number of rays (0 to 1)\n * - u_spotty (float): The length of the rays, higher = more spots/shorter rays (0 to 1)\n * - u_midSize (float): Size of the circular glow shape in the center (0 to 1)\n * - u_midIntensity (float): Brightness/intensity of the central glow (0 to 1)\n * - u_noiseTexture (sampler2D): Pre-computed randomizer source texture\n *\n * Vertex shader outputs (used in fragment shader):\n * - v_objectUV (vec2): Object box UV coordinates with global sizing (scale, rotation, offsets, etc) applied\n *\n * The rays are adjustable by size, density, brightness and center glow.\n *\n * Vertex shader uniforms:\n * - u_resolution (vec2): Canvas resolution in pixels\n * - u_pixelRatio (float): Device pixel ratio\n * - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)\n * - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)\n * - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas\n * - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas\n * - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)\n * - u_scale (float): Overall zoom level of the graphics (0.01 to 4)\n * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)\n * - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)\n * - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)\n *\n */\n\n// language=GLSL\nexport const godRaysFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform float u_time;\n\nuniform sampler2D u_noiseTexture;\n\nuniform vec4 u_colorBack;\nuniform vec4 u_colorBloom;\nuniform vec4 u_colors[${ godRaysMeta.maxColorCount }];\nuniform float u_colorsCount;\n\nuniform float u_density;\nuniform float u_spotty;\nuniform float u_midSize;\nuniform float u_midIntensity;\nuniform float u_intensity;\nuniform float u_bloom;\n\nin vec2 v_objectUV;\n\nout vec4 fragColor;\n\n${ declarePI }\n${ rotation2 }\n${ textureRandomizerR }\nfloat valueNoise(vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = randomR(i);\n float b = randomR(i + vec2(1.0, 0.0));\n float c = randomR(i + vec2(0.0, 1.0));\n float d = randomR(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n float x1 = mix(a, b, u.x);\n float x2 = mix(c, d, u.x);\n return mix(x1, x2, u.y);\n}\n\n${ proceduralHash11 }\n\nfloat raysShape(vec2 uv, float r, float freq, float intensity, float radius) {\n float a = atan(uv.y, uv.x);\n vec2 left = vec2(a * freq, r);\n vec2 right = vec2(fract(a / TWO_PI) * TWO_PI * freq, r);\n float n_left = pow(valueNoise(left), intensity);\n float n_right = pow(valueNoise(right), intensity);\n float shape = mix(n_right, n_left, smoothstep(-.15, .15, uv.x));\n return shape;\n}\n\nvoid main() {\n vec2 shape_uv = v_objectUV;\n\n float t = .2 * u_time;\n\n float radius = length(shape_uv);\n float spots = 6.5 * abs(u_spotty);\n\n float intensity = 4. - 3. * clamp(u_intensity, 0., 1.);\n\n float delta = 1. - smoothstep(0., 1., radius);\n\n float midSize = 10. * abs(u_midSize);\n float ms_lo = 0.02 * midSize;\n float ms_hi = max(midSize, 1e-6);\n float middleShape = pow(u_midIntensity, 0.3) * (1. - smoothstep(ms_lo, ms_hi, 3.0 * radius));\n middleShape = pow(middleShape, 5.0);\n\n vec3 accumColor = vec3(0.0);\n float accumAlpha = 0.0;\n\n for (int i = 0; i < ${ godRaysMeta.maxColorCount }; i++) {\n if (i >= int(u_colorsCount)) break;\n\n vec2 rotatedUV = rotate(shape_uv, float(i) + 1.0);\n\n float r1 = radius * (1.0 + 0.4 * float(i)) - 3.0 * t;\n float r2 = 0.5 * radius * (1.0 + spots) - 2.0 * t;\n float density = 6. * u_density + step(.5, u_density) * pow(4.5 * (u_density - .5), 4.);\n float f = mix(1.0, 3.0 + 0.5 * float(i), hash11(float(i) * 15.)) * density;\n\n float ray = raysShape(rotatedUV, r1, 5.0 * f, intensity, radius);\n ray *= rayLine truncated
"mappings": ";;;;;AAEA,eAAmE;AACnE,SAAS,WAAW,WAAW,oBAAoB,iBAAiB,wBAAwB;AAErF,MAAM,cAAc;AAAA,EACzB,eAAe;AACjB;AAwCO,MAAM,wBAAgC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBASpB,YAAY,aAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAchD,SAAU;AAAA,EACV,SAAU;AAAA,EACV,kBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcnB,gBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAiCK,YAAY,aAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAyC9C,eAAgB;AAAA;AAAA;AAAA;AAAA;",
"names": []
}
+82
View File
@@ -0,0 +1,82 @@
import type { vec4 } from '../types.js';
import type { ShaderMotionParams } from '../shader-mount.js';
import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';
export declare const grainGradientMeta: {
readonly maxColorCount: 7;
};
/**
* Multi-color gradients with grainy, noise-textured distortion available in 7 animated abstract forms.
*
* Note: grains are calculated using gl_FragCoord & u_resolution, meaning grains don't react to scaling and fit
*
* Fragment shader uniforms:
* - u_time (float): Animation time
* - u_resolution (vec2): Canvas resolution in pixels
* - u_pixelRatio (float): Device pixel ratio
* - u_originX (float): Reference point for positioning (0 to 1)
* - u_originY (float): Reference point for positioning (0 to 1)
* - u_worldWidth (float): Virtual width of the graphic
* - u_worldHeight (float): Virtual height of the graphic
* - u_fit (float): Fit mode (0 = none, 1 = contain, 2 = cover)
* - u_scale (float): Overall zoom level (0.01 to 4)
* - u_rotation (float): Rotation angle in degrees (0 to 360)
* - u_offsetX (float): Horizontal offset (-1 to 1)
* - u_offsetY (float): Vertical offset (-1 to 1)
* - u_colorBack (vec4): Background color in RGBA
* - u_colors (vec4[]): Up to 7 gradient colors in RGBA
* - u_colorsCount (float): Number of active colors
* - u_softness (float): Color transition sharpness, 0 = hard edge, 1 = smooth gradient (0 to 1)
* - u_intensity (float): Distortion between color bands (0 to 1)
* - u_noise (float): Grainy noise overlay (0 to 1)
* - u_shape (float): Shape type (1 = wave, 2 = dots, 3 = truchet, 4 = corners, 5 = ripple, 6 = blob, 7 = sphere)
* - u_noiseTexture (sampler2D): Pre-computed randomizer source texture
*
* Vertex shader outputs (used in fragment shader):
* - v_objectUV (vec2): Object box UV coordinates with global sizing (scale, rotation, offsets, etc) applied (used for shapes 4-7)
* - v_objectBoxSize (vec2): Size of the object bounding box in pixels
* - v_patternUV (vec2): UV coordinates for pattern with global sizing (rotation, scale, offset, etc) applied (used for shapes 1-3)
* - v_patternBoxSize (vec2): Size of the pattern bounding box in pixels
*
* Vertex shader uniforms:
* - u_resolution (vec2): Canvas resolution in pixels
* - u_pixelRatio (float): Device pixel ratio
* - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)
* - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)
* - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas
* - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas
* - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)
* - u_scale (float): Overall zoom level of the graphics (0.01 to 4)
* - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
* - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)
* - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)
*
*/
export declare const grainGradientFragmentShader: string;
export interface GrainGradientUniforms extends ShaderSizingUniforms {
u_colorBack: [number, number, number, number];
u_colors: vec4[];
u_colorsCount: number;
u_softness: number;
u_intensity: number;
u_noise: number;
u_shape: (typeof GrainGradientShapes)[GrainGradientShape];
u_noiseTexture?: HTMLImageElement;
}
export interface GrainGradientParams extends ShaderSizingParams, ShaderMotionParams {
colorBack?: string;
colors?: string[];
softness?: number;
intensity?: number;
noise?: number;
shape?: GrainGradientShape;
}
export declare const GrainGradientShapes: {
wave: number;
dots: number;
truchet: number;
corners: number;
ripple: number;
blob: number;
sphere: number;
};
export type GrainGradientShape = keyof typeof GrainGradientShapes;
+303
View File
@@ -0,0 +1,303 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
import {
} from "../shader-sizing.js";
import {
simplexNoise,
declarePI,
rotation2,
textureRandomizerR,
proceduralHash11
} from "../shader-utils.js";
const grainGradientMeta = {
maxColorCount: 7
};
const grainGradientFragmentShader = `#version 300 es
precision lowp float;
uniform mediump float u_time;
uniform mediump vec2 u_resolution;
uniform mediump float u_pixelRatio;
uniform sampler2D u_noiseTexture;
uniform vec4 u_colorBack;
uniform vec4 u_colors[${grainGradientMeta.maxColorCount}];
uniform float u_colorsCount;
uniform float u_softness;
uniform float u_intensity;
uniform float u_noise;
uniform float u_shape;
uniform mediump float u_originX;
uniform mediump float u_originY;
uniform mediump float u_worldWidth;
uniform mediump float u_worldHeight;
uniform mediump float u_fit;
uniform mediump float u_scale;
uniform mediump float u_rotation;
uniform mediump float u_offsetX;
uniform mediump float u_offsetY;
in vec2 v_objectUV;
in vec2 v_patternUV;
in vec2 v_objectBoxSize;
in vec2 v_patternBoxSize;
out vec4 fragColor;
${declarePI}
${simplexNoise}
${rotation2}
${textureRandomizerR}
float valueNoiseR(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = randomR(i);
float b = randomR(i + vec2(1.0, 0.0));
float c = randomR(i + vec2(0.0, 1.0));
float d = randomR(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
float x1 = mix(a, b, u.x);
float x2 = mix(c, d, u.x);
return mix(x1, x2, u.y);
}
vec4 fbmR(vec2 n0, vec2 n1, vec2 n2, vec2 n3) {
float amplitude = 0.2;
vec4 total = vec4(0.);
for (int i = 0; i < 3; i++) {
n0 = rotate(n0, 0.3);
n1 = rotate(n1, 0.3);
n2 = rotate(n2, 0.3);
n3 = rotate(n3, 0.3);
total.x += valueNoiseR(n0) * amplitude;
total.y += valueNoiseR(n1) * amplitude;
total.z += valueNoiseR(n2) * amplitude;
total.z += valueNoiseR(n3) * amplitude;
n0 *= 1.99;
n1 *= 1.99;
n2 *= 1.99;
n3 *= 1.99;
amplitude *= 0.6;
}
return total;
}
${proceduralHash11}
vec2 truchet(vec2 uv, float idx){
idx = fract(((idx - .5) * 2.));
if (idx > 0.75) {
uv = vec2(1.0) - uv;
} else if (idx > 0.5) {
uv = vec2(1.0 - uv.x, uv.y);
} else if (idx > 0.25) {
uv = 1.0 - vec2(1.0 - uv.x, uv.y);
}
return uv;
}
void main() {
const float firstFrameOffset = 7.;
float t = .1 * (u_time + firstFrameOffset);
vec2 shape_uv = vec2(0.);
vec2 grain_uv = vec2(0.);
float r = u_rotation * PI / 180.;
float cr = cos(r);
float sr = sin(r);
mat2 graphicRotation = mat2(cr, sr, -sr, cr);
vec2 graphicOffset = vec2(-u_offsetX, u_offsetY);
if (u_shape > 3.5) {
shape_uv = v_objectUV;
grain_uv = shape_uv;
// apply inverse transform to grain_uv so it respects the originXY
grain_uv = transpose(graphicRotation) * grain_uv;
grain_uv *= u_scale;
grain_uv -= graphicOffset;
grain_uv *= v_objectBoxSize;
grain_uv *= .7;
} else {
shape_uv = .5 * v_patternUV;
grain_uv = 100. * v_patternUV;
// apply inverse transform to grain_uv so it respects the originXY
grain_uv = transpose(graphicRotation) * grain_uv;
grain_uv *= u_scale;
if (u_fit > 0.) {
vec2 givenBoxSize = vec2(u_worldWidth, u_worldHeight);
givenBoxSize = max(givenBoxSize, vec2(1.)) * u_pixelRatio;
float patternBoxRatio = givenBoxSize.x / givenBoxSize.y;
vec2 patternBoxGivenSize = vec2(
(u_worldWidth == 0.) ? u_resolution.x : givenBoxSize.x,
(u_worldHeight == 0.) ? u_resolution.y : givenBoxSize.y
);
patternBoxRatio = patternBoxGivenSize.x / patternBoxGivenSize.y;
float patternBoxNoFitBoxWidth = patternBoxRatio * min(patternBoxGivenSize.x / patternBoxRatio, patternBoxGivenSize.y);
grain_uv /= (patternBoxNoFitBoxWidth / v_patternBoxSize.x);
}
vec2 patternBoxScale = u_resolution.xy / v_patternBoxSize;
grain_uv -= graphicOffset / patternBoxScale;
grain_uv *= 1.6;
}
float shape = 0.;
if (u_shape < 1.5) {
// Sine wave
float wave = cos(.5 * shape_uv.x - 4. * t) * sin(1.5 * shape_uv.x + 2. * t) * (.75 + .25 * cos(6. * t));
shape = 1. - smoothstep(-1., 1., shape_uv.y + wave);
} else if (u_shape < 2.5) {
// Grid (dots)
float stripeIdx = floor(2. * shape_uv.x / TWO_PI);
float rand = hash11(stripeIdx * 100.);
rand = sign(rand - .5) * pow(4. * abs(rand), .3);
shape = sin(shape_uv.x) * cos(shape_uv.y - 5. * rand * t);
shape = pow(abs(shape), 4.);
} else if (u_shape < 3.5) {
// Truchet pattern
float n2 = valueNoiseR(shape_uv * .4 - 3.75 * t);
shape_uv.x += 10.;
shape_uv *= .6;
vec2 tile = truchet(fract(shape_uv), randomR(floor(shape_uv)));
float distance1 = length(tile);
float distance2 = length(tile - vec2(1.));
n2 -= .5;
n2 *= .1;
shape = smoothstep(.2, .55, distance1 + n2) * (1. - smoothstep(.45, .8, distance1 - n2));
shape += smoothstep(.2, .55, distance2 + n2) * (1. - smoothstep(.45, .8, distance2 - n2));
shape = pow(shape, 1.5);
} else if (u_shape < 4.5) {
// Corners
shape_uv *= .6;
vec2 outer = vec2(.5);
vec2 bl = smoothstep(vec2(0.), outer, shape_uv + vec2(.1 + .1 * sin(3. * t), .2 - .1 * sin(5.25 * t)));
vec2 tr = smoothstep(vec2(0.), outer, 1. - shape_uv);
shape = 1. - bl.x * bl.y * tr.x * tr.y;
shape_uv = -shape_uv;
bl = smoothstep(vec2(0.), outer, shape_uv + vec2(.1 + .1 * sin(3. * t), .2 - .1 * cos(5.25 * t)));
tr = smoothstep(vec2(0.), outer, 1. - shape_uv);
shape -= bl.x * bl.y * tr.x * tr.y;
shape = 1. - smoothstep(0., 1., shape);
} else if (u_shape < 5.5) {
// Ripple
shape_uv *= 2.;
float dist = length(.4 * shape_uv);
float waves = sin(pow(dist, 1.2) * 5. - 3. * t) * .5 + .5;
shape = waves;
} else if (u_shape < 6.5) {
// Blob
t *= 2.;
vec2 f1_traj = .25 * vec2(1.3 * sin(t), .2 + 1.3 * cos(.6 * t + 4.));
vec2 f2_traj = .2 * vec2(1.2 * sin(-t), 1.3 * sin(1.6 * t));
vec2 f3_traj = .25 * vec2(1.7 * cos(-.6 * t), cos(-1.6 * t));
vec2 f4_traj = .3 * vec2(1.4 * cos(.8 * t), 1.2 * sin(-.6 * t - 3.));
shape = .5 * pow(1. - clamp(0., 1., length(shape_uv + f1_traj)), 5.);
shape += .5 * pow(1. - clamp(0., 1., length(shape_uv + f2_traj)), 5.);
shape += .5 * pow(1. - clamp(0., 1., length(shape_uv + f3_traj)), 5.);
shape += .5 * pow(1. - clamp(0., 1., length(shape_uv + f4_traj)), 5.);
shape = smoothstep(.0, .9, shape);
float edge = smoothstep(.25, .3, shape);
shape = mix(.0, shape, edge);
} else {
// Sphere
shape_uv *= 2.;
float d = 1. - pow(length(shape_uv), 2.);
vec3 pos = vec3(shape_uv, sqrt(max(d, 0.)));
vec3 lightPos = normalize(vec3(cos(1.5 * t), .8, sin(1.25 * t)));
shape = .5 + .5 * dot(lightPos, pos);
shape *= step(0., d);
}
float baseNoise = snoise(grain_uv * .5);
vec4 fbmVals = fbmR(
.002 * grain_uv + 10.,
.003 * grain_uv,
.001 * grain_uv,
rotate(.4 * grain_uv, 2.)
);
float grainDist = baseNoise * snoise(grain_uv * .2) - fbmVals.x - fbmVals.y;
float rawNoise = .75 * baseNoise - fbmVals.w - fbmVals.z;
float noise = clamp(rawNoise, 0., 1.);
shape += u_intensity * 2. / u_colorsCount * (grainDist + .5);
shape += u_noise * 10. / u_colorsCount * noise;
float aa = fwidth(shape);
shape = clamp(shape - .5 / u_colorsCount, 0., 1.);
float totalShape = smoothstep(0., u_softness + 2. * aa, clamp(shape * u_colorsCount, 0., 1.));
float mixer = shape * (u_colorsCount - 1.);
int cntStop = int(u_colorsCount) - 1;
vec4 gradient = u_colors[0];
gradient.rgb *= gradient.a;
for (int i = 1; i < ${grainGradientMeta.maxColorCount}; i++) {
if (i > cntStop) break;
float localT = clamp(mixer - float(i - 1), 0., 1.);
localT = smoothstep(.5 - .5 * u_softness - aa, .5 + .5 * u_softness + aa, localT);
vec4 c = u_colors[i];
c.rgb *= c.a;
gradient = mix(gradient, c, localT);
}
vec3 color = gradient.rgb * totalShape;
float opacity = gradient.a * totalShape;
vec3 bgColor = u_colorBack.rgb * u_colorBack.a;
color = color + bgColor * (1.0 - opacity);
opacity = opacity + u_colorBack.a * (1.0 - opacity);
fragColor = vec4(color, opacity);
}
`;
const GrainGradientShapes = {
wave: 1,
dots: 2,
truchet: 3,
corners: 4,
ripple: 5,
blob: 6,
sphere: 7
};
export {
GrainGradientShapes,
grainGradientFragmentShader,
grainGradientMeta
};
//# sourceMappingURL=grain-gradient.js.map
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../src/shaders/grain-gradient.ts"],
"sourcesContent": ["import type { vec4 } from '../types.js';\nimport type { ShaderMotionParams } from '../shader-mount.js';\nimport {\n type ShaderSizingParams,\n type ShaderSizingUniforms,\n} from '../shader-sizing.js';\nimport {\n simplexNoise,\n declarePI,\n rotation2,\n textureRandomizerR,\n proceduralHash11,\n} from '../shader-utils.js';\n\nexport const grainGradientMeta = {\n maxColorCount: 7,\n} as const;\n\n/**\n * Multi-color gradients with grainy, noise-textured distortion available in 7 animated abstract forms.\n *\n * Note: grains are calculated using gl_FragCoord & u_resolution, meaning grains don't react to scaling and fit\n *\n * Fragment shader uniforms:\n * - u_time (float): Animation time\n * - u_resolution (vec2): Canvas resolution in pixels\n * - u_pixelRatio (float): Device pixel ratio\n * - u_originX (float): Reference point for positioning (0 to 1)\n * - u_originY (float): Reference point for positioning (0 to 1)\n * - u_worldWidth (float): Virtual width of the graphic\n * - u_worldHeight (float): Virtual height of the graphic\n * - u_fit (float): Fit mode (0 = none, 1 = contain, 2 = cover)\n * - u_scale (float): Overall zoom level (0.01 to 4)\n * - u_rotation (float): Rotation angle in degrees (0 to 360)\n * - u_offsetX (float): Horizontal offset (-1 to 1)\n * - u_offsetY (float): Vertical offset (-1 to 1)\n * - u_colorBack (vec4): Background color in RGBA\n * - u_colors (vec4[]): Up to 7 gradient colors in RGBA\n * - u_colorsCount (float): Number of active colors\n * - u_softness (float): Color transition sharpness, 0 = hard edge, 1 = smooth gradient (0 to 1)\n * - u_intensity (float): Distortion between color bands (0 to 1)\n * - u_noise (float): Grainy noise overlay (0 to 1)\n * - u_shape (float): Shape type (1 = wave, 2 = dots, 3 = truchet, 4 = corners, 5 = ripple, 6 = blob, 7 = sphere)\n * - u_noiseTexture (sampler2D): Pre-computed randomizer source texture\n *\n * Vertex shader outputs (used in fragment shader):\n * - v_objectUV (vec2): Object box UV coordinates with global sizing (scale, rotation, offsets, etc) applied (used for shapes 4-7)\n * - v_objectBoxSize (vec2): Size of the object bounding box in pixels\n * - v_patternUV (vec2): UV coordinates for pattern with global sizing (rotation, scale, offset, etc) applied (used for shapes 1-3)\n * - v_patternBoxSize (vec2): Size of the pattern bounding box in pixels\n *\n * Vertex shader uniforms:\n * - u_resolution (vec2): Canvas resolution in pixels\n * - u_pixelRatio (float): Device pixel ratio\n * - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)\n * - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)\n * - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas\n * - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas\n * - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)\n * - u_scale (float): Overall zoom level of the graphics (0.01 to 4)\n * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)\n * - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)\n * - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)\n *\n */\n\n// language=GLSL\nexport const grainGradientFragmentShader: string = `#version 300 es\nprecision lowp float;\n\nuniform mediump float u_time;\nuniform mediump vec2 u_resolution;\nuniform mediump float u_pixelRatio;\n\nuniform sampler2D u_noiseTexture;\n\nuniform vec4 u_colorBack;\nuniform vec4 u_colors[${ grainGradientMeta.maxColorCount }];\nuniform float u_colorsCount;\nuniform float u_softness;\nuniform float u_intensity;\nuniform float u_noise;\nuniform float u_shape;\n\nuniform mediump float u_originX;\nuniform mediump float u_originY;\nuniform mediump float u_worldWidth;\nuniform mediump float u_worldHeight;\nuniform mediump float u_fit;\n\nuniform mediump float u_scale;\nuniform mediump float u_rotation;\nuniform mediump float u_offsetX;\nuniform mediump float u_offsetY;\n\nin vec2 v_objectUV;\nin vec2 v_patternUV;\nin vec2 v_objectBoxSize;\nin vec2 v_patternBoxSize;\n\nout vec4 fragColor;\n\n${ declarePI }\n${ simplexNoise }\n${ rotation2 }\n${ textureRandomizerR }\n\nfloat valueNoiseR(vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = randomR(i);\n float b = randomR(i + vec2(1.0, 0.0));\n float c = randomR(i + vec2(0.0, 1.0));\n float d = randomR(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n float x1 = mix(a, b, u.x);\n float x2 = mix(c, d, u.x);\n return mix(x1, x2, u.y);\n}\nvec4 fbmR(vec2 n0, vec2 n1, vec2 n2, vec2 n3) {\n float amplitude = 0.2;\n vec4 total = vec4(0.);\n for (int i = 0; i < 3; i++) {\n n0 = rotate(n0, 0.3);\n n1 = rotate(n1, 0.3);\n n2 = rotate(n2, 0.3);\n n3 = rotate(n3, 0.3);\n total.x += valueNoiseR(n0) * amplitude;\n total.y += valueNoiseR(nLine truncated
"mappings": ";;;;;AAEA;AAAA,OAGO;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,oBAAoB;AAAA,EAC/B,eAAe;AACjB;AAmDO,MAAM,8BAAsC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAU1B,kBAAkB,aAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyBtD,SAAU;AAAA,EACV,YAAa;AAAA,EACb,SAAU;AAAA,EACV,kBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmCnB,gBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAiLK,kBAAkB,aAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0ClD,MAAM,sBAAsB;AAAA,EACjC,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,QAAQ;AACV;",
"names": []
}
+105
View File
@@ -0,0 +1,105 @@
import type { ShaderMotionParams } from '../shader-mount.js';
import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';
/**
* CMYK halftone printing effect applied to images with customizable dot patterns
* and ink colors for each channel (Cyan, Magenta, Yellow, Black).
*
* Fragment shader uniforms:
* - u_image (sampler2D): Source image texture
* - u_imageAspectRatio (float): Aspect ratio of the source image
* - u_colorBack (vec4): Background (paper) color in RGBA
* - u_colorC (vec4): Cyan ink color in RGBA
* - u_colorM (vec4): Magenta ink color in RGBA
* - u_colorY (vec4): Yellow ink color in RGBA
* - u_colorK (vec4): Black ink color in RGBA
* - u_size (float): Halftone cell size (0 to 1)
* - u_minDot (float): Minimum dot thickness (0 to 1)
* - u_contrast (float): Image contrast adjustment (0 to 2)
* - u_softness (float): Edge softness of dots (0 to 1)
* - u_grainSize (float): Size of grain overlay texture (0 to 1)
* - u_grainMixer (float): Strength of grain affecting dot size (0 to 1)
* - u_grainOverlay (float): Strength of grain overlay on final output (0 to 1)
* - u_gridNoise (float): Strength of smooth noise applied to both dot positions and color sampling (0 to 1)
* - u_floodC (float): Flat cyan dot size adjustment applied uniformly (-1 to 1)
* - u_floodM (float): Flat magenta dot size adjustment applied uniformly (-1 to 1)
* - u_floodY (float): Flat yellow dot size adjustment applied uniformly (-1 to 1)
* - u_floodK (float): Flat black dot size adjustment applied uniformly (-1 to 1)
* - u_gainC (float): Proportional cyan dot size gain (enhances existing dots, -1 to 1)
* - u_gainM (float): Proportional magenta dot size gain (enhances existing dots, -1 to 1)
* - u_gainY (float): Proportional yellow dot size gain (enhances existing dots, -1 to 1)
* - u_gainK (float): Proportional black dot size gain (enhances existing dots, -1 to 1)
* - u_type (float): Dot shape style (0 = dots, 1 = ink, 2 = sharp)
* - u_noiseTexture (sampler2D): Pre-computed randomizer source texture
*
* Vertex shader outputs (used in fragment shader):
* - v_imageUV (vec2): UV coordinates for sampling the source image, with fit, scale, rotation, and offset applied
*
* Vertex shader uniforms:
* - u_resolution (vec2): Canvas resolution in pixels
* - u_pixelRatio (float): Device pixel ratio
* - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)
* - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)
* - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)
* - u_scale (float): Overall zoom level of the graphics (0.01 to 4)
* - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
* - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)
* - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)
* - u_imageAspectRatio (float): Aspect ratio of the source image
*
*/
export declare const halftoneCmykFragmentShader: string;
export interface HalftoneCmykUniforms extends ShaderSizingUniforms {
u_image: HTMLImageElement | string | undefined;
u_noiseTexture?: HTMLImageElement;
u_colorBack: [number, number, number, number];
u_colorC: [number, number, number, number];
u_colorM: [number, number, number, number];
u_colorY: [number, number, number, number];
u_colorK: [number, number, number, number];
u_size: number;
u_contrast: number;
u_softness: number;
u_grainSize: number;
u_grainMixer: number;
u_grainOverlay: number;
u_gridNoise: number;
u_floodC: number;
u_floodM: number;
u_floodY: number;
u_floodK: number;
u_gainC: number;
u_gainM: number;
u_gainY: number;
u_gainK: number;
u_type: (typeof HalftoneCmykTypes)[HalftoneCmykType];
}
export interface HalftoneCmykParams extends ShaderSizingParams, ShaderMotionParams {
image?: HTMLImageElement | string;
colorBack?: string;
colorC?: string;
colorM?: string;
colorY?: string;
colorK?: string;
size?: number;
contrast?: number;
softness?: number;
grainSize?: number;
grainMixer?: number;
grainOverlay?: number;
gridNoise?: number;
floodC?: number;
floodM?: number;
floodY?: number;
floodK?: number;
gainC?: number;
gainM?: number;
gainY?: number;
gainK?: number;
type?: HalftoneCmykType;
}
export declare const HalftoneCmykTypes: {
readonly dots: 0;
readonly ink: 1;
readonly sharp: 2;
};
export type HalftoneCmykType = keyof typeof HalftoneCmykTypes;
+292
View File
@@ -0,0 +1,292 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
import {} from "../shader-sizing.js";
import { declarePI } from "../shader-utils.js";
const halftoneCmykFragmentShader = `#version 300 es
precision mediump float;
uniform sampler2D u_image;
uniform float u_imageAspectRatio;
uniform vec4 u_colorBack;
uniform vec4 u_colorC;
uniform vec4 u_colorM;
uniform vec4 u_colorY;
uniform vec4 u_colorK;
uniform float u_size;
uniform float u_minDot;
uniform float u_contrast;
uniform float u_grainSize;
uniform float u_grainMixer;
uniform float u_grainOverlay;
uniform float u_gridNoise;
uniform float u_softness;
uniform float u_floodC;
uniform float u_floodM;
uniform float u_floodY;
uniform float u_floodK;
uniform float u_gainC;
uniform float u_gainM;
uniform float u_gainY;
uniform float u_gainK;
uniform float u_type;
uniform sampler2D u_noiseTexture;
in vec2 v_imageUV;
out vec4 fragColor;
const float shiftC = -.5;
const float shiftM = -.25;
const float shiftY = .2;
const float shiftK = 0.;
// Precomputed sin/cos for rotation angles (15\xB0, 75\xB0, 0\xB0, 45\xB0)
const float cosC = 0.9659258; const float sinC = 0.2588190; // 15\xB0
const float cosM = 0.2588190; const float sinM = 0.9659258; // 75\xB0
const float cosY = 1.0; const float sinY = 0.0; // 0\xB0
const float cosK = 0.7071068; const float sinK = 0.7071068; // 45\xB0
${declarePI}
vec2 randomRG(vec2 p) {
vec2 uv = floor(p) / 100. + .5;
return texture(u_noiseTexture, fract(uv)).rg;
}
vec3 hash23(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * vec3(0.3183099, 0.3678794, 0.3141592)) + 0.1;
p3 += dot(p3, p3.yzx + 19.19);
return fract(vec3(p3.x * p3.y, p3.y * p3.z, p3.z * p3.x));
}
float sst(float edge0, float edge1, float x) {
return smoothstep(edge0, edge1, x);
}
vec3 valueNoise3(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
vec3 a = hash23(i);
vec3 b = hash23(i + vec2(1.0, 0.0));
vec3 c = hash23(i + vec2(0.0, 1.0));
vec3 d = hash23(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
vec3 x1 = mix(a, b, u.x);
vec3 x2 = mix(c, d, u.x);
return mix(x1, x2, u.y);
}
float getUvFrame(vec2 uv, vec2 pad) {
float left = smoothstep(-pad.x, 0., uv.x);
float right = smoothstep(1. + pad.x, 1., uv.x);
float bottom = smoothstep(-pad.y, 0., uv.y);
float top = smoothstep(1. + pad.y, 1., uv.y);
return left * right * bottom * top;
}
vec4 RGBAtoCMYK(vec4 rgba) {
float k = 1. - max(max(rgba.r, rgba.g), rgba.b);
float denom = 1. - k;
vec3 cmy = vec3(0.);
if (denom > 1e-5) {
cmy = (1. - rgba.rgb - vec3(k)) / denom;
}
return vec4(cmy, k) * rgba.a;
}
vec3 applyContrast(vec3 rgb) {
return clamp((rgb - 0.5) * u_contrast + 0.5, 0.0, 1.0);
}
// Single-component CMYK extractors with contrast built-in, alpha-aware
float getCyan(vec4 rgba) {
vec3 c = clamp((rgba.rgb - 0.5) * u_contrast + 0.5, 0.0, 1.0);
float maxRGB = max(max(c.r, c.g), c.b);
return (maxRGB > 1e-5 ? (maxRGB - c.r) / maxRGB : 0.) * rgba.a;
}
float getMagenta(vec4 rgba) {
vec3 c = clamp((rgba.rgb - 0.5) * u_contrast + 0.5, 0.0, 1.0);
float maxRGB = max(max(c.r, c.g), c.b);
return (maxRGB > 1e-5 ? (maxRGB - c.g) / maxRGB : 0.) * rgba.a;
}
float getYellow(vec4 rgba) {
vec3 c = clamp((rgba.rgb - 0.5) * u_contrast + 0.5, 0.0, 1.0);
float maxRGB = max(max(c.r, c.g), c.b);
return (maxRGB > 1e-5 ? (maxRGB - c.b) / maxRGB : 0.) * rgba.a;
}
float getBlack(vec4 rgba) {
vec3 c = clamp((rgba.rgb - 0.5) * u_contrast + 0.5, 0.0, 1.0);
return (1. - max(max(c.r, c.g), c.b)) * rgba.a;
}
vec2 cellCenterPos(vec2 uv, vec2 cellOffset, float channelIdx) {
vec2 cellCenter = floor(uv) + .5 + cellOffset;
return cellCenter + (randomRG(cellCenter + channelIdx * 50.) - .5) * u_gridNoise;
}
vec2 gridToImageUV(vec2 cellCenter, float cosA, float sinA, float shift, vec2 pad) {
vec2 uvGrid = mat2(cosA, -sinA, sinA, cosA) * (cellCenter - shift);
return uvGrid * pad + 0.5;
}
void colorMask(vec2 pos, vec2 cellCenter, float rad, float transparency, float grain, float channelAddon, float channelgain, float generalComp, bool isJoined, inout float outMask) {
float dist = length(pos - cellCenter);
float radius = rad;
radius *= (1. + generalComp);
radius += (.15 + channelgain * radius);
radius = max(0., radius);
radius = mix(0., radius, transparency);
radius += channelAddon;
radius *= (1. - grain);
float mask = 1. - sst(0., radius, dist);
if (isJoined) {
// ink or sharp (joined)
mask = pow(mask, 1.2);
} else {
// dots (separate)
mask = sst(.5 - .5 * u_softness, .51 + .49 * u_softness, mask);
}
mask *= mix(1., mix(.5, 1., 1.5 * radius), u_softness);
outMask += mask;
}
vec3 applyInk(vec3 paper, vec3 inkColor, float cov) {
vec3 inkEffect = mix(vec3(1.0), inkColor, clamp(cov, 0.0, 1.0));
return paper * inkEffect;
}
void main() {
vec2 uv = v_imageUV;
float cellsPerSide = mix(400.0, 7.0, pow(u_size, 0.7));
float cellSizeY = 1.0 / cellsPerSide;
vec2 pad = cellSizeY * vec2(1.0 / u_imageAspectRatio, 1.0);
vec2 uvGrid = (uv - .5) / pad;
float insideImageBox = getUvFrame(uv, pad);
float generalComp = .1 * u_softness + .1 * u_gridNoise + .1 * (1. - step(0.5, u_type)) * (1.5 - u_softness);
vec2 uvC = mat2(cosC, sinC, -sinC, cosC) * uvGrid + shiftC;
vec2 uvM = mat2(cosM, sinM, -sinM, cosM) * uvGrid + shiftM;
vec2 uvY = mat2(cosY, sinY, -sinY, cosY) * uvGrid + shiftY;
vec2 uvK = mat2(cosK, sinK, -sinK, cosK) * uvGrid + shiftK;
vec2 grainSize = mix(2000., 200., u_grainSize) * vec2(1., 1. / u_imageAspectRatio);
vec2 grainUV = (v_imageUV - .5) * grainSize + .5;
vec3 noiseValues = valueNoise3(grainUV);
float grain = sst(.55, 1., noiseValues.r);
grain *= u_grainMixer;
vec4 outMask = vec4(0.);
bool isJoined = u_type > 0.5;
if (u_type < 1.5) {
// dots or ink: per-cell color sampling
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
vec2 cellOffset = vec2(float(dx), float(dy));
vec2 cellCenterC = cellCenterPos(uvC, cellOffset, 0.);
vec4 texC = texture(u_image, gridToImageUV(cellCenterC, cosC, sinC, shiftC, pad));
colorMask(uvC, cellCenterC, getCyan(texC), insideImageBox * texC.a, grain, u_floodC, u_gainC, generalComp, isJoined, outMask[0]);
vec2 cellCenterM = cellCenterPos(uvM, cellOffset, 1.);
vec4 texM = texture(u_image, gridToImageUV(cellCenterM, cosM, sinM, shiftM, pad));
colorMask(uvM, cellCenterM, getMagenta(texM), insideImageBox * texM.a, grain, u_floodM, u_gainM, generalComp, isJoined, outMask[1]);
vec2 cellCenterY = cellCenterPos(uvY, cellOffset, 2.);
vec4 texY = texture(u_image, gridToImageUV(cellCenterY, cosY, sinY, shiftY, pad));
colorMask(uvY, cellCenterY, getYellow(texY), insideImageBox * texY.a, grain, u_floodY, u_gainY, generalComp, isJoined, outMask[2]);
vec2 cellCenterK = cellCenterPos(uvK, cellOffset, 3.);
vec4 texK = texture(u_image, gridToImageUV(cellCenterK, cosK, sinK, shiftK, pad));
colorMask(uvK, cellCenterK, getBlack(texK), insideImageBox * texK.a, grain, u_floodK, u_gainK, generalComp, isJoined, outMask[3]);
}
}
} else {
// sharp: direct px color sampling
vec4 tex = texture(u_image, uv);
tex.rgb = applyContrast(tex.rgb);
insideImageBox *= tex.a;
vec4 cmykOriginal = RGBAtoCMYK(tex);
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
vec2 cellOffset = vec2(float(dx), float(dy));
colorMask(uvC, cellCenterPos(uvC, cellOffset, 0.), cmykOriginal.x, insideImageBox, grain, u_floodC, u_gainC, generalComp, isJoined, outMask[0]);
colorMask(uvM, cellCenterPos(uvM, cellOffset, 1.), cmykOriginal.y, insideImageBox, grain, u_floodM, u_gainM, generalComp, isJoined, outMask[1]);
colorMask(uvY, cellCenterPos(uvY, cellOffset, 2.), cmykOriginal.z, insideImageBox, grain, u_floodY, u_gainY, generalComp, isJoined, outMask[2]);
colorMask(uvK, cellCenterPos(uvK, cellOffset, 3.), cmykOriginal.w, insideImageBox, grain, u_floodK, u_gainK, generalComp, isJoined, outMask[3]);
}
}
}
float shape;
float C = outMask[0];
float M = outMask[1];
float Y = outMask[2];
float K = outMask[3];
if (isJoined) {
// ink or sharp: apply threshold for joined dots
float th = .5;
float sLeft = th * u_softness;
float sRight = (1. - th) * u_softness + .01;
C = smoothstep(th - sLeft - fwidth(C), th + sRight, C);
M = smoothstep(th - sLeft - fwidth(M), th + sRight, M);
Y = smoothstep(th - sLeft - fwidth(Y), th + sRight, Y);
K = smoothstep(th - sLeft - fwidth(K), th + sRight, K);
}
C *= u_colorC.a;
M *= u_colorM.a;
Y *= u_colorY.a;
K *= u_colorK.a;
vec3 ink = vec3(1.);
ink = applyInk(ink, u_colorK.rgb, K);
ink = applyInk(ink, u_colorC.rgb, C);
ink = applyInk(ink, u_colorM.rgb, M);
ink = applyInk(ink, u_colorY.rgb, Y);
shape = clamp(max(max(C, M), max(Y, K)), 0., 1.);
vec3 color = u_colorBack.rgb * u_colorBack.a;
float opacity = u_colorBack.a;
color = mix(color, ink, shape);
opacity += shape;
opacity = clamp(opacity, 0., 1.);
float grainOverlay = mix(noiseValues.g, noiseValues.b, .5);
grainOverlay = pow(grainOverlay, 1.3);
float grainOverlayV = grainOverlay * 2. - 1.;
vec3 grainOverlayColor = vec3(step(0., grainOverlayV));
float grainOverlayStrength = u_grainOverlay * abs(grainOverlayV);
grainOverlayStrength = pow(grainOverlayStrength, .8);
color = mix(color, grainOverlayColor, .5 * grainOverlayStrength);
opacity += .5 * grainOverlayStrength;
opacity = clamp(opacity, 0., 1.);
fragColor = vec4(color, opacity);
}
`;
const HalftoneCmykTypes = {
dots: 0,
ink: 1,
sharp: 2
};
export {
HalftoneCmykTypes,
halftoneCmykFragmentShader
};
//# sourceMappingURL=halftone-cmyk.js.map
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../src/shaders/halftone-cmyk.ts"],
"sourcesContent": ["import type { ShaderMotionParams } from '../shader-mount.js';\nimport { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';\nimport { declarePI } from '../shader-utils.js';\n\n/**\n * CMYK halftone printing effect applied to images with customizable dot patterns\n * and ink colors for each channel (Cyan, Magenta, Yellow, Black).\n *\n * Fragment shader uniforms:\n * - u_image (sampler2D): Source image texture\n * - u_imageAspectRatio (float): Aspect ratio of the source image\n * - u_colorBack (vec4): Background (paper) color in RGBA\n * - u_colorC (vec4): Cyan ink color in RGBA\n * - u_colorM (vec4): Magenta ink color in RGBA\n * - u_colorY (vec4): Yellow ink color in RGBA\n * - u_colorK (vec4): Black ink color in RGBA\n * - u_size (float): Halftone cell size (0 to 1)\n * - u_minDot (float): Minimum dot thickness (0 to 1)\n * - u_contrast (float): Image contrast adjustment (0 to 2)\n * - u_softness (float): Edge softness of dots (0 to 1)\n * - u_grainSize (float): Size of grain overlay texture (0 to 1)\n * - u_grainMixer (float): Strength of grain affecting dot size (0 to 1)\n * - u_grainOverlay (float): Strength of grain overlay on final output (0 to 1)\n * - u_gridNoise (float): Strength of smooth noise applied to both dot positions and color sampling (0 to 1)\n * - u_floodC (float): Flat cyan dot size adjustment applied uniformly (-1 to 1)\n * - u_floodM (float): Flat magenta dot size adjustment applied uniformly (-1 to 1)\n * - u_floodY (float): Flat yellow dot size adjustment applied uniformly (-1 to 1)\n * - u_floodK (float): Flat black dot size adjustment applied uniformly (-1 to 1)\n * - u_gainC (float): Proportional cyan dot size gain (enhances existing dots, -1 to 1)\n * - u_gainM (float): Proportional magenta dot size gain (enhances existing dots, -1 to 1)\n * - u_gainY (float): Proportional yellow dot size gain (enhances existing dots, -1 to 1)\n * - u_gainK (float): Proportional black dot size gain (enhances existing dots, -1 to 1)\n * - u_type (float): Dot shape style (0 = dots, 1 = ink, 2 = sharp)\n * - u_noiseTexture (sampler2D): Pre-computed randomizer source texture\n *\n * Vertex shader outputs (used in fragment shader):\n * - v_imageUV (vec2): UV coordinates for sampling the source image, with fit, scale, rotation, and offset applied\n *\n * Vertex shader uniforms:\n * - u_resolution (vec2): Canvas resolution in pixels\n * - u_pixelRatio (float): Device pixel ratio\n * - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)\n * - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)\n * - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)\n * - u_scale (float): Overall zoom level of the graphics (0.01 to 4)\n * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)\n * - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)\n * - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)\n * - u_imageAspectRatio (float): Aspect ratio of the source image\n *\n */\n\n// language=GLSL\nexport const halftoneCmykFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform sampler2D u_image;\nuniform float u_imageAspectRatio;\n\nuniform vec4 u_colorBack;\nuniform vec4 u_colorC;\nuniform vec4 u_colorM;\nuniform vec4 u_colorY;\nuniform vec4 u_colorK;\nuniform float u_size;\nuniform float u_minDot;\nuniform float u_contrast;\nuniform float u_grainSize;\nuniform float u_grainMixer;\nuniform float u_grainOverlay;\nuniform float u_gridNoise;\nuniform float u_softness;\nuniform float u_floodC;\nuniform float u_floodM;\nuniform float u_floodY;\nuniform float u_floodK;\nuniform float u_gainC;\nuniform float u_gainM;\nuniform float u_gainY;\nuniform float u_gainK;\nuniform float u_type;\nuniform sampler2D u_noiseTexture;\n\nin vec2 v_imageUV;\nout vec4 fragColor;\n\nconst float shiftC = -.5;\nconst float shiftM = -.25;\nconst float shiftY = .2;\nconst float shiftK = 0.;\n\n// Precomputed sin/cos for rotation angles (15\u00B0, 75\u00B0, 0\u00B0, 45\u00B0)\nconst float cosC = 0.9659258; const float sinC = 0.2588190; // 15\u00B0\nconst float cosM = 0.2588190; const float sinM = 0.9659258; // 75\u00B0\nconst float cosY = 1.0; const float sinY = 0.0; // 0\u00B0\nconst float cosK = 0.7071068; const float sinK = 0.7071068; // 45\u00B0\n\n${ declarePI }\n\nvec2 randomRG(vec2 p) {\n vec2 uv = floor(p) / 100. + .5;\n return texture(u_noiseTexture, fract(uv)).rg;\n}\nvec3 hash23(vec2 p) {\n vec3 p3 = fract(vec3(p.xyx) * vec3(0.3183099, 0.3678794, 0.3141592)) + 0.1;\n p3 += dot(p3, p3.yzx + 19.19);\n return fract(vec3(p3.x * p3.y, p3.y * p3.z, p3.z * p3.x));\n}\n\nfloat sst(float edge0, float edge1, float x) {\n return smoothstep(edge0, edge1, x);\n}\n\nvec3 valueNoise3(vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n vec3 a = hash23(Line truncated
"mappings": ";;;;;AACA,eAAmE;AACnE,SAAS,iBAAiB;AAmDnB,MAAM,6BAAqC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4C/C,SAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2RN,MAAM,oBAAoB;AAAA,EAC/B,MAAM;AAAA,EACN,KAAK;AAAA,EACL,OAAO;AACT;",
"names": []
}
+82
View File
@@ -0,0 +1,82 @@
import type { ShaderMotionParams } from '../shader-mount.js';
import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';
/**
* A halftone-dot image filter featuring customizable grids, color palettes, and dot styles.
*
* Fragment shader uniforms:
* - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
* - u_time (float): Animation time
* - u_image (sampler2D): Source image texture
* - u_imageAspectRatio (float): Aspect ratio of the source image
* - u_colorFront (vec4): Foreground color in RGBA
* - u_colorBack (vec4): Background color in RGBA
* - u_originalColors (bool): Use sampled image's original colors instead of colorFront
* - u_type (float): Dot style (0 = classic, 1 = gooey, 2 = holes, 3 = soft)
* - u_inverted (bool): Inverts the image luminance, doesn't affect the color scheme; not effective at zero contrast
* - u_grid (float): Grid type (0 = square, 1 = hex)
* - u_size (float): Grid size relative to the image box (0 to 1)
* - u_radius (float): Maximum dot size relative to grid cell (0 to 2)
* - u_contrast (float): Contrast applied to the sampled image (0 to 1)
* - u_grainMixer (float): Strength of grain distortion applied to shape edges (0 to 1)
* - u_grainOverlay (float): Post-processing black/white grain overlay (0 to 1)
* - u_grainSize (float): Scale applied to both grain distortion and grain overlay (0 to 1)
*
* Vertex shader outputs (used in fragment shader):
* - v_imageUV (vec2): Image UV coordinates with global sizing (rotation, scale, offset, etc) applied
*
* Vertex shader uniforms:
* - u_resolution (vec2): Canvas resolution in pixels
* - u_pixelRatio (float): Device pixel ratio
* - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)
* - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)
* - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)
* - u_scale (float): Overall zoom level of the graphics (0.01 to 4)
* - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
* - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)
* - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)
* - u_imageAspectRatio (float): Aspect ratio of the source image
*
*/
export declare const halftoneDotsFragmentShader: string;
export interface HalftoneDotsUniforms extends ShaderSizingUniforms {
u_image: HTMLImageElement | string | undefined;
u_colorFront: [number, number, number, number];
u_colorBack: [number, number, number, number];
u_size: number;
u_grid: (typeof HalftoneDotsGrids)[HalftoneDotsGrid];
u_radius: number;
u_contrast: number;
u_originalColors: boolean;
u_inverted: boolean;
u_grainMixer: number;
u_grainOverlay: number;
u_grainSize: number;
u_type: (typeof HalftoneDotsTypes)[HalftoneDotsType];
}
export interface HalftoneDotsParams extends ShaderSizingParams, ShaderMotionParams {
image?: HTMLImageElement | string;
colorFront?: string;
colorBack?: string;
size?: number;
grid?: HalftoneDotsGrid;
radius?: number;
contrast?: number;
originalColors?: boolean;
inverted?: boolean;
grainMixer?: number;
grainOverlay?: number;
grainSize?: number;
type?: HalftoneDotsType;
}
export declare const HalftoneDotsTypes: {
readonly classic: 0;
readonly gooey: 1;
readonly holes: 2;
readonly soft: 3;
};
export type HalftoneDotsType = keyof typeof HalftoneDotsTypes;
export declare const HalftoneDotsGrids: {
readonly square: 0;
readonly hex: 1;
};
export type HalftoneDotsGrid = keyof typeof HalftoneDotsGrids;
+321
View File
@@ -0,0 +1,321 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
import {} from "../shader-sizing.js";
import { declarePI, rotation2, proceduralHash21 } from "../shader-utils.js";
const halftoneDotsFragmentShader = `#version 300 es
precision mediump float;
uniform float u_rotation;
uniform float u_time;
uniform vec4 u_colorFront;
uniform vec4 u_colorBack;
uniform float u_radius;
uniform float u_contrast;
uniform sampler2D u_image;
uniform float u_imageAspectRatio;
uniform float u_size;
uniform float u_grainMixer;
uniform float u_grainOverlay;
uniform float u_grainSize;
uniform float u_grid;
uniform bool u_originalColors;
uniform bool u_inverted;
uniform float u_type;
in vec2 v_imageUV;
out vec4 fragColor;
${declarePI}
${rotation2}
${proceduralHash21}
float valueNoise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = hash21(i);
float b = hash21(i + vec2(1.0, 0.0));
float c = hash21(i + vec2(0.0, 1.0));
float d = hash21(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
float x1 = mix(a, b, u.x);
float x2 = mix(c, d, u.x);
return mix(x1, x2, u.y);
}
float lst(float edge0, float edge1, float x) {
return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
}
float sst(float edge0, float edge1, float x) {
return smoothstep(edge0, edge1, x);
}
float getCircle(vec2 uv, float r, float baseR) {
r = mix(.25 * baseR, 0., r);
float d = length(uv - .5);
float aa = fwidth(d);
return 1. - smoothstep(r - aa, r + aa, d);
}
float getCell(vec2 uv) {
float insideX = step(0.0, uv.x) * (1.0 - step(1.0, uv.x));
float insideY = step(0.0, uv.y) * (1.0 - step(1.0, uv.y));
return insideX * insideY;
}
float getCircleWithHole(vec2 uv, float r, float baseR) {
float cell = getCell(uv);
r = mix(.75 * baseR, 0., r);
float rMod = mod(r, .5);
float d = length(uv - .5);
float aa = fwidth(d);
float circle = 1. - smoothstep(rMod - aa, rMod + aa, d);
if (r < .5) {
return circle;
} else {
return cell - circle;
}
}
float getGooeyBall(vec2 uv, float r, float baseR) {
float d = length(uv - .5);
float sizeRadius = .3;
if (u_grid == 1.) {
sizeRadius = .42;
}
sizeRadius = mix(sizeRadius * baseR, 0., r);
d = 1. - sst(0., sizeRadius, d);
d = pow(d, 2. + baseR);
return d;
}
float getSoftBall(vec2 uv, float r, float baseR) {
float d = length(uv - .5);
float sizeRadius = clamp(baseR, 0., 1.);
sizeRadius = mix(.5 * sizeRadius, 0., r);
d = 1. - lst(0., sizeRadius, d);
float powRadius = 1. - lst(0., 2., baseR);
d = pow(d, 4. + 3. * powRadius);
return d;
}
float getUvFrame(vec2 uv, vec2 pad) {
float aa = 0.0001;
float left = smoothstep(-pad.x, -pad.x + aa, uv.x);
float right = smoothstep(1.0 + pad.x, 1.0 + pad.x - aa, uv.x);
float bottom = smoothstep(-pad.y, -pad.y + aa, uv.y);
float top = smoothstep(1.0 + pad.y, 1.0 + pad.y - aa, uv.y);
return left * right * bottom * top;
}
float sigmoid(float x, float k) {
return 1.0 / (1.0 + exp(-k * (x - 0.5)));
}
float getLumAtPx(vec2 uv, float contrast) {
vec4 tex = texture(u_image, uv);
vec3 color = vec3(
sigmoid(tex.r, contrast),
sigmoid(tex.g, contrast),
sigmoid(tex.b, contrast)
);
float lum = dot(vec3(0.2126, 0.7152, 0.0722), color);
lum = mix(1., lum, tex.a);
lum = u_inverted ? (1. - lum) : lum;
return lum;
}
float getLumBall(vec2 p, vec2 pad, vec2 inCellOffset, float contrast, float baseR, float stepSize, out vec4 ballColor) {
p += inCellOffset;
vec2 uv_i = floor(p);
vec2 uv_f = fract(p);
vec2 samplingUV = (uv_i + .5 - inCellOffset) * pad + vec2(.5);
float outOfFrame = getUvFrame(samplingUV, pad * stepSize);
float lum = getLumAtPx(samplingUV, contrast);
ballColor = texture(u_image, samplingUV);
ballColor.rgb *= ballColor.a;
ballColor *= outOfFrame;
float ball = 0.;
if (u_type == 0.) {
// classic
ball = getCircle(uv_f, lum, baseR);
} else if (u_type == 1.) {
// gooey
ball = getGooeyBall(uv_f, lum, baseR);
} else if (u_type == 2.) {
// holes
ball = getCircleWithHole(uv_f, lum, baseR);
} else if (u_type == 3.) {
// soft
ball = getSoftBall(uv_f, lum, baseR);
}
return ball * outOfFrame;
}
void main() {
float stepMultiplier = 1.;
if (u_type == 0.) {
// classic
stepMultiplier = 2.;
} else if (u_type == 1. || u_type == 3.) {
// gooey & soft
stepMultiplier = 6.;
}
float cellsPerSide = mix(300., 7., pow(u_size, .7));
cellsPerSide /= stepMultiplier;
float cellSizeY = 1. / cellsPerSide;
vec2 pad = cellSizeY * vec2(1. / u_imageAspectRatio, 1.);
if (u_type == 1. && u_grid == 1.) {
// gooey diagonal grid works differently
pad *= .7;
}
vec2 uv = v_imageUV;
uv -= vec2(.5);
uv /= pad;
float contrast = mix(0., 15., pow(u_contrast, 1.5));
float baseRadius = u_radius;
if (u_originalColors == true) {
contrast = mix(.1, 4., pow(u_contrast, 2.));
baseRadius = 2. * pow(.5 * u_radius, .3);
}
float totalShape = 0.;
vec3 totalColor = vec3(0.);
float totalOpacity = 0.;
vec4 ballColor;
float shape;
float stepSize = 1. / stepMultiplier;
for (float x = -0.5; x < 0.5; x += stepSize) {
for (float y = -0.5; y < 0.5; y += stepSize) {
vec2 offset = vec2(x, y);
if (u_grid == 1.) {
float rowIndex = floor((y + .5) / stepSize);
float colIndex = floor((x + .5) / stepSize);
if (stepSize == 1.) {
rowIndex = floor(uv.y + y + 1.);
if (u_type == 1.) {
colIndex = floor(uv.x + x + 1.);
}
}
if (u_type == 1.) {
if (mod(rowIndex + colIndex, 2.) == 1.) {
continue;
}
} else {
if (mod(rowIndex, 2.) == 1.) {
offset.x += .5 * stepSize;
}
}
}
shape = getLumBall(uv, pad, offset, contrast, baseRadius, stepSize, ballColor);
totalColor += ballColor.rgb * shape;
totalShape += shape;
totalOpacity += shape;
}
}
const float eps = 1e-4;
totalColor /= max(totalShape, eps);
totalOpacity /= max(totalShape, eps);
float finalShape = 0.;
if (u_type == 0.) {
finalShape = min(1., totalShape);
} else if (u_type == 1.) {
float aa = fwidth(totalShape);
float th = .5;
finalShape = smoothstep(th - aa, th + aa, totalShape);
} else if (u_type == 2.) {
finalShape = min(1., totalShape);
} else if (u_type == 3.) {
finalShape = totalShape;
}
vec2 grainSize = mix(2000., 200., u_grainSize) * vec2(1., 1. / u_imageAspectRatio);
vec2 grainUV = v_imageUV - .5;
grainUV *= grainSize;
grainUV += .5;
float grain = valueNoise(grainUV);
grain = smoothstep(.55, .7 + .2 * u_grainMixer, grain);
grain *= u_grainMixer;
finalShape = mix(finalShape, 0., grain);
vec3 color = vec3(0.);
float opacity = 0.;
if (u_originalColors == true) {
color = totalColor * finalShape;
opacity = totalOpacity * finalShape;
vec3 bgColor = u_colorBack.rgb * u_colorBack.a;
color = color + bgColor * (1. - opacity);
opacity = opacity + u_colorBack.a * (1. - opacity);
} else {
vec3 fgColor = u_colorFront.rgb * u_colorFront.a;
float fgOpacity = u_colorFront.a;
vec3 bgColor = u_colorBack.rgb * u_colorBack.a;
float bgOpacity = u_colorBack.a;
color = fgColor * finalShape;
opacity = fgOpacity * finalShape;
color += bgColor * (1. - opacity);
opacity += bgOpacity * (1. - opacity);
}
float grainOverlay = valueNoise(rotate(grainUV, 1.) + vec2(3.));
grainOverlay = mix(grainOverlay, valueNoise(rotate(grainUV, 2.) + vec2(-1.)), .5);
grainOverlay = pow(grainOverlay, 1.3);
float grainOverlayV = grainOverlay * 2. - 1.;
vec3 grainOverlayColor = vec3(step(0., grainOverlayV));
float grainOverlayStrength = u_grainOverlay * abs(grainOverlayV);
grainOverlayStrength = pow(grainOverlayStrength, .8);
color = mix(color, grainOverlayColor, .5 * grainOverlayStrength);
opacity += .5 * grainOverlayStrength;
opacity = clamp(opacity, 0., 1.);
fragColor = vec4(color, opacity);
}
`;
const HalftoneDotsTypes = {
classic: 0,
gooey: 1,
holes: 2,
soft: 3
};
const HalftoneDotsGrids = {
square: 0,
hex: 1
};
export {
HalftoneDotsGrids,
HalftoneDotsTypes,
halftoneDotsFragmentShader
};
//# sourceMappingURL=halftone-dots.js.map
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../src/shaders/halftone-dots.ts"],
"sourcesContent": ["import type { ShaderMotionParams } from '../shader-mount.js';\nimport { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';\nimport { declarePI, rotation2, proceduralHash21 } from '../shader-utils.js';\n\n/**\n * A halftone-dot image filter featuring customizable grids, color palettes, and dot styles.\n *\n * Fragment shader uniforms:\n * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)\n * - u_time (float): Animation time\n * - u_image (sampler2D): Source image texture\n * - u_imageAspectRatio (float): Aspect ratio of the source image\n * - u_colorFront (vec4): Foreground color in RGBA\n * - u_colorBack (vec4): Background color in RGBA\n * - u_originalColors (bool): Use sampled image's original colors instead of colorFront\n * - u_type (float): Dot style (0 = classic, 1 = gooey, 2 = holes, 3 = soft)\n * - u_inverted (bool): Inverts the image luminance, doesn't affect the color scheme; not effective at zero contrast\n * - u_grid (float): Grid type (0 = square, 1 = hex)\n * - u_size (float): Grid size relative to the image box (0 to 1)\n * - u_radius (float): Maximum dot size relative to grid cell (0 to 2)\n * - u_contrast (float): Contrast applied to the sampled image (0 to 1)\n * - u_grainMixer (float): Strength of grain distortion applied to shape edges (0 to 1)\n * - u_grainOverlay (float): Post-processing black/white grain overlay (0 to 1)\n * - u_grainSize (float): Scale applied to both grain distortion and grain overlay (0 to 1)\n *\n * Vertex shader outputs (used in fragment shader):\n * - v_imageUV (vec2): Image UV coordinates with global sizing (rotation, scale, offset, etc) applied\n *\n * Vertex shader uniforms:\n * - u_resolution (vec2): Canvas resolution in pixels\n * - u_pixelRatio (float): Device pixel ratio\n * - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)\n * - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)\n * - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)\n * - u_scale (float): Overall zoom level of the graphics (0.01 to 4)\n * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)\n * - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)\n * - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)\n * - u_imageAspectRatio (float): Aspect ratio of the source image\n *\n */\n\n// language=GLSL\nexport const halftoneDotsFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform float u_rotation;\n\nuniform float u_time;\n\nuniform vec4 u_colorFront;\nuniform vec4 u_colorBack;\nuniform float u_radius;\nuniform float u_contrast;\n\nuniform sampler2D u_image;\nuniform float u_imageAspectRatio;\n\nuniform float u_size;\nuniform float u_grainMixer;\nuniform float u_grainOverlay;\nuniform float u_grainSize;\nuniform float u_grid;\nuniform bool u_originalColors;\nuniform bool u_inverted;\nuniform float u_type;\n\nin vec2 v_imageUV;\n\nout vec4 fragColor;\n\n${ declarePI }\n${ rotation2 }\n${ proceduralHash21 }\n\nfloat valueNoise(vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = hash21(i);\n float b = hash21(i + vec2(1.0, 0.0));\n float c = hash21(i + vec2(0.0, 1.0));\n float d = hash21(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n float x1 = mix(a, b, u.x);\n float x2 = mix(c, d, u.x);\n return mix(x1, x2, u.y);\n}\n\nfloat lst(float edge0, float edge1, float x) {\n return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);\n}\n\nfloat sst(float edge0, float edge1, float x) {\n return smoothstep(edge0, edge1, x);\n}\n\nfloat getCircle(vec2 uv, float r, float baseR) {\n r = mix(.25 * baseR, 0., r);\n float d = length(uv - .5);\n float aa = fwidth(d);\n return 1. - smoothstep(r - aa, r + aa, d);\n}\n\nfloat getCell(vec2 uv) {\n float insideX = step(0.0, uv.x) * (1.0 - step(1.0, uv.x));\n float insideY = step(0.0, uv.y) * (1.0 - step(1.0, uv.y));\n return insideX * insideY;\n}\n\nfloat getCircleWithHole(vec2 uv, float r, float baseR) {\n float cell = getCell(uv);\n\n r = mix(.75 * baseR, 0., r);\n float rMod = mod(r, .5);\n\n float d = length(uv - .5);\n float aa = fwidth(d);\n float circle = 1. - smoothstep(rMod - aa, rMod + aa, d);\n if (r < .5) {\n return circle;\n } else {\n return cell - circle;\n }\n}\n\nfloat getGooeyBall(vec2 uv, float r, float baseR) {\n float d = length(uv - .5);\n float sizeRadius = .3;\n if (u_grid == 1.) {\n sizeRadius = .42;\n }\n sizeRadius = mix(sizeRadius * baseR, 0., r);\n d = 1. - sst(0., sizeRadius, d);\n\n d = pow(d, 2. + baseR);\n return d;\n}\n\nfloat getSoftBall(vec2 uv, float r, float baseR) {\n float d = length(uv - .5);\n float sizeRadius = clamp(baseR, 0., 1.);\n sizeRadius = mix(.5 * sizeRadius, 0., r);\n d = 1. - lst(0., sizeRadius, d);\n float powRadius = 1. - lst(0., 2., bLine truncated
"mappings": ";;;;;AACA,eAAmE;AACnE,SAAS,WAAW,WAAW,wBAAwB;AAyChD,MAAM,6BAAqC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4B/C,SAAU;AAAA,EACV,SAAU;AAAA,EACV,gBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6Sb,MAAM,oBAAoB;AAAA,EAC/B,SAAS;AAAA,EACT,OAAO;AAAA,EACP,OAAO;AAAA,EACP,MAAM;AACR;AAIO,MAAM,oBAAoB;AAAA,EAC/B,QAAQ;AAAA,EACR,KAAK;AACP;",
"names": []
}
+67
View File
@@ -0,0 +1,67 @@
import type { vec4 } from '../types.js';
import type { ShaderMotionParams } from '../shader-mount.js';
import type { ShaderSizingParams, ShaderSizingUniforms } from '../shader-sizing.js';
export declare const heatmapMeta: {
readonly maxColorCount: 10;
};
/**
* A glowing gradient of colors flowing through an input shape.
* The effect creates a smoothly animated wave of intensity across the image.
*
* Fragment shader uniforms:
* - u_time (float): Animation time
* - u_image (sampler2D): Pre-processed source image texture (R = contour, G = outer blur, B = inner blur)
* - u_imageAspectRatio (float): Aspect ratio of the source image
* - u_colorBack (vec4): Background color in RGBA
* - u_colors (vec4[]): Up to 10 heatmap colors in RGBA
* - u_colorsCount (float): Number of active colors
* - u_contour (float): Heat intensity near the edges of the input shape (0 to 1)
* - u_angle (float): Direction of the heatwaves in degrees (0 to 360)
* - u_noise (float): Grain applied across the entire graphic (0 to 1)
* - u_innerGlow (float): Size of the heated area inside the input shape (0 to 1)
* - u_outerGlow (float): Size of the heated area outside the input shape (0 to 1)
*
* Vertex shader outputs (used in fragment shader):
* - v_imageUV (vec2): UV coordinates for sampling the source image, with fit, scale, rotation, and offset applied
* - v_objectUV (vec2): Object box UV coordinates with global sizing (scale, rotation, offsets, etc) applied
*
* Vertex shader uniforms:
* - u_resolution (vec2): Canvas resolution in pixels
* - u_pixelRatio (float): Device pixel ratio
* - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)
* - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)
* - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas
* - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas
* - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)
* - u_scale (float): Overall zoom level of the graphics (0.01 to 4)
* - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
* - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)
* - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)
* - u_imageAspectRatio (float): Aspect ratio of the source image
*
*/
export declare const heatmapFragmentShader: string;
export declare function toProcessedHeatmap(file: File | string): Promise<{
blob: Blob;
}>;
export interface HeatmapUniforms extends ShaderSizingUniforms {
u_image: HTMLImageElement | string;
u_contour: number;
u_angle: number;
u_noise: number;
u_innerGlow: number;
u_outerGlow: number;
u_colorBack: [number, number, number, number];
u_colors: vec4[];
u_colorsCount: number;
}
export interface HeatmapParams extends ShaderSizingParams, ShaderMotionParams {
image: HTMLImageElement | string;
contour?: number;
angle?: number;
noise?: number;
innerGlow?: number;
outerGlow?: number;
colorBack?: string;
colors?: string[];
}
Loaded 100 of 170 files, more files were not shown because too many files have changed in this diff. Show more