react-native-effects 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +313 -0
- package/lib/module/components/Aurora.js +184 -0
- package/lib/module/components/Aurora.js.map +1 -0
- package/lib/module/components/CalicoSwirl.js +155 -0
- package/lib/module/components/CalicoSwirl.js.map +1 -0
- package/lib/module/components/Campfire.js +225 -0
- package/lib/module/components/Campfire.js.map +1 -0
- package/lib/module/components/CircularGradient.js +52 -0
- package/lib/module/components/CircularGradient.js.map +1 -0
- package/lib/module/components/Iridescence.js +57 -0
- package/lib/module/components/Iridescence.js.map +1 -0
- package/lib/module/components/LinearGradient.js +48 -0
- package/lib/module/components/LinearGradient.js.map +1 -0
- package/lib/module/components/LiquidChrome.js +75 -0
- package/lib/module/components/LiquidChrome.js.map +1 -0
- package/lib/module/components/ShaderView/index.js +252 -0
- package/lib/module/components/ShaderView/index.js.map +1 -0
- package/lib/module/components/ShaderView/types.js +4 -0
- package/lib/module/components/ShaderView/types.js.map +1 -0
- package/lib/module/components/ShaderViewWithPanGesture/index.js +196 -0
- package/lib/module/components/ShaderViewWithPanGesture/index.js.map +1 -0
- package/lib/module/components/Silk.js +83 -0
- package/lib/module/components/Silk.js.map +1 -0
- package/lib/module/consts.js +154 -0
- package/lib/module/consts.js.map +1 -0
- package/lib/module/hooks/useClock.js +15 -0
- package/lib/module/hooks/useClock.js.map +1 -0
- package/lib/module/hooks/useParamsSynchronizable.js +37 -0
- package/lib/module/hooks/useParamsSynchronizable.js.map +1 -0
- package/lib/module/hooks/useWGPUSetup.js +54 -0
- package/lib/module/hooks/useWGPUSetup.js.map +1 -0
- package/lib/module/index.js +15 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/shaders/TRIANGLE_VERTEX_SHADER.js +20 -0
- package/lib/module/shaders/TRIANGLE_VERTEX_SHADER.js.map +1 -0
- package/lib/module/shaders/uniforms.js +21 -0
- package/lib/module/shaders/uniforms.js.map +1 -0
- package/lib/module/utils/backgroundRuntime.js +12 -0
- package/lib/module/utils/backgroundRuntime.js.map +1 -0
- package/lib/module/utils/colors.js +94 -0
- package/lib/module/utils/colors.js.map +1 -0
- package/lib/module/utils/initWebGPU.js +40 -0
- package/lib/module/utils/initWebGPU.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/components/Aurora.d.ts +17 -0
- package/lib/typescript/src/components/Aurora.d.ts.map +1 -0
- package/lib/typescript/src/components/CalicoSwirl.d.ts +13 -0
- package/lib/typescript/src/components/CalicoSwirl.d.ts.map +1 -0
- package/lib/typescript/src/components/Campfire.d.ts +17 -0
- package/lib/typescript/src/components/Campfire.d.ts.map +1 -0
- package/lib/typescript/src/components/CircularGradient.d.ts +19 -0
- package/lib/typescript/src/components/CircularGradient.d.ts.map +1 -0
- package/lib/typescript/src/components/Iridescence.d.ts +11 -0
- package/lib/typescript/src/components/Iridescence.d.ts.map +1 -0
- package/lib/typescript/src/components/LinearGradient.d.ts +15 -0
- package/lib/typescript/src/components/LinearGradient.d.ts.map +1 -0
- package/lib/typescript/src/components/LiquidChrome.d.ts +17 -0
- package/lib/typescript/src/components/LiquidChrome.d.ts.map +1 -0
- package/lib/typescript/src/components/ShaderView/index.d.ts +3 -0
- package/lib/typescript/src/components/ShaderView/index.d.ts.map +1 -0
- package/lib/typescript/src/components/ShaderView/types.d.ts +35 -0
- package/lib/typescript/src/components/ShaderView/types.d.ts.map +1 -0
- package/lib/typescript/src/components/ShaderViewWithPanGesture/index.d.ts +35 -0
- package/lib/typescript/src/components/ShaderViewWithPanGesture/index.d.ts.map +1 -0
- package/lib/typescript/src/components/Silk.d.ts +17 -0
- package/lib/typescript/src/components/Silk.d.ts.map +1 -0
- package/lib/typescript/src/consts.d.ts +2 -0
- package/lib/typescript/src/consts.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useClock.d.ts +3 -0
- package/lib/typescript/src/hooks/useClock.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useParamsSynchronizable.d.ts +22 -0
- package/lib/typescript/src/hooks/useParamsSynchronizable.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useWGPUSetup.d.ts +15 -0
- package/lib/typescript/src/hooks/useWGPUSetup.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +16 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/shaders/TRIANGLE_VERTEX_SHADER.d.ts +2 -0
- package/lib/typescript/src/shaders/TRIANGLE_VERTEX_SHADER.d.ts.map +1 -0
- package/lib/typescript/src/shaders/uniforms.d.ts +6 -0
- package/lib/typescript/src/shaders/uniforms.d.ts.map +1 -0
- package/lib/typescript/src/utils/backgroundRuntime.d.ts +3 -0
- package/lib/typescript/src/utils/backgroundRuntime.d.ts.map +1 -0
- package/lib/typescript/src/utils/colors.d.ts +22 -0
- package/lib/typescript/src/utils/colors.d.ts.map +1 -0
- package/lib/typescript/src/utils/initWebGPU.d.ts +23 -0
- package/lib/typescript/src/utils/initWebGPU.d.ts.map +1 -0
- package/package.json +175 -7
- package/src/components/Aurora.tsx +203 -0
- package/src/components/CalicoSwirl.tsx +167 -0
- package/src/components/Campfire.tsx +244 -0
- package/src/components/CircularGradient.tsx +76 -0
- package/src/components/Iridescence.tsx +67 -0
- package/src/components/LinearGradient.tsx +62 -0
- package/src/components/LiquidChrome.tsx +94 -0
- package/src/components/ShaderView/index.tsx +262 -0
- package/src/components/ShaderView/types.ts +36 -0
- package/src/components/ShaderViewWithPanGesture/index.tsx +225 -0
- package/src/components/Silk.tsx +102 -0
- package/src/consts.ts +152 -0
- package/src/hooks/useClock.ts +20 -0
- package/src/hooks/useParamsSynchronizable.ts +52 -0
- package/src/hooks/useWGPUSetup.tsx +73 -0
- package/src/index.tsx +32 -0
- package/src/shaders/TRIANGLE_VERTEX_SHADER.ts +17 -0
- package/src/shaders/uniforms.ts +18 -0
- package/src/utils/backgroundRuntime.ts +10 -0
- package/src/utils/colors.ts +117 -0
- package/src/utils/initWebGPU.ts +47 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react';
|
|
2
|
+
import { StyleSheet, View, type LayoutChangeEvent } from 'react-native';
|
|
3
|
+
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
4
|
+
import {
|
|
5
|
+
createSynchronizable,
|
|
6
|
+
type Synchronizable,
|
|
7
|
+
} from 'react-native-worklets';
|
|
8
|
+
import ShaderView from '../ShaderView';
|
|
9
|
+
import { useParamsSynchronizable } from '../../hooks/useParamsSynchronizable';
|
|
10
|
+
import type { ShaderViewProps } from '../ShaderView/types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A {@link ShaderView} that feeds touch input into the shader's `u.live`:
|
|
14
|
+
*
|
|
15
|
+
* - `live.x` → pointer X, normalized 0..1 (left → right)
|
|
16
|
+
* - `live.y` → pointer Y, normalized 0..1 (bottom → top, matching UV space)
|
|
17
|
+
* - `live.z` → 1.0 while touching, 0.0 when released
|
|
18
|
+
* - `live.w` → 0.0 (reserved)
|
|
19
|
+
*
|
|
20
|
+
* Dragging moves the pointer **relatively** — it pushes from where the pointer
|
|
21
|
+
* already is rather than jumping under the finger — and a fling lets it glide to
|
|
22
|
+
* a stop. The position is **remembered**: it stays wherever it ended and is
|
|
23
|
+
* never reset; only the "touched" flag (`live.z`) toggles on release. A
|
|
24
|
+
* shader can read `live.xy` as a stable resting position and use `live.z`
|
|
25
|
+
* purely for touch-driven emphasis, so the effect never snaps back.
|
|
26
|
+
*
|
|
27
|
+
* The resting value before the first touch is `[0, 0, 0, 0]` by default; pass
|
|
28
|
+
* `initialParamsSynchronizable` to seed it — e.g. `[0.5, 0.5, 0, 0]` to start a
|
|
29
|
+
* pointer at screen center.
|
|
30
|
+
*
|
|
31
|
+
* The drag runs as a **worklet on the UI thread** and writes the synchronizable
|
|
32
|
+
* directly, so pointer updates never hop to the JS thread — matching the rest of
|
|
33
|
+
* the library, which renders off the JS thread. The render runtime reads the
|
|
34
|
+
* same synchronizable each frame.
|
|
35
|
+
*/
|
|
36
|
+
export type ShaderViewWithPanGestureProps = Omit<
|
|
37
|
+
ShaderViewProps,
|
|
38
|
+
'paramsSynchronizable'
|
|
39
|
+
> & {
|
|
40
|
+
/**
|
|
41
|
+
* Initial value for the gesture channel (`u.live`) before the first touch.
|
|
42
|
+
* Defaults to `[0, 0, 0, 0]`. Use e.g. `[0.5, 0.5, 0, 0]` to rest a pointer at
|
|
43
|
+
* screen center.
|
|
44
|
+
*/
|
|
45
|
+
initialParamsSynchronizable?: readonly [number, number, number, number];
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default function ShaderViewWithPanGesture({
|
|
49
|
+
style,
|
|
50
|
+
initialParamsSynchronizable = [0, 0, 0, 0],
|
|
51
|
+
...props
|
|
52
|
+
}: ShaderViewWithPanGestureProps) {
|
|
53
|
+
const { paramsSynchronizable } = useParamsSynchronizable(
|
|
54
|
+
initialParamsSynchronizable
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// View size, read inside the gesture worklets to normalize pointer coords.
|
|
58
|
+
const sizeRef = useRef<Synchronizable<Float64Array> | null>(null);
|
|
59
|
+
if (sizeRef.current === null) {
|
|
60
|
+
sizeRef.current = createSynchronizable<Float64Array>(Float64Array.of(1, 1));
|
|
61
|
+
}
|
|
62
|
+
const sizeSynchronizable = sizeRef.current;
|
|
63
|
+
|
|
64
|
+
// Generation of the current post-release glide; bumped to cancel an old one.
|
|
65
|
+
const momentumRef = useRef<Synchronizable<Float64Array> | null>(null);
|
|
66
|
+
if (momentumRef.current === null) {
|
|
67
|
+
momentumRef.current = createSynchronizable<Float64Array>(
|
|
68
|
+
Float64Array.of(0)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const momentumSynchronizable = momentumRef.current;
|
|
72
|
+
|
|
73
|
+
// Pointer position when the current drag began — the pan moves the pointer
|
|
74
|
+
// relative to this, so a drag pushes from where it was rather than jumping.
|
|
75
|
+
const panStartRef = useRef<Synchronizable<Float64Array> | null>(null);
|
|
76
|
+
if (panStartRef.current === null) {
|
|
77
|
+
panStartRef.current = createSynchronizable<Float64Array>(
|
|
78
|
+
Float64Array.of(0, 0)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const panStartSynchronizable = panStartRef.current;
|
|
82
|
+
|
|
83
|
+
const onLayout = useCallback(
|
|
84
|
+
(e: LayoutChangeEvent) => {
|
|
85
|
+
const { width, height } = e.nativeEvent.layout;
|
|
86
|
+
sizeSynchronizable.setBlocking(() =>
|
|
87
|
+
Float64Array.of(width || 1, height || 1)
|
|
88
|
+
);
|
|
89
|
+
},
|
|
90
|
+
[sizeSynchronizable]
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// Worklet: runs on the UI thread and writes the normalized pointer straight
|
|
94
|
+
// into the synchronizable the render runtime reads, so a pointer move never
|
|
95
|
+
// touches the JS thread.
|
|
96
|
+
const writePointer = (nx: number, ny: number, active: number) => {
|
|
97
|
+
'worklet';
|
|
98
|
+
const x = Math.min(1, Math.max(0, nx));
|
|
99
|
+
const y = Math.min(1, Math.max(0, ny));
|
|
100
|
+
paramsSynchronizable.setBlocking(() => Float64Array.of(x, y, active, 0));
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const stopMomentum = () => {
|
|
104
|
+
'worklet';
|
|
105
|
+
const next = (momentumSynchronizable.getDirty()[0] || 0) + 1;
|
|
106
|
+
momentumSynchronizable.setBlocking(() => Float64Array.of(next));
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Drop the touched flag in place — safety net for a gesture cancelled with no
|
|
110
|
+
// onEnd, so the flag never sticks.
|
|
111
|
+
const releaseFlag = () => {
|
|
112
|
+
'worklet';
|
|
113
|
+
const p = paramsSynchronizable.getDirty();
|
|
114
|
+
const x = p[0] || 0;
|
|
115
|
+
const y = p[1] || 0;
|
|
116
|
+
paramsSynchronizable.setBlocking(() => Float64Array.of(x, y, 0, 0));
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// After release, drift from the last position along the fling velocity and
|
|
120
|
+
// decay to a stop — a little inertia. Runs on the UI thread via rAF, like the
|
|
121
|
+
// render loop, writing each frame into the same synchronizable.
|
|
122
|
+
const startMomentum = (velX: number, velY: number) => {
|
|
123
|
+
'worklet';
|
|
124
|
+
const s = sizeSynchronizable.getDirty();
|
|
125
|
+
const w = s[0] || 1;
|
|
126
|
+
const h = s[1] || 1;
|
|
127
|
+
|
|
128
|
+
// Flick speed in normalized units/sec, scaled to a subtle glide (Y flipped).
|
|
129
|
+
const SCALE = 0.12;
|
|
130
|
+
let vx = (velX / w) * SCALE;
|
|
131
|
+
let vy = (-velY / h) * SCALE;
|
|
132
|
+
|
|
133
|
+
const p = paramsSynchronizable.getDirty();
|
|
134
|
+
let x = p[0] || 0;
|
|
135
|
+
let y = p[1] || 0;
|
|
136
|
+
|
|
137
|
+
// Claim this glide; a newer one bumps the generation and this loop bails.
|
|
138
|
+
const gen = (momentumSynchronizable.getDirty()[0] || 0) + 1;
|
|
139
|
+
momentumSynchronizable.setBlocking(() => Float64Array.of(gen));
|
|
140
|
+
|
|
141
|
+
const FRICTION = 2; // 1/s — higher stops sooner
|
|
142
|
+
let last = -1;
|
|
143
|
+
|
|
144
|
+
// Plain closure (no 'worklet') so its accumulators and self-reference
|
|
145
|
+
// survive across frames; a serialized worklet would snapshot them by value.
|
|
146
|
+
const step = (now: number) => {
|
|
147
|
+
if ((momentumSynchronizable.getDirty()[0] || 0) !== gen) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const dt = last < 0 ? 0 : (now - last) / 1000;
|
|
151
|
+
last = now;
|
|
152
|
+
|
|
153
|
+
x = Math.min(1, Math.max(0, x + vx * dt));
|
|
154
|
+
y = Math.min(1, Math.max(0, y + vy * dt));
|
|
155
|
+
const decay = Math.exp(-FRICTION * dt);
|
|
156
|
+
vx = vx * decay;
|
|
157
|
+
vy = vy * decay;
|
|
158
|
+
|
|
159
|
+
paramsSynchronizable.setBlocking(() => Float64Array.of(x, y, 0, 0));
|
|
160
|
+
|
|
161
|
+
if (Math.abs(vx) + Math.abs(vy) > 0.0008) {
|
|
162
|
+
requestAnimationFrame(step);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
requestAnimationFrame(step);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Drag moves the pointer *relatively*: grab anywhere and push it from where it
|
|
169
|
+
// is, rather than snapping it under the finger. A plain tap leaves it put.
|
|
170
|
+
const pan = Gesture.Pan()
|
|
171
|
+
.onBegin(() => {
|
|
172
|
+
'worklet';
|
|
173
|
+
stopMomentum();
|
|
174
|
+
const p = paramsSynchronizable.getDirty();
|
|
175
|
+
const sx = p[0] || 0;
|
|
176
|
+
const sy = p[1] || 0;
|
|
177
|
+
panStartSynchronizable.setBlocking(() => Float64Array.of(sx, sy));
|
|
178
|
+
writePointer(sx, sy, 1);
|
|
179
|
+
})
|
|
180
|
+
.onUpdate((e) => {
|
|
181
|
+
'worklet';
|
|
182
|
+
const s = sizeSynchronizable.getDirty();
|
|
183
|
+
const w = s[0] || 1;
|
|
184
|
+
const h = s[1] || 1;
|
|
185
|
+
const start = panStartSynchronizable.getDirty();
|
|
186
|
+
// Add the drag delta; Y is flipped to match the shader's UV space.
|
|
187
|
+
writePointer(
|
|
188
|
+
(start[0] || 0) + e.translationX / w,
|
|
189
|
+
(start[1] || 0) - e.translationY / h,
|
|
190
|
+
1
|
|
191
|
+
);
|
|
192
|
+
})
|
|
193
|
+
.onEnd((e) => {
|
|
194
|
+
'worklet';
|
|
195
|
+
const p = paramsSynchronizable.getDirty();
|
|
196
|
+
writePointer(p[0] || 0, p[1] || 0, 0);
|
|
197
|
+
startMomentum(e.velocityX, e.velocityY);
|
|
198
|
+
})
|
|
199
|
+
.onFinalize(() => {
|
|
200
|
+
'worklet';
|
|
201
|
+
releaseFlag();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<GestureDetector gesture={pan}>
|
|
206
|
+
<View
|
|
207
|
+
style={[styles.fill, style]}
|
|
208
|
+
onLayout={onLayout}
|
|
209
|
+
collapsable={false}
|
|
210
|
+
>
|
|
211
|
+
<ShaderView
|
|
212
|
+
{...props}
|
|
213
|
+
paramsSynchronizable={paramsSynchronizable}
|
|
214
|
+
style={StyleSheet.absoluteFill}
|
|
215
|
+
/>
|
|
216
|
+
</View>
|
|
217
|
+
</GestureDetector>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const styles = StyleSheet.create({
|
|
222
|
+
fill: {
|
|
223
|
+
flex: 1,
|
|
224
|
+
},
|
|
225
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import type { ViewProps } from 'react-native';
|
|
3
|
+
import type { ColorInput } from '../utils/colors';
|
|
4
|
+
import ShaderView from './ShaderView';
|
|
5
|
+
|
|
6
|
+
type Props = ViewProps & {
|
|
7
|
+
/** The base color for the silk effect. */
|
|
8
|
+
color?: ColorInput;
|
|
9
|
+
/** Animation speed multiplier. Default: 1.0 */
|
|
10
|
+
speed?: number;
|
|
11
|
+
/** Scale of the pattern. Default: 1.0 */
|
|
12
|
+
scale?: number;
|
|
13
|
+
/** Rotation angle in radians. Default: 0.0 */
|
|
14
|
+
rotation?: number;
|
|
15
|
+
/** Intensity of the noise grain. Default: 1.5 */
|
|
16
|
+
noiseIntensity?: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default function Silk({
|
|
20
|
+
color = '#7B7481',
|
|
21
|
+
speed = 1.0,
|
|
22
|
+
scale = 1.0,
|
|
23
|
+
rotation = 0.0,
|
|
24
|
+
noiseIntensity = 1.5,
|
|
25
|
+
...viewProps
|
|
26
|
+
}: Props) {
|
|
27
|
+
const colors = useMemo(() => [color], [color]);
|
|
28
|
+
const params = useMemo(
|
|
29
|
+
() => [scale, rotation, noiseIntensity],
|
|
30
|
+
[scale, rotation, noiseIntensity]
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<ShaderView
|
|
35
|
+
fragmentShader={SILK_SHADER}
|
|
36
|
+
colors={colors}
|
|
37
|
+
params={params}
|
|
38
|
+
speed={speed}
|
|
39
|
+
isStatic={false}
|
|
40
|
+
{...viewProps}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const SILK_SHADER = /* wgsl */ `
|
|
46
|
+
struct Uniforms {
|
|
47
|
+
resolution: vec4<f32>,
|
|
48
|
+
time: vec4<f32>,
|
|
49
|
+
color0: vec4<f32>,
|
|
50
|
+
color1: vec4<f32>,
|
|
51
|
+
params0: vec4<f32>,
|
|
52
|
+
params1: vec4<f32>,
|
|
53
|
+
};
|
|
54
|
+
@group(0) @binding(0) var<uniform> u: Uniforms;
|
|
55
|
+
|
|
56
|
+
const e = 2.71828182845904523536;
|
|
57
|
+
|
|
58
|
+
fn noise(texCoord: vec2<f32>) -> f32 {
|
|
59
|
+
let G = e;
|
|
60
|
+
let r = (G * sin(G * texCoord));
|
|
61
|
+
return fract(r.x * r.y * (1.0 + texCoord.x));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn rotateUvs(uv: vec2<f32>, angle: f32) -> vec2<f32> {
|
|
65
|
+
let c = cos(angle);
|
|
66
|
+
let s = sin(angle);
|
|
67
|
+
let rot = mat2x2<f32>(c, -s, s, c);
|
|
68
|
+
return rot * uv;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@fragment
|
|
72
|
+
fn main(@location(0) ndc: vec2<f32>) -> @location(0) vec4<f32> {
|
|
73
|
+
let time = u.time.x;
|
|
74
|
+
let scale = u.params0.x;
|
|
75
|
+
let rotation = u.params0.y;
|
|
76
|
+
let noiseIntensity = u.params0.z;
|
|
77
|
+
|
|
78
|
+
let resolution2D = u.resolution.xy;
|
|
79
|
+
let vUv = ndc * 0.5 + vec2<f32>(0.5, 0.5);
|
|
80
|
+
let fragCoord = vUv * resolution2D;
|
|
81
|
+
|
|
82
|
+
let rnd = noise(fragCoord);
|
|
83
|
+
let uv = rotateUvs(vUv * scale, rotation);
|
|
84
|
+
var tex = uv * scale;
|
|
85
|
+
let tOffset = time;
|
|
86
|
+
|
|
87
|
+
tex.y = tex.y + 0.03 * sin(8.0 * tex.x - tOffset);
|
|
88
|
+
|
|
89
|
+
let pattern = 0.6 +
|
|
90
|
+
0.4 * sin(5.0 * (tex.x + tex.y +
|
|
91
|
+
cos(3.0 * tex.x + 5.0 * tex.y) +
|
|
92
|
+
0.02 * tOffset) +
|
|
93
|
+
sin(20.0 * (tex.x + tex.y - 0.1 * tOffset)));
|
|
94
|
+
|
|
95
|
+
var col = u.color0 * vec4<f32>(pattern, pattern, pattern, 1.0) -
|
|
96
|
+
vec4<f32>(rnd / 15.0 * noiseIntensity, rnd / 15.0 * noiseIntensity,
|
|
97
|
+
rnd / 15.0 * noiseIntensity, 0.0);
|
|
98
|
+
col.a = u.color0.a;
|
|
99
|
+
|
|
100
|
+
return clamp(col, vec4<f32>(0.0), vec4<f32>(1.0));
|
|
101
|
+
}
|
|
102
|
+
`;
|
package/src/consts.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
export const NAMED_COLORS: Record<string, number> = {
|
|
2
|
+
// Basic colors
|
|
3
|
+
black: 0x000000,
|
|
4
|
+
white: 0xffffff,
|
|
5
|
+
red: 0xff0000,
|
|
6
|
+
green: 0x008000,
|
|
7
|
+
blue: 0x0000ff,
|
|
8
|
+
yellow: 0xffff00,
|
|
9
|
+
cyan: 0x00ffff,
|
|
10
|
+
magenta: 0xff00ff,
|
|
11
|
+
silver: 0xc0c0c0,
|
|
12
|
+
gray: 0x808080,
|
|
13
|
+
grey: 0x808080,
|
|
14
|
+
maroon: 0x800000,
|
|
15
|
+
olive: 0x808000,
|
|
16
|
+
lime: 0x00ff00,
|
|
17
|
+
aqua: 0x00ffff,
|
|
18
|
+
teal: 0x008080,
|
|
19
|
+
navy: 0x000080,
|
|
20
|
+
fuchsia: 0xff00ff,
|
|
21
|
+
purple: 0x800080,
|
|
22
|
+
|
|
23
|
+
// Extended colors
|
|
24
|
+
aliceblue: 0xf0f8ff,
|
|
25
|
+
antiquewhite: 0xfaebd7,
|
|
26
|
+
aquamarine: 0x7fffd4,
|
|
27
|
+
azure: 0xf0ffff,
|
|
28
|
+
beige: 0xf5f5dc,
|
|
29
|
+
bisque: 0xffe4c4,
|
|
30
|
+
blanchedalmond: 0xffebcd,
|
|
31
|
+
blueviolet: 0x8a2be2,
|
|
32
|
+
brown: 0xa52a2a,
|
|
33
|
+
burlywood: 0xdeb887,
|
|
34
|
+
cadetblue: 0x5f9ea0,
|
|
35
|
+
chartreuse: 0x7fff00,
|
|
36
|
+
chocolate: 0xd2691e,
|
|
37
|
+
coral: 0xff7f50,
|
|
38
|
+
cornflowerblue: 0x6495ed,
|
|
39
|
+
cornsilk: 0xfff8dc,
|
|
40
|
+
crimson: 0xdc143c,
|
|
41
|
+
darkblue: 0x00008b,
|
|
42
|
+
darkcyan: 0x008b8b,
|
|
43
|
+
darkgoldenrod: 0xb8860b,
|
|
44
|
+
darkgray: 0xa9a9a9,
|
|
45
|
+
darkgrey: 0xa9a9a9,
|
|
46
|
+
darkgreen: 0x006400,
|
|
47
|
+
darkkhaki: 0xbdb76b,
|
|
48
|
+
darkmagenta: 0x8b008b,
|
|
49
|
+
darkolivegreen: 0x556b2f,
|
|
50
|
+
darkorange: 0xff8c00,
|
|
51
|
+
darkorchid: 0x9932cc,
|
|
52
|
+
darkred: 0x8b0000,
|
|
53
|
+
darksalmon: 0xe9967a,
|
|
54
|
+
darkseagreen: 0x8fbc8f,
|
|
55
|
+
darkslateblue: 0x483d8b,
|
|
56
|
+
darkslategray: 0x2f4f4f,
|
|
57
|
+
darkslategrey: 0x2f4f4f,
|
|
58
|
+
darkturquoise: 0x00ced1,
|
|
59
|
+
darkviolet: 0x9400d3,
|
|
60
|
+
deeppink: 0xff1493,
|
|
61
|
+
deepskyblue: 0x00bfff,
|
|
62
|
+
dimgray: 0x696969,
|
|
63
|
+
dimgrey: 0x696969,
|
|
64
|
+
dodgerblue: 0x1e90ff,
|
|
65
|
+
firebrick: 0xb22222,
|
|
66
|
+
floralwhite: 0xfffaf0,
|
|
67
|
+
forestgreen: 0x228b22,
|
|
68
|
+
gainsboro: 0xdcdcdc,
|
|
69
|
+
ghostwhite: 0xf8f8ff,
|
|
70
|
+
gold: 0xffd700,
|
|
71
|
+
goldenrod: 0xdaa520,
|
|
72
|
+
greenyellow: 0xadff2f,
|
|
73
|
+
honeydew: 0xf0fff0,
|
|
74
|
+
hotpink: 0xff69b4,
|
|
75
|
+
indianred: 0xcd5c5c,
|
|
76
|
+
indigo: 0x4b0082,
|
|
77
|
+
ivory: 0xfffff0,
|
|
78
|
+
khaki: 0xf0e68c,
|
|
79
|
+
lavender: 0xe6e6fa,
|
|
80
|
+
lavenderblush: 0xfff0f5,
|
|
81
|
+
lawngreen: 0x7cfc00,
|
|
82
|
+
lemonchiffon: 0xfffacd,
|
|
83
|
+
lightblue: 0xadd8e6,
|
|
84
|
+
lightcoral: 0xf08080,
|
|
85
|
+
lightcyan: 0xe0ffff,
|
|
86
|
+
lightgoldenrodyellow: 0xfafad2,
|
|
87
|
+
lightgray: 0xd3d3d3,
|
|
88
|
+
lightgrey: 0xd3d3d3,
|
|
89
|
+
lightgreen: 0x90ee90,
|
|
90
|
+
lightpink: 0xffb6c1,
|
|
91
|
+
lightsalmon: 0xffa07a,
|
|
92
|
+
lightseagreen: 0x20b2aa,
|
|
93
|
+
lightskyblue: 0x87cefa,
|
|
94
|
+
lightslategray: 0x778899,
|
|
95
|
+
lightslategrey: 0x778899,
|
|
96
|
+
lightsteelblue: 0xb0c4de,
|
|
97
|
+
lightyellow: 0xffffe0,
|
|
98
|
+
limegreen: 0x32cd32,
|
|
99
|
+
linen: 0xfaf0e6,
|
|
100
|
+
mediumaquamarine: 0x66cdaa,
|
|
101
|
+
mediumblue: 0x0000cd,
|
|
102
|
+
mediumorchid: 0xba55d3,
|
|
103
|
+
mediumpurple: 0x9370db,
|
|
104
|
+
mediumseagreen: 0x3cb371,
|
|
105
|
+
mediumslateblue: 0x7b68ee,
|
|
106
|
+
mediumspringgreen: 0x00fa9a,
|
|
107
|
+
mediumturquoise: 0x48d1cc,
|
|
108
|
+
mediumvioletred: 0xc71585,
|
|
109
|
+
midnightblue: 0x191970,
|
|
110
|
+
mintcream: 0xf5fffa,
|
|
111
|
+
mistyrose: 0xffe4e1,
|
|
112
|
+
moccasin: 0xffe4b5,
|
|
113
|
+
navajowhite: 0xffdead,
|
|
114
|
+
oldlace: 0xfdf5e6,
|
|
115
|
+
olivedrab: 0x6b8e23,
|
|
116
|
+
orange: 0xffa500,
|
|
117
|
+
orangered: 0xff4500,
|
|
118
|
+
orchid: 0xda70d6,
|
|
119
|
+
palegoldenrod: 0xeee8aa,
|
|
120
|
+
palegreen: 0x98fb98,
|
|
121
|
+
paleturquoise: 0xafeeee,
|
|
122
|
+
palevioletred: 0xdb7093,
|
|
123
|
+
papayawhip: 0xffefd5,
|
|
124
|
+
peachpuff: 0xffdab9,
|
|
125
|
+
peru: 0xcd853f,
|
|
126
|
+
pink: 0xffc0cb,
|
|
127
|
+
plum: 0xdda0dd,
|
|
128
|
+
powderblue: 0xb0e0e6,
|
|
129
|
+
rosybrown: 0xbc8f8f,
|
|
130
|
+
royalblue: 0x4169e1,
|
|
131
|
+
saddlebrown: 0x8b4513,
|
|
132
|
+
salmon: 0xfa8072,
|
|
133
|
+
sandybrown: 0xf4a460,
|
|
134
|
+
seagreen: 0x2e8b57,
|
|
135
|
+
seashell: 0xfff5ee,
|
|
136
|
+
sienna: 0xa0522d,
|
|
137
|
+
skyblue: 0x87ceeb,
|
|
138
|
+
slateblue: 0x6a5acd,
|
|
139
|
+
slategray: 0x708090,
|
|
140
|
+
slategrey: 0x708090,
|
|
141
|
+
snow: 0xfffafa,
|
|
142
|
+
springgreen: 0x00ff7f,
|
|
143
|
+
steelblue: 0x4682b4,
|
|
144
|
+
tan: 0xd2b48c,
|
|
145
|
+
thistle: 0xd8bfd8,
|
|
146
|
+
tomato: 0xff6347,
|
|
147
|
+
turquoise: 0x40e0d0,
|
|
148
|
+
violet: 0xee82ee,
|
|
149
|
+
wheat: 0xf5deb3,
|
|
150
|
+
whitesmoke: 0xf5f5f5,
|
|
151
|
+
yellowgreen: 0x9acd32,
|
|
152
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
useFrameCallback,
|
|
4
|
+
useSharedValue,
|
|
5
|
+
type FrameInfo,
|
|
6
|
+
type SharedValue,
|
|
7
|
+
} from 'react-native-reanimated';
|
|
8
|
+
|
|
9
|
+
export function useClock(): SharedValue<number> {
|
|
10
|
+
const clock = useSharedValue(0);
|
|
11
|
+
const callback = useCallback(
|
|
12
|
+
(info: FrameInfo) => {
|
|
13
|
+
'worklet';
|
|
14
|
+
clock.value = info.timeSinceFirstFrame;
|
|
15
|
+
},
|
|
16
|
+
[clock]
|
|
17
|
+
);
|
|
18
|
+
useFrameCallback(callback);
|
|
19
|
+
return clock;
|
|
20
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react';
|
|
2
|
+
import { createSynchronizable } from 'react-native-worklets';
|
|
3
|
+
import type { ParamsSynchronizable } from '../components/ShaderView/types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a {@link ParamsSynchronizable} — a 4-float channel written into the
|
|
7
|
+
* dedicated `u.live` slot of a {@link ShaderView} every frame. It has its own
|
|
8
|
+
* uniform slot, so it leaves all 8 static `params` untouched.
|
|
9
|
+
*
|
|
10
|
+
* The returned `setParamsSynchronizable` runs on the JS thread (call it from gesture or scroll
|
|
11
|
+
* handlers); the values are read by the off-thread render loop. By convention
|
|
12
|
+
* the four floats carry `(x, y, active, extra)` for pointer input, or
|
|
13
|
+
* `(progress, ...)` for scroll-driven effects — but the meaning is up to the
|
|
14
|
+
* shader consuming `u.live`.
|
|
15
|
+
*
|
|
16
|
+
* Pass `initial` to seed the channel's starting value (read once on first
|
|
17
|
+
* render), so the shader has a sane resting state before the first update —
|
|
18
|
+
* e.g. `[0.5, 0.5, 0, 0]` to start a pointer at screen center. Defaults to all
|
|
19
|
+
* zeros.
|
|
20
|
+
*/
|
|
21
|
+
export function useParamsSynchronizable(
|
|
22
|
+
initial: readonly [number, number, number, number] = [0, 0, 0, 0]
|
|
23
|
+
): {
|
|
24
|
+
paramsSynchronizable: ParamsSynchronizable;
|
|
25
|
+
setParamsSynchronizable: (
|
|
26
|
+
x: number,
|
|
27
|
+
y: number,
|
|
28
|
+
active: number,
|
|
29
|
+
extra: number
|
|
30
|
+
) => void;
|
|
31
|
+
} {
|
|
32
|
+
// Lazily create once; `initial` is only a seed, so it is read on first render
|
|
33
|
+
// and ignored thereafter.
|
|
34
|
+
const ref = useRef<ParamsSynchronizable | null>(null);
|
|
35
|
+
if (ref.current === null) {
|
|
36
|
+
ref.current = createSynchronizable<Float64Array>(
|
|
37
|
+
Float64Array.of(initial[0], initial[1], initial[2], initial[3])
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const paramsSynchronizable = ref.current;
|
|
41
|
+
|
|
42
|
+
const setParamsSynchronizable = useCallback(
|
|
43
|
+
(x: number, y: number, active: number, extra: number) => {
|
|
44
|
+
paramsSynchronizable.setBlocking(() =>
|
|
45
|
+
Float64Array.of(x, y, active, extra)
|
|
46
|
+
);
|
|
47
|
+
},
|
|
48
|
+
[paramsSynchronizable]
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return { paramsSynchronizable, setParamsSynchronizable };
|
|
52
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { PixelRatio } from 'react-native';
|
|
2
|
+
import {
|
|
3
|
+
useCanvasRef,
|
|
4
|
+
type CanvasRef,
|
|
5
|
+
type RNCanvasContext,
|
|
6
|
+
} from 'react-native-webgpu';
|
|
7
|
+
import { useEffect, useState } from 'react';
|
|
8
|
+
import type { WorkletRuntime } from 'react-native-worklets';
|
|
9
|
+
import { initWebGPU } from '../utils/initWebGPU';
|
|
10
|
+
import { BackgroundRuntime } from '../utils/backgroundRuntime';
|
|
11
|
+
|
|
12
|
+
type GPUResources = {
|
|
13
|
+
device: GPUDevice;
|
|
14
|
+
context: RNCanvasContext;
|
|
15
|
+
presentationFormat: GPUTextureFormat;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type WGPUSetupResult = {
|
|
19
|
+
canvasRef: React.RefObject<CanvasRef>;
|
|
20
|
+
runtime: WorkletRuntime;
|
|
21
|
+
resources: GPUResources | null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function useWGPUSetup(): WGPUSetupResult {
|
|
25
|
+
const canvasRef = useCanvasRef();
|
|
26
|
+
const [resources, setResources] = useState<GPUResources | null>(null);
|
|
27
|
+
const runtime = BackgroundRuntime;
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
let cancelled = false;
|
|
31
|
+
|
|
32
|
+
(async () => {
|
|
33
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
34
|
+
if (!adapter || cancelled) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const device = await adapter.requestDevice();
|
|
39
|
+
if (cancelled) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const context = canvasRef.current!.getContext('webgpu')!;
|
|
44
|
+
const canvas = context.canvas as typeof context.canvas & {
|
|
45
|
+
width: number;
|
|
46
|
+
height: number;
|
|
47
|
+
};
|
|
48
|
+
const dpr = PixelRatio.get();
|
|
49
|
+
canvas.width = canvas.width * dpr;
|
|
50
|
+
canvas.height = canvas.height * dpr;
|
|
51
|
+
|
|
52
|
+
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
|
|
53
|
+
context.configure({
|
|
54
|
+
device,
|
|
55
|
+
format: presentationFormat,
|
|
56
|
+
alphaMode: 'premultiplied',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
initWebGPU(runtime);
|
|
60
|
+
|
|
61
|
+
if (!cancelled) {
|
|
62
|
+
setResources({ device, context, presentationFormat });
|
|
63
|
+
}
|
|
64
|
+
})();
|
|
65
|
+
|
|
66
|
+
return () => {
|
|
67
|
+
cancelled = true;
|
|
68
|
+
};
|
|
69
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
72
|
+
return { canvasRef, runtime, resources };
|
|
73
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import CircularGradient from './components/CircularGradient';
|
|
2
|
+
import LinearGradient from './components/LinearGradient';
|
|
3
|
+
import ShaderView from './components/ShaderView';
|
|
4
|
+
import ShaderViewWithPanGesture from './components/ShaderViewWithPanGesture';
|
|
5
|
+
import Iridescence from './components/Iridescence';
|
|
6
|
+
import LiquidChrome from './components/LiquidChrome';
|
|
7
|
+
import Silk from './components/Silk';
|
|
8
|
+
import Campfire from './components/Campfire';
|
|
9
|
+
import CalicoSwirl from './components/CalicoSwirl';
|
|
10
|
+
import Aurora from './components/Aurora';
|
|
11
|
+
import { useParamsSynchronizable } from './hooks/useParamsSynchronizable';
|
|
12
|
+
|
|
13
|
+
export type {
|
|
14
|
+
ShaderViewProps,
|
|
15
|
+
ParamsSynchronizable,
|
|
16
|
+
} from './components/ShaderView/types';
|
|
17
|
+
export type { ShaderViewWithPanGestureProps } from './components/ShaderViewWithPanGesture';
|
|
18
|
+
export type { ColorInput } from './utils/colors';
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
CircularGradient,
|
|
22
|
+
LinearGradient,
|
|
23
|
+
ShaderView,
|
|
24
|
+
ShaderViewWithPanGesture,
|
|
25
|
+
Iridescence,
|
|
26
|
+
LiquidChrome,
|
|
27
|
+
Silk,
|
|
28
|
+
Campfire,
|
|
29
|
+
CalicoSwirl,
|
|
30
|
+
Aurora,
|
|
31
|
+
useParamsSynchronizable,
|
|
32
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const TRIANGLE_VERTEX_SHADER = /* wgsl */ `
|
|
2
|
+
struct VSOut {
|
|
3
|
+
@builtin(position) pos: vec4<f32>,
|
|
4
|
+
@location(0) ndc: vec2<f32>,
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
@vertex
|
|
8
|
+
fn main(@builtin(vertex_index) vid: u32) -> VSOut {
|
|
9
|
+
var p = array<vec2<f32>,3>(
|
|
10
|
+
vec2<f32>(-1.0,-3.0), vec2<f32>(-1.0,1.0), vec2<f32>(3.0,1.0)
|
|
11
|
+
);
|
|
12
|
+
var o: VSOut;
|
|
13
|
+
o.pos = vec4<f32>(p[vid], 0, 1.0);
|
|
14
|
+
o.ndc = p[vid];
|
|
15
|
+
return o;
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** 112 bytes = 7 × vec4<f32> */
|
|
2
|
+
export const UNIFORM_BUFFER_SIZE = 112;
|
|
3
|
+
|
|
4
|
+
/** Number of float32 values in the uniform buffer */
|
|
5
|
+
export const UNIFORM_FLOAT_COUNT = UNIFORM_BUFFER_SIZE / 4; // 28
|
|
6
|
+
|
|
7
|
+
export const UNIFORMS_WGSL = /* wgsl */ `
|
|
8
|
+
struct Uniforms {
|
|
9
|
+
resolution: vec4<f32>, // (width, height, aspect, pixelRatio)
|
|
10
|
+
time: vec4<f32>, // (seconds, dt, 0, 0)
|
|
11
|
+
color0: vec4<f32>, // colors[0] RGBA
|
|
12
|
+
color1: vec4<f32>, // colors[1] RGBA
|
|
13
|
+
params0: vec4<f32>, // params[0..3]
|
|
14
|
+
params1: vec4<f32>, // params[4..7]
|
|
15
|
+
live: vec4<f32>, // paramsSynchronizable (touch/scroll/audio); (0,0,0,0) when unused
|
|
16
|
+
};
|
|
17
|
+
@group(0) @binding(0) var<uniform> u: Uniforms;
|
|
18
|
+
`;
|