Setting the file. One moment. Noise · Frost Sequence Camera Orbit · heygen-com/hyperframes · Skills DocsThree Mesh BVH LICENSE
70 of 76source/src/tsl/noise.ts
TypeScript·253 lines·7 KB
,
13 max,
14 min,
15 sqrt,
16 normalize,
17 cross,
18 sin,
19 cos,
20 int,
21 If,
22 Loop,
23 mx_noise_float,
24 mx_noise_vec3,
25 smoothstep,
26 select,
27} = tsl;
28
29type N = any;
30
31/** vec3 -> float hash in [0,1). */
32export const hash31 = Fn(([p]: [N]) => {
33 const q = fract(vec3(p).mul(vec3(0.1031, 0.103, 0.0973))).toVar();
34 q.addAssign(dot(q, q.yxz.add(33.33)));
35 return fract(q.x.add(q.y).mul(q.z));
36});
37
38/** vec3 -> vec3 hash in [0,1)^3. */
39export const hash33 = Fn(([p]: [N]) => {
40 const q = fract(vec3(p).mul(vec3(0.1031, 0.103, 0.0973))).toVar();
41 q.addAssign(dot(q, q.yxz.add(33.33)));
42 return fract(q.xxy.add(q.yxx).mul(q.zyx));
43});
44
45/** float -> float hash. */
46export const hash11 = Fn(([x]: [N]) => {
47 const q = fract(float(x).mul(0.1031)).toVar();
48 q.mulAssign(q.add(33.33));
49 q.mulAssign(q.add(q));
50 return fract(q);
51});
52
53/**
54 * Decorrelated per-particle random in [0,1) from a seed in [0,1) and a small integer channel `k`
55 * (hash11 on small inputs is smooth in its argument, which correlated grain rotations with the seed).
56 */
57export const hashSeed = (seed: N, k: number) =>
58 hash31(
59 vec3(
60 float(seed)
61 .mul(1024.7)
62 .add(k * 3.1),
63 float(seed)
64 .mul(2047.3)
65 .add(k * 7.7 + 1.3),
66 float(seed)
67 .mul(511.1)
68 .add(k * 13.9 + 2.9),
69 ),
70 );
71
72/** Perlin gradient noise, ~[-1,1]. */
73export const gnoise = (p: N) => mx_noise_float(p);
74
75/**
76 * vec3 gradient noise built from three float Perlin evaluations. (three's mx_noise_vec3 is avoided:
77 * its WGSL stalls SwiftShader's compiler in fragment shaders, which is our only headless check.)
78 */
79export const vnoise3 = (p: N) =>
80 vec3(
81 mx_noise_float(p),
82 mx_noise_float(vec3(p).yzx.add(vec3(17.1, 9.7, 3.3))),
83 mx_noise_float(vec3(p).zxy.add(vec3(31.7, 5.9, 21.3))),
84 );
85
86/** Fractal Brownian motion, unrolled `octaves` times. Returns roughly [-1,1]. */
87export function fbm(p: N, octaves: number, lacunarity = 2.0, gain = 0.5): N {
88 let sum: N = float(0);
89 let amp = 1;
90 let norm = 0;
91 let q: N = vec3(p);
92 for (let i = 0; i < octaves; i++) {
93 sum = sum.add(mx_noise_float(q).mul(amp));
94 norm += amp;
95 amp *= gain;
96 q = q.mul(lacunarity).add(vec3(17.3, 9.1, 31.7));
97 }
98 return sum.div(norm);
99}
100
101/** Ridged fbm in [0,1] (sharp creases) — used for frost crystal texture. */
102export function ridged(p: N, octaves: number): N {
103 let sum: N = float(0);
104 let amp = 1;
105 let norm = 0;
106 let q: N = vec3(p);
107 for (let i = 0; i < octaves; i++) {
108 const n = abs(mx_noise_float(q)).oneMinus();
109 sum = sum.add(n.mul(n).mul(amp));
110 norm += amp;
111 amp *= 0.5;
112 q = q.mul(2.1).add(vec3(5.2, 1.3, 8.7));
113 }
114 return sum.div(norm);
115}
116
117/** Divergence-free curl noise from a vec3 potential (finite differences). */
118export const curlNoise = Fn(([p]: [N]) => {
119 const e = float(0.02);
120 const dx = vec3(e, 0, 0),
121 dy = vec3(0, e, 0),
122 dz = vec3(0, 0, e);
123 const px0 = vnoise3(p.sub(dx)),
124 px1 = vnoise3(p.add(dx));
125 const py0 = vnoise3(p.sub(dy)),
126 py1 = vnoise3(p.add(dy));
127 const pz0 = vnoise3(p.sub(dz)),
128 pz1 = vnoise3(p.add(dz));
129 const x = py1.z.sub(py0.z).sub(pz1.y.sub(pz0.y));
130 const y = pz1.x.sub(pz0.x).sub(px1.z.sub(px0.z));
131 const z = px1.y.sub(px0.y).sub(py1.x.sub(py0.x));
132 return vec3(x, y, z).div(e.mul(2));
133});
134
135/**
136 * 3D Voronoi over the 27-cell neighbourhood.
137 * Returns vec4( nearest feature point (3), sqrt F1 ) — feature point acts as the cell id.
138 */
139export const voronoiCell = Fn(([p, seed]: [N, N]) => {
140 const ip = floor(p).toVar();
141 const fp = fract(p).toVar();
142 const f1 = float(8).toVar();
143 const center = vec3(0).toVar();
144 const rng = { start: int(-1), end: int(1), condition: "<=" };
145 Loop(rng, rng, rng, ({ i, j, k }: any) => {
146 const g = vec3(float(i), float(j), float(k));
147 const cell = ip.add(g);
148 const o = hash33(cell.add(seed));
149 const r = g.add(o).sub(fp);
150 const d = dot(r, r);
151 If(d.lessThan(f1), () => {
152 f1.assign(d);
153 center.assign(cell.add(o));
154 });
155 });
156 return vec4(center, sqrt(f1));
157});
158
159/**
160 * Cheaper Voronoi: feature points on a jittered lattice, only the 2x2x2 cells around p are searched.
161 * ~3x cheaper than the 27-cell version; the nearest point is occasionally missed near cell corners.
162 */
163export const voronoiCell8 = Fn(([p, seed]: [N, N]) => {
164 const ip = floor(vec3(p).sub(0.5)).toVar();
165 const f1 = float(8).toVar();
166 const center = vec3(0).toVar();
167 const rng = { start: int(0), end: int(1), condition: "<=" };
168 Loop(rng, rng, rng, ({ i, j, k }: any) => {
169 const cell = ip.add(vec3(float(i), float(j), float(k)));
170 const fp = cell.add(hash33(cell.add(seed)));
171 const r = fp.sub(p);
172 const d = dot(r, r);
173 If(d.lessThan(f1), () => {
174 f1.assign(d);
175 center.assign(fp);
176 });
177 });
178 return vec4(center, sqrt(f1));
179});
180
181/**
182 * Distance to the nearest Voronoi cell boundary (true plane distance, IQ-style second pass); boundaries whose
183 * pair-hash exceeds `coverage` are culled so cells are not fully enclosed. Returns vec4(boundary normal (3), distance).
184 */
185export const voronoiEdge = Fn(([p, seed, coverage]: [N, N, N]) => {
186 const ip = floor(p).toVar();
187 const fp = fract(p).toVar();
188 const md = float(8).toVar();
189 const mr = vec3(0).toVar();
190 const mc = vec3(0).toVar();
191 const rng1 = { start: int(-1), end: int(1), condition: "<=" };
192 Loop(rng1, rng1, rng1, ({ i, j, k }: any) => {
193 const g = vec3(float(i), float(j), float(k));
194 const cell = ip.add(g);
195 const o = hash33(cell.add(seed));
196 const r = g.add(o).sub(fp);
197 const d = dot(r, r);
198 If(d.lessThan(md), () => {
199 md.assign(d);
200 mr.assign(r);
201 mc.assign(cell);
202 });
203 });
204 md.assign(8);
205 const mn = vec3(0, 0, 1).toVar();
206 const rng2 = { start: int(-2), end: int(2), condition: "<=" };
207 Loop(rng2, rng2, rng2, ({ i, j, k }: any) => {
208 const g = vec3(float(i), float(j), float(k));
209 const cell = ip.add(g);
210 const o = hash33(cell.add(seed));
211 const r = g.add(o).sub(fp);
212 const diff = r.sub(mr);
213 const keep = hash31(cell.add(mc).mul(0.731).add(seed)).lessThan(coverage);
214 If(dot(diff, diff).greaterThan(0.00001).and(keep), () => {
215 const n = normalize(diff);
216 const d = dot(mr.add(r).mul(0.5), n);
217 If(d.lessThan(md), () => {
218 md.assign(d);
219 mn.assign(n);
220 });
221 });
222 });
223 return vec4(mn, md);
224});
225
226/** Smooth 0..1 threshold helper. */
227export const softThreshold = (x: N, threshold: N, softness: N) =>
228 smoothstep(
229 float(threshold).sub(float(softness).mul(0.5)),
230 float(threshold).add(float(softness).mul(0.5)),
231 x,
232 );
233
234/** Orthonormal basis (tangent, bitangent) for a normal. */
235export const basisFor = (n: N) => {
236 const nn = normalize(n);
237 const up = select(abs(nn.y).lessThan(0.99), vec3(0, 1, 0), vec3(1, 0, 0));
238 const t = normalize(cross(up, nn));
239 const b = cross(nn, t);
240 return { t, b };
241};
242
243/** Rotation of vector v around axis a by angle ang (Rodrigues). */
244export const rotateAxis = (v: N, a: N, ang: N) => {
245 const c = cos(ang),
246 s = sin(ang);
247 return v
248 .mul(c)
249 .add(cross(a, v).mul(s))
250 .add(a.mul(dot(a, v)).mul(float(1).sub(c)));
251};
252
253export const saturate = (x: N) => min(max(x, 0), 1);