onejs-sl 0.1.0 → 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 +20 -0
- package/README.md +96 -7
- package/goldens/goldens.json +480 -0
- package/lib/common.hlsl +155 -0
- package/lib/noise2d.hlsl +167 -0
- package/lib/sdf2d.hlsl +510 -0
- package/package.json +9 -3
- package/src/body.ts +243 -0
- package/src/core.ts +1 -1
- package/src/emit/hlsl-body.ts +7 -0
- package/src/encode.ts +3 -24
- package/src/hlsl.ts +34 -143
- package/src/ir.ts +49 -2
- package/src/lang/lower.ts +6 -3
- 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/serial.ts +3 -1
- package/src/shapes.ts +4 -4
- package/src/sl.ts +27 -1
- package/src/web.ts +53 -28
- package/src/weblib.ts +0 -1303
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
|
+
}
|