onejs-sl 0.1.1 → 0.1.2
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/CHANGELOG.md +10 -0
- package/README.md +60 -12
- package/goldens/goldens.json +1 -1
- package/lib/common.hlsl +155 -0
- package/lib/noise2d.hlsl +167 -0
- package/lib/sdf2d.hlsl +510 -0
- package/package.json +6 -3
- package/src/body.ts +62 -21
- package/src/emit/hlsl-body.ts +3 -2
- package/src/lib/glsl.ts +899 -0
- package/src/lib/hlsl.ts +891 -0
- package/src/lib/index.ts +47 -0
- package/src/lib/table.ts +125 -0
- package/src/lib/types.ts +23 -0
- package/src/lib/wgsl.ts +994 -0
- package/src/shapes.ts +4 -4
- package/src/sl.ts +9 -1
- package/src/web.ts +53 -28
- package/src/weblib.ts +0 -1303
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.2
|
|
4
|
+
|
|
5
|
+
The helper library has one source, `lib/*.hlsl`, translated at build time into GLSL ES, WGSL and the shared subset and copied into OneJS as its `.cginc` files. The web draws exactly what it drew; the Unity shader's text changes where Metal needed it to, and draws the same.
|
|
6
|
+
|
|
7
|
+
- `emitLibrary` prints the library functions a body calls, in the shared subset
|
|
8
|
+
- `emitBody` prints no scalar swizzles, and types every literal and scalar it passes to an intrinsic
|
|
9
|
+
- `atan2` of vectors is refused; no web backend compiled it
|
|
10
|
+
- `onejs-sl/goldens.json` exports the goldens
|
|
11
|
+
- The tests compile the shared subset as Metal on a Mac with Xcode
|
|
12
|
+
|
|
3
13
|
## 0.1.1
|
|
4
14
|
|
|
5
15
|
A host can now supply its own frame for a program's body and get goldens for the pictures it should draw. Nothing an existing program draws changes, and OneJS's generated Unity shader is byte for byte what 0.1.0 printed.
|
package/README.md
CHANGED
|
@@ -40,9 +40,10 @@ only what it calls.
|
|
|
40
40
|
| `onejs-sl/tables` | `BUILTINS`, `SL_HLSL`, `INPUTS`, `SL_SDF_SHAPES`, `SL_SDF_PARAMS`: what completion and highlighting read |
|
|
41
41
|
| `onejs-sl/limits` | `vmFit(program)`: whether the VM runs it, and why not, without encoding |
|
|
42
42
|
| `onejs-sl/vm` | `encode`, `SL_WIRE_VERSION` |
|
|
43
|
-
| `onejs-sl/emit/hlsl-body` | `emitBody`: a program as a function body for a host's own frame |
|
|
43
|
+
| `onejs-sl/emit/hlsl-body` | `emitBody`: a program as a function body for a host's own frame; `emitLibrary`: the library functions it calls |
|
|
44
44
|
| `onejs-sl/emit/unity` | `emitShader`: the `.shader` a Unity editor generates, a frame over `emitBody` |
|
|
45
45
|
| `onejs-sl/emit/web` | `emitWGSL`, `emitGLSL`: OneJS's web frame |
|
|
46
|
+
| `onejs-sl/goldens.json` | the goldens (below), as data |
|
|
46
47
|
|
|
47
48
|
`src/entries.test.ts` pins every name, since removing one breaks a host.
|
|
48
49
|
|
|
@@ -50,16 +51,25 @@ only what it calls.
|
|
|
50
51
|
|
|
51
52
|
`emitBody(program, target)` prints the body only: one local per node, in the
|
|
52
53
|
HLSL and Metal shared subset (HLSL spelling, `fmod`, no `mul`, no `static`, no
|
|
53
|
-
derivatives
|
|
54
|
+
derivatives, no swizzle of a scalar, and every literal and scalar in an
|
|
55
|
+
intrinsic at its exact type, since Metal overloads where HLSL converts). The `BodyTarget` says what differs between hosts: an expression
|
|
54
56
|
for each input, the float4 holding a uniform slot, a texture sample, whether
|
|
55
57
|
`toLinear` is real (`colour: "linear"`) or the identity (`"gamma"`), and
|
|
56
58
|
optionally a local to assign the result to. It returns the uniform and texture
|
|
57
59
|
slots the body uses and the library functions it calls. OneJS's Unity shader is
|
|
58
60
|
one frame over it (`hlsl.ts`); Magerie's compute kernel is another, and its
|
|
59
61
|
target is in `src/body.test.ts` so an opcode cannot change without the text
|
|
60
|
-
Magerie compiles changing in front of a test.
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
Magerie compiles changing in front of a test.
|
|
63
|
+
|
|
64
|
+
`emitLibrary(body.uses.helpers, colour)` prints the library functions the body
|
|
65
|
+
calls, and everything they call, in the same subset and dependency order, with
|
|
66
|
+
the colour branch asked for: the text a host puts ahead of its frame. It is a
|
|
67
|
+
separate call because OneJS's own frame never needs it (the Unity shader
|
|
68
|
+
includes `SLCommon.cginc`), and a bundle that prints only Unity shaders should
|
|
69
|
+
not carry the library too. On a Mac with Xcode, `src/metal.test.ts` compiles
|
|
70
|
+
the whole library and a body for every corpus program and every builtin at
|
|
71
|
+
every width as Metal, behind the two defines a host's compatibility header
|
|
72
|
+
supplies (`frac`, `lerp`).
|
|
63
73
|
|
|
64
74
|
A uniform declared with a hex default (`uniform float4 tint = #ff8040;`), or
|
|
65
75
|
with `sl.uniform.colour`, is marked `colour: true`: its value is sRGB as
|
|
@@ -67,6 +77,42 @@ written, which is what a colour picker shows, and its reads convert. `inputsUsed
|
|
|
67
77
|
says which inputs the result depends on, dead nodes aside, so a host knows
|
|
68
78
|
whether a program animates.
|
|
69
79
|
|
|
80
|
+
## The helper library: one source
|
|
81
|
+
|
|
82
|
+
Value and simplex noise, the octave kinds, voronoi, hsv2rgb, `toLinear` and the
|
|
83
|
+
42 distance shapes are written once, in HLSL, in `lib/sdf2d.hlsl`,
|
|
84
|
+
`lib/noise2d.hlsl` and `lib/common.hlsl`. Everything else is printed from them
|
|
85
|
+
by `npm run lib`, and nobody edits the copies:
|
|
86
|
+
|
|
87
|
+
- **OneJS's `.cginc` files** (`SDF2D`, `Noise2D`, `SLCommon` in
|
|
88
|
+
`Resources/OneJS/`) are the source with an include guard and a generated
|
|
89
|
+
header around it. The VM, the generated Unity shaders and `fx` include them.
|
|
90
|
+
`npm run lib` writes them when the package sits in the OneJS container.
|
|
91
|
+
- **`src/lib/`**: `table.ts` (every function, its signature, what it calls, and
|
|
92
|
+
the shape table read from `sl_sdfDistance`'s switch), and the text of each
|
|
93
|
+
function as GLSL ES (`glsl.ts`), WGSL (`wgsl.ts`) and the shared subset
|
|
94
|
+
(`hlsl.ts`). The web emitters and `emitLibrary` take only what a program calls.
|
|
95
|
+
|
|
96
|
+
`lib/translate.ts` does the printing, at build time only. It reads a subset of
|
|
97
|
+
HLSL, listed at its top (functions, locals, `if`, bounded `for`, `switch`,
|
|
98
|
+
intrinsics, `mul(v, float2x2(...))`, and one preprocessor switch,
|
|
99
|
+
`UNITY_COLORSPACE_GAMMA`, which becomes the target's colour), and anything
|
|
100
|
+
outside it is an error with a file and line rather than a guess. HLSL converts
|
|
101
|
+
where GLSL, WGSL and Metal refuse, so it makes every conversion explicit first;
|
|
102
|
+
the printers then differ in spelling, plus what WGSL lacks (overloading,
|
|
103
|
+
ternaries, swizzle assignment, assignable parameters). On the web the colour
|
|
104
|
+
switch stays a runtime branch on the frame's `opt.x`, since a web build does not
|
|
105
|
+
know the project's colour space when it prints.
|
|
106
|
+
|
|
107
|
+
`src/lib/lib.test.ts` regenerates everything and fails on any byte of
|
|
108
|
+
difference, the `.cginc` copies included in the container, and OneJS's
|
|
109
|
+
`SLSharedLibraryTests` checks them from the other side. What proves the
|
|
110
|
+
translations draw the same picture: the goldens compile the whole library on
|
|
111
|
+
WebGPU and WebGL2 and draw every fixture; OneJS's parity harness holds the web
|
|
112
|
+
output to the VM; `SLSharedLibraryTests` draws every corpus program in Unity
|
|
113
|
+
through `SLCommon.cginc` and through the translated shared subset and requires
|
|
114
|
+
them to agree; and the Metal check above compiles the shared subset.
|
|
115
|
+
|
|
70
116
|
## Goldens
|
|
71
117
|
|
|
72
118
|
`goldens/goldens.json` is every corpus fixture drawn as a Linear OneJS game
|
|
@@ -81,8 +127,10 @@ profile (set `CHROME` to choose one). The two backends must agree within 1/255
|
|
|
81
127
|
over every pixel, and three anchors must match arithmetic, not each other:
|
|
82
128
|
`orient.sl` (orientation, and the linear to sRGB store), `hex.sl` (a hex colour
|
|
83
129
|
stores as written) and `texture.sl` (a texture's orientation and sRGB decode).
|
|
84
|
-
The
|
|
85
|
-
|
|
130
|
+
The whole translated library must also compile on both backends, including
|
|
131
|
+
the functions no fixture reaches. The file describes the sampling grid, the
|
|
132
|
+
times and the texture every sampled slot gets. A host imports it as
|
|
133
|
+
`onejs-sl/goldens.json`. No CI runner here has a GPU, so `src/goldens.test.ts` checks instead
|
|
86
134
|
that the file still covers the corpus at today's hashes; a change that moves a
|
|
87
135
|
hash fails there until the goldens are drawn again.
|
|
88
136
|
|
|
@@ -131,8 +179,8 @@ would rather write its programs out at build time.
|
|
|
131
179
|
|
|
132
180
|
**A third and fourth backend, for the browser.** A player cannot compile a
|
|
133
181
|
shader, but the page it runs in can. `web.ts` prints every program as WGSL and
|
|
134
|
-
as GLSL ES 3.00 (
|
|
135
|
-
|
|
182
|
+
as GLSL ES 3.00 (carrying the library functions it calls, translated from
|
|
183
|
+
`lib/*.hlsl`), and OneJS's
|
|
136
184
|
`Plugins/WebGL/OneJSSLWeb.jslib` compiles whichever one Unity's device speaks
|
|
137
185
|
and draws it into the element's target in place of the VM. A `.sl` import
|
|
138
186
|
carries both strings, printed at build time; an `encode()` result has them as
|
|
@@ -294,13 +342,13 @@ linear light. `sl.ramp` mixes its stops in sRGB, which is what reads as an even
|
|
|
294
342
|
ramp, and converts the result once through `TO_LINEAR`; `sl.color("#hex")` is
|
|
295
343
|
`parseColor` plus that conversion, and `sl.toLinear` is the conversion on its
|
|
296
344
|
own for a vec4 built from raw components. Both backends implement it gamma
|
|
297
|
-
aware (`sl_toLinear` in `
|
|
298
|
-
written. Alpha is coverage and is never converted. Same rule as `fx`.
|
|
345
|
+
aware (`sl_toLinear` in `lib/common.hlsl`), so a Gamma project gets the value
|
|
346
|
+
as written. Alpha is coverage and is never converted. Same rule as `fx`.
|
|
299
347
|
|
|
300
348
|
## Noise
|
|
301
349
|
|
|
302
350
|
`sl.noise`, `sl.simplex`, `sl.fbm(p, octaves, base)`, `sl.turbulence` and
|
|
303
|
-
`sl.ridged` are the fields `fx.noise` draws, from the same `
|
|
351
|
+
`sl.ridged` are the fields `fx.noise` draws, from the same `lib/noise2d.hlsl`, so
|
|
304
352
|
a simplex here is the simplex there. Octaves are 1 to 4. A program has no seed;
|
|
305
353
|
offset the input for a different field. `sl.simplex` used to be value noise on
|
|
306
354
|
a rotated lattice, and `sl.fbm` had its own value noise; both changed on
|
package/goldens/goldens.json
CHANGED
package/lib/common.hlsl
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Helpers shared by EVERY shader language backend.
|
|
2
|
+
//
|
|
3
|
+
// The VM (OneJS/FxProgram.shader) includes this as SLCommon.cginc, and every
|
|
4
|
+
// shader generated from a program by the HLSL emitter includes it too. The web
|
|
5
|
+
// emitters and a host's own frame (`emitBody`) get it translated, by
|
|
6
|
+
// `lib/translate.ts`, from this one text. That sharing is the whole reason this
|
|
7
|
+
// file exists: the backends have to agree, and the fastest way to make them
|
|
8
|
+
// disagree is to write `noise` twice.
|
|
9
|
+
//
|
|
10
|
+
// Anything an opcode needs that is more than one expression belongs here rather
|
|
11
|
+
// than in either backend. If it lives in only one, the golden image comparison
|
|
12
|
+
// fails on every program that touches it, and the failure looks like a bug in
|
|
13
|
+
// the program.
|
|
14
|
+
|
|
15
|
+
// Noise is the same code fx draws with (noise2d.hlsl), so `sl.simplex` and
|
|
16
|
+
// `fx.noise({ type: "simplex" })` mean one thing, and turbulence and ridged
|
|
17
|
+
// exist here for the same reason they exist there. A program has no seed of
|
|
18
|
+
// its own: offset the input to get a different field. The four kinds and
|
|
19
|
+
// their numbers are the contract with `onejsFbmKind`: 0 value fBm, 1 simplex
|
|
20
|
+
// fBm, 2 turbulence, 3 ridged, with the classic lacunarity 2 and gain 0.5.
|
|
21
|
+
// Voronoi keeps its own hash: the fx side has no voronoi to share yet.
|
|
22
|
+
float sl_hash21(float2 p)
|
|
23
|
+
{
|
|
24
|
+
p = frac(p * float2(123.34, 456.21));
|
|
25
|
+
p += dot(p, p + 45.32);
|
|
26
|
+
return frac(p.x * p.y);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
float sl_valueNoise(float2 p) { return onejsVNoise(p, 0.0); }
|
|
30
|
+
float sl_simplex(float2 p) { return onejsSimplex(p, 0.0); }
|
|
31
|
+
float sl_fbm(float2 p, int octaves, int kind)
|
|
32
|
+
{
|
|
33
|
+
return onejsFbmKind(kind, p, 0.0, clamp(octaves, 1, 4), 2.0, 0.5);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
float3 sl_hsv2rgb(float3 c)
|
|
37
|
+
{
|
|
38
|
+
float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
|
39
|
+
float3 p = abs(frac(c.xxx + K.xyz) * 6.0 - K.www);
|
|
40
|
+
return c.z * lerp(K.xxx, saturate(p - K.xxx), c.y);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Signed distance to a shape, positive outside.
|
|
45
|
+
*
|
|
46
|
+
* The id table is SDF_SHAPES in onejs-unity/src/fx/sdf.ts, shared with the fx
|
|
47
|
+
* pipeline so a shape means the same thing in both. A contract test compares
|
|
48
|
+
* them, because this dispatcher is a second copy of the one in FxSources and a
|
|
49
|
+
* copy is exactly the thing that drifts.
|
|
50
|
+
*
|
|
51
|
+
* The shader language passes four parameters where fx passes six. Position and
|
|
52
|
+
* rotation are deliberately absent: a program transforms the point before
|
|
53
|
+
* calling this, which is both how signed distance code is normally written and
|
|
54
|
+
* more composable than baking a transform into every shape.
|
|
55
|
+
*/
|
|
56
|
+
float sl_sdfDistance(int id, float2 p, float4 a, float2 b)
|
|
57
|
+
{
|
|
58
|
+
switch (id)
|
|
59
|
+
{
|
|
60
|
+
case 0: return sdCircle(p, a.x);
|
|
61
|
+
case 1: return sdRoundedBox(p, a.xy, float4(a.z, a.w, b.x, b.y));
|
|
62
|
+
case 2: return sdBox(p, a.xy);
|
|
63
|
+
case 3: return sdOrientedBox(p, a.xy, a.zw, b.x);
|
|
64
|
+
case 4: return sdSegment(p, a.xy, a.zw);
|
|
65
|
+
case 5: return sdRhombus(p, a.xy);
|
|
66
|
+
case 6: return sdTrapezoid(p, a.x, a.y, a.z);
|
|
67
|
+
case 7: return sdParallelogram(p, a.x, a.y, a.z);
|
|
68
|
+
case 8: return sdEquilateralTriangle(p, a.x);
|
|
69
|
+
case 9: return sdTriangleIsosceles(p, a.xy);
|
|
70
|
+
case 10: return sdTriangle(p, a.xy, a.zw, b.xy);
|
|
71
|
+
case 11: return sdUnevenCapsule(p, a.x, a.y, a.z);
|
|
72
|
+
case 12: return sdPentagon(p, a.x);
|
|
73
|
+
case 13: return sdHexagon(p, a.x);
|
|
74
|
+
case 14: return sdOctogon(p, a.x);
|
|
75
|
+
case 15: return sdHexagram(p, a.x);
|
|
76
|
+
case 16: return sdStar5(p, a.x, a.y);
|
|
77
|
+
case 17: return sdStar(p, a.x, (int)a.y, a.z);
|
|
78
|
+
case 18: return sdPie(p, a.xy, a.z);
|
|
79
|
+
case 19: return sdCutDisk(p, a.x, a.y);
|
|
80
|
+
case 20: return sdArc(p, a.xy, a.z, a.w);
|
|
81
|
+
case 21: return sdRing(p, a.xy, a.z, a.w);
|
|
82
|
+
case 22: return sdHorseshoe(p, a.xy, a.z, float2(a.w, b.x));
|
|
83
|
+
case 23: return sdVesica(p, a.x, a.y);
|
|
84
|
+
case 24: return sdOrientedVesica(p, a.xy, a.zw, b.x);
|
|
85
|
+
case 25: return sdMoon(p, a.x, a.y, a.z);
|
|
86
|
+
case 26: return sdRoundedCross(p, a.x);
|
|
87
|
+
case 27: return sdEgg(p, a.x, a.y, a.z, a.w);
|
|
88
|
+
case 28: return sdHeart(p);
|
|
89
|
+
case 29: return sdCross(p, a.xy, a.z);
|
|
90
|
+
case 30: return sdRoundedX(p, a.x, a.y);
|
|
91
|
+
case 31: return sdEllipse(p, a.xy);
|
|
92
|
+
case 32: return sdParabola(p, a.x);
|
|
93
|
+
case 33: return sdParabolaSegment(p, a.x, a.y);
|
|
94
|
+
case 34: return sdBezier(p, a.xy, a.zw, b.xy);
|
|
95
|
+
case 35: return sdBlobbyCross(p, a.x);
|
|
96
|
+
case 36: return sdTunnel(p, a.xy);
|
|
97
|
+
case 37: return sdStairs(p, a.xy, a.z);
|
|
98
|
+
case 38: return sdQuadraticCircle(p);
|
|
99
|
+
case 39: return sdHyberbola(p, a.x, a.y);
|
|
100
|
+
case 40: return sdCoolS(p);
|
|
101
|
+
case 41: return sdCircleWave(p, a.x, a.y);
|
|
102
|
+
default: return 1e6;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
float sl_voronoi(float2 p)
|
|
107
|
+
{
|
|
108
|
+
// Distance to the nearest of a jittered lattice of points. Written here
|
|
109
|
+
// rather than in either backend, for the same reason as the noise above.
|
|
110
|
+
float2 cell = floor(p);
|
|
111
|
+
float2 f = frac(p);
|
|
112
|
+
float best = 8.0;
|
|
113
|
+
[unroll]
|
|
114
|
+
for (int y = -1; y <= 1; y++)
|
|
115
|
+
{
|
|
116
|
+
[unroll]
|
|
117
|
+
for (int x = -1; x <= 1; x++)
|
|
118
|
+
{
|
|
119
|
+
float2 o = float2(x, y);
|
|
120
|
+
float2 jitter = float2(sl_hash21(cell + o), sl_hash21(cell + o + 37.7));
|
|
121
|
+
best = min(best, length(o + jitter - f));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return best;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
float sl_luminance(float3 c) { return dot(c, float3(0.2126, 0.7152, 0.0722)); }
|
|
128
|
+
|
|
129
|
+
// A colour as written (sRGB, like CSS) to the working space the target holds.
|
|
130
|
+
// Fx targets are linear float textures that a Linear colour space project
|
|
131
|
+
// encodes on display, so a stop stored as written drew lighter than its swatch.
|
|
132
|
+
// In a Gamma project the written value is already right. The float4 form keeps
|
|
133
|
+
// alpha, which is coverage rather than light. Mirrors fxColorToWorking.
|
|
134
|
+
//
|
|
135
|
+
// Every width uses Unity's cubic approximation, lane by lane. The VM has one
|
|
136
|
+
// TO_LINEAR for all widths (its registers are float4, so it converts .rgb),
|
|
137
|
+
// and a scalar or float2 on the exact curve here drew up to 5/255 apart from
|
|
138
|
+
// it in the dark range: the editor and a player disagreed.
|
|
139
|
+
//
|
|
140
|
+
// The curve is Unity's GammaToLinearSpace, written out here so that a target
|
|
141
|
+
// without UnityCG.cginc has it too. The switch below is the one piece of
|
|
142
|
+
// preprocessor the translator reads: it becomes the target's colour setting.
|
|
143
|
+
float3 sl_gammaToLinear(float3 c) { return c * (c * (c * 0.305306011 + 0.682171111) + 0.012522878); }
|
|
144
|
+
|
|
145
|
+
#ifdef UNITY_COLORSPACE_GAMMA
|
|
146
|
+
float sl_toLinear(float c) { return c; }
|
|
147
|
+
float2 sl_toLinear(float2 c) { return c; }
|
|
148
|
+
float3 sl_toLinear(float3 c) { return c; }
|
|
149
|
+
float4 sl_toLinear(float4 c) { return c; }
|
|
150
|
+
#else
|
|
151
|
+
float sl_toLinear(float c) { return sl_gammaToLinear(float3(c, 0, 0)).x; }
|
|
152
|
+
float2 sl_toLinear(float2 c) { return sl_gammaToLinear(float3(c, 0)).xy; }
|
|
153
|
+
float3 sl_toLinear(float3 c) { return sl_gammaToLinear(c); }
|
|
154
|
+
float4 sl_toLinear(float4 c) { return float4(sl_gammaToLinear(c.rgb), c.a); }
|
|
155
|
+
#endif
|
package/lib/noise2d.hlsl
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// Scrolling fBm value noise, shared by OneJS/TextureFX and OneJS/FxSources.
|
|
2
|
+
//
|
|
3
|
+
// Computed from a seed rather than sampled, so an effect needs no texture, any
|
|
4
|
+
// seed works without shipping art for it, and the field is infinite: scrolling
|
|
5
|
+
// never repeats and never has to tile.
|
|
6
|
+
//
|
|
7
|
+
// This lives in its own include because two shaders want it. Keeping a second
|
|
8
|
+
// copy in each would let them drift, and a noise field that differs by shader
|
|
9
|
+
// is the kind of thing nobody notices until two effects that should match do
|
|
10
|
+
// not.
|
|
11
|
+
|
|
12
|
+
float onejsHash21(float2 p, float seed)
|
|
13
|
+
{
|
|
14
|
+
p = frac(p * float2(123.34, 456.21) + seed * 0.1731);
|
|
15
|
+
p += dot(p, p + 45.32);
|
|
16
|
+
return frac(p.x * p.y);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
float onejsVNoise(float2 p, float seed)
|
|
20
|
+
{
|
|
21
|
+
float2 i = floor(p), f = frac(p);
|
|
22
|
+
f = f * f * (3.0 - 2.0 * f);
|
|
23
|
+
float a = onejsHash21(i, seed);
|
|
24
|
+
float b = onejsHash21(i + float2(1, 0), seed);
|
|
25
|
+
float c = onejsHash21(i + float2(0, 1), seed);
|
|
26
|
+
float d = onejsHash21(i + float2(1, 1), seed);
|
|
27
|
+
return lerp(lerp(a, b, f.x), lerp(c, d, f.x), f.y);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Octaves are capped at 4 and the loop is unrolled to that, because a dynamic
|
|
31
|
+
// trip count here costs more than the octaves it saves.
|
|
32
|
+
/// lacunarity is how much finer each octave gets, gain how much quieter.
|
|
33
|
+
/// The classic pair is 2 and 0.5; pushing lacunarity up and gain toward 1 gives
|
|
34
|
+
/// the stringy, turbulent look that reads as fire or smoke rather than cloud.
|
|
35
|
+
float onejsFbm(float2 p, float seed, int octaves, float lacunarity, float gain)
|
|
36
|
+
{
|
|
37
|
+
float sum = 0, amp = 0.5, norm = 0;
|
|
38
|
+
[unroll(4)]
|
|
39
|
+
for (int o = 0; o < 4; o++)
|
|
40
|
+
{
|
|
41
|
+
if (o >= octaves) break;
|
|
42
|
+
sum += onejsVNoise(p, seed + o * 19.0) * amp;
|
|
43
|
+
norm += amp;
|
|
44
|
+
p *= lacunarity;
|
|
45
|
+
amp *= gain;
|
|
46
|
+
}
|
|
47
|
+
return sum / max(norm, 1e-4);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// The classic parameters, for callers that do not care.
|
|
51
|
+
float onejsFbm(float2 p, float seed, int octaves)
|
|
52
|
+
{
|
|
53
|
+
return onejsFbm(p, seed, octaves, 2.0, 0.5);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// MARK: simplex
|
|
57
|
+
//
|
|
58
|
+
// Ashima/McEwan 2D simplex. Worth carrying next to the value noise because the
|
|
59
|
+
// two fail differently: value noise interpolates a square grid, so at high
|
|
60
|
+
// octave gain its cells show through as blocks, which is exactly what spoiled
|
|
61
|
+
// the first pass at the fire sample. Simplex is built on triangles and has no
|
|
62
|
+
// axis-aligned structure to leak.
|
|
63
|
+
|
|
64
|
+
float2 onejsMod289(float2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
|
65
|
+
float3 onejsMod289(float3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
|
66
|
+
float3 onejsPermute(float3 x) { return onejsMod289(((x * 34.0) + 1.0) * x); }
|
|
67
|
+
|
|
68
|
+
/// Raw simplex, roughly -1..1.
|
|
69
|
+
float onejsSimplexRaw(float2 v)
|
|
70
|
+
{
|
|
71
|
+
const float4 C = float4(0.211324865405187, 0.366025403784439,
|
|
72
|
+
-0.577350269189626, 0.024390243902439);
|
|
73
|
+
float2 i = floor(v + dot(v, C.yy));
|
|
74
|
+
float2 x0 = v - i + dot(i, C.xx);
|
|
75
|
+
float2 i1 = (x0.x > x0.y) ? float2(1.0, 0.0) : float2(0.0, 1.0);
|
|
76
|
+
float4 x12 = x0.xyxy + C.xxzz;
|
|
77
|
+
x12.xy -= i1;
|
|
78
|
+
i = onejsMod289(i);
|
|
79
|
+
float3 p = onejsPermute(onejsPermute(i.y + float3(0.0, i1.y, 1.0))
|
|
80
|
+
+ i.x + float3(0.0, i1.x, 1.0));
|
|
81
|
+
float3 m = max(0.5 - float3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
|
|
82
|
+
m = m * m; m = m * m;
|
|
83
|
+
float3 x = 2.0 * frac(p * C.www) - 1.0;
|
|
84
|
+
float3 h = abs(x) - 0.5;
|
|
85
|
+
float3 ox = floor(x + 0.5);
|
|
86
|
+
float3 a0 = x - ox;
|
|
87
|
+
m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);
|
|
88
|
+
float3 g;
|
|
89
|
+
g.x = a0.x * x0.x + h.x * x0.y;
|
|
90
|
+
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
|
|
91
|
+
return 130.0 * dot(m, g);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// 0..1, and seeded by displacing the input: simplex has no seed of its own.
|
|
95
|
+
float onejsSimplex(float2 p, float seed)
|
|
96
|
+
{
|
|
97
|
+
return onejsSimplexRaw(p + seed * 137.13) * 0.5 + 0.5;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
float onejsFbmSimplex(float2 p, float seed, int octaves, float lacunarity, float gain)
|
|
101
|
+
{
|
|
102
|
+
float sum = 0, amp = 0.5, norm = 0;
|
|
103
|
+
[unroll(4)]
|
|
104
|
+
for (int o = 0; o < 4; o++)
|
|
105
|
+
{
|
|
106
|
+
if (o >= octaves) break;
|
|
107
|
+
sum += onejsSimplex(p, seed + o * 19.0) * amp;
|
|
108
|
+
norm += amp;
|
|
109
|
+
p *= lacunarity;
|
|
110
|
+
amp *= gain;
|
|
111
|
+
}
|
|
112
|
+
return sum / max(norm, 1e-4);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// MARK: turbulence and ridged
|
|
116
|
+
//
|
|
117
|
+
// fBm above sums signed octaves, so octaves cancel as often as they add and the
|
|
118
|
+
// field reads as cloud. Turbulence sums the ABSOLUTE value of each octave: the
|
|
119
|
+
// zero crossings become creases, and the creases of every octave stack into
|
|
120
|
+
// the veins and licks that read as fire, smoke and marble. Ridged inverts the
|
|
121
|
+
// same crease so it is the bright line rather than the dark one, and squares
|
|
122
|
+
// it so the ridges stay sharp under the finer octaves. Both are built on
|
|
123
|
+
// simplex; a value noise crease follows the grid and shows as blocks.
|
|
124
|
+
|
|
125
|
+
/// 0..1. Perlin's turbulence: sum over octaves of |signed simplex|.
|
|
126
|
+
float onejsTurbulence(float2 p, float seed, int octaves, float lacunarity, float gain)
|
|
127
|
+
{
|
|
128
|
+
float sum = 0, amp = 0.5, norm = 0;
|
|
129
|
+
[unroll(4)]
|
|
130
|
+
for (int o = 0; o < 4; o++)
|
|
131
|
+
{
|
|
132
|
+
if (o >= octaves) break;
|
|
133
|
+
sum += abs(onejsSimplexRaw(p + (seed + o * 19.0) * 137.13)) * amp;
|
|
134
|
+
norm += amp;
|
|
135
|
+
p *= lacunarity;
|
|
136
|
+
amp *= gain;
|
|
137
|
+
}
|
|
138
|
+
return sum / max(norm, 1e-4);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// 0..1. Musgrave's ridged multifractal, without the feedback term: the
|
|
142
|
+
/// crease of |signed simplex| turned into a bright ridge and squared.
|
|
143
|
+
float onejsRidged(float2 p, float seed, int octaves, float lacunarity, float gain)
|
|
144
|
+
{
|
|
145
|
+
float sum = 0, amp = 0.5, norm = 0;
|
|
146
|
+
[unroll(4)]
|
|
147
|
+
for (int o = 0; o < 4; o++)
|
|
148
|
+
{
|
|
149
|
+
if (o >= octaves) break;
|
|
150
|
+
float r = 1.0 - abs(onejsSimplexRaw(p + (seed + o * 19.0) * 137.13));
|
|
151
|
+
sum += r * r * amp;
|
|
152
|
+
norm += amp;
|
|
153
|
+
p *= lacunarity;
|
|
154
|
+
amp *= gain;
|
|
155
|
+
}
|
|
156
|
+
return sum / max(norm, 1e-4);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/// Dispatches on the noise kind: 0 value fBm, 1 simplex fBm, 2 turbulence,
|
|
160
|
+
/// 3 ridged. The numbers are the contract with onejs-unity/src/fx/image.ts.
|
|
161
|
+
float onejsFbmKind(int kind, float2 p, float seed, int octaves, float lacunarity, float gain)
|
|
162
|
+
{
|
|
163
|
+
if (kind == 2) return onejsTurbulence(p, seed, octaves, lacunarity, gain);
|
|
164
|
+
if (kind == 3) return onejsRidged(p, seed, octaves, lacunarity, gain);
|
|
165
|
+
return kind == 1 ? onejsFbmSimplex(p, seed, octaves, lacunarity, gain)
|
|
166
|
+
: onejsFbm(p, seed, octaves, lacunarity, gain);
|
|
167
|
+
}
|