Skip to content

Commit f85fa6b

Browse files
Update flake nodes to standard cellnoise functions (#2842)
This changelist updates the implementations of `flake2d` and `flake3d` to use standard cellnoise functions across all shading languages, including a new implementation in OSL. The following specific changes are included: - Replace the custom random number generator with standard cellnoise functions in all languages, using scalar cellnoise for density and priority rejection and vector cellnoise for flake rotation. - Optimize `mx_cell_noise_vec3` in GLSL and `mx_cell_noise_float3` in MDL to share a single bjmix across all three output channels, matching the existing optimization in the native OSL version. - Add OSL implementations of the `flake2d` and `flake3d` nodes, removing corresponding exclusions in the render test suite. - Fix the GLSL rotation matrix to use correct sine and cosine terms in the Arvo fast rotation construction, aligning with the MDL implementation. - Tighten the bounding sphere early-rejection test to the exact circumsphere of the flake cube. Fixes issue #2819.
1 parent f6dc35f commit f85fa6b

File tree

11 files changed

+254
-139
lines changed

11 files changed

+254
-139
lines changed

libraries/stdlib/genglsl/lib/mx_flake.glsl

Lines changed: 21 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,4 @@
1-
uint mx_flake_hash(uint seed, uint i)
2-
{
3-
return (i ^ seed) * 1075385539u;
4-
}
5-
6-
uint mx_flake_init_seed(ivec3 i)
7-
{
8-
return mx_flake_hash(mx_flake_hash(mx_flake_hash(0, i.x), i.y), i.z);
9-
}
10-
11-
uint mx_flake_xorshift32(uint seed)
12-
{
13-
seed ^= seed << 13;
14-
seed ^= seed >> 17;
15-
seed ^= seed << 5;
16-
return seed;
17-
}
18-
19-
float mx_uint_to_01(uint x)
20-
{
21-
return float(x) / float(0xffffffffu); // scale to [0, 1)
22-
}
1+
#include "mx_noise.glsl"
232

243
// "Fast Random Rotation Matrices" by James Arvo, Graphics Gems3 P.117
254
vec3 mx_rotate_flake(vec3 p, vec3 i)
@@ -35,12 +14,12 @@ vec3 mx_rotate_flake(vec3 p, vec3 i)
3514

3615
float s_theta = sin(theta);
3716
float c_theta = cos(theta);
38-
float sx = vx * s_theta - vy * s_theta;
39-
float sy = vx * c_theta + vy * c_theta;
17+
float sx = vx * c_theta - vy * s_theta;
18+
float sy = vx * s_theta + vy * c_theta;
4019

4120
mat3 m = mat3(
42-
vx * sx - s_theta, vx * sy - s_theta, vx * vz,
43-
vy * sx + c_theta, vy * sy - c_theta, vy * vz,
21+
vx * sx - c_theta, vx * sy - s_theta, vx * vz,
22+
vy * sx + s_theta, vy * sy - c_theta, vy * vz,
4423
vz * sx , vz * sy , 1.0 - z
4524
);
4625

@@ -77,51 +56,41 @@ void mx_flake(
7756

7857
vec3 P = position / vec3(size);
7958
vec3 base_P = floor(P);
80-
ivec3 base_P_int = ivec3(base_P);
8159

8260
// flake priority in [0..1], 0: no flake, flakes with higher priority shadow flakes "below" them
8361
float flake_priority = 0.0;
84-
uint flake_seed = 0;
62+
vec3 flake_cell = vec3(0.0);
8563

86-
// Examine the 3×3×3 lattice neighborhood around the sample cell. Flakes are seeded at cell
87-
// centers but can overlap adjacent cells by up to flake_diameter, so neighbors may contribute
88-
// at the sample position. For each neighbor we deterministically generate a seed, reject it
89-
// by the density probability, compute a per-flake priority, and test the rotated, centered
90-
// flake position for overlap. The highest-priority overlapping flake is selected.
64+
// Examine the 3x3x3 neighborhood of cells around the sample position, selecting the
65+
// highest-priority overlapping flake.
9166
for (int i = -1; i < 2; ++i)
9267
{
9368
for (int j = -1; j < 2; ++j)
9469
{
9570
for (int k = -1; k < 2; ++k)
9671
{
97-
uint seed = mx_flake_init_seed(base_P_int + ivec3(i, j, k));
72+
vec3 cell_pos = base_P + vec3(i, j, k);
9873

99-
seed = mx_flake_xorshift32(seed);
100-
if (mx_uint_to_01(seed) > probability)
74+
vec3 PP = P - cell_pos - vec3(0.5);
75+
if (dot(PP, PP) >= flake_diameter * flake_diameter * 3.0)
10176
continue;
10277

103-
seed = mx_flake_xorshift32(seed);
104-
float priority = mx_uint_to_01(seed);
105-
if (priority < flake_priority)
78+
if (mx_cell_noise_float(cell_pos) > probability)
10679
continue;
10780

108-
vec3 flake_P = base_P + vec3(i, j, k) + vec3(0.5);
109-
vec3 PP = P - flake_P;
110-
if (dot(PP, PP) >= flake_diameter * flake_diameter * 4.0)
81+
float priority = mx_cell_noise_float(vec4(cell_pos, 3.0));
82+
if (priority < flake_priority)
11183
continue;
11284

113-
vec3 rot;
114-
seed = mx_flake_xorshift32(seed); rot.x = mx_uint_to_01(seed);
115-
seed = mx_flake_xorshift32(seed); rot.y = mx_uint_to_01(seed);
116-
seed = mx_flake_xorshift32(seed); rot.z = mx_uint_to_01(seed);
85+
vec3 rot = mx_cell_noise_vec3(cell_pos);
11786
PP = mx_rotate_flake(PP, rot);
11887

11988
if (abs(PP.x) <= flake_diameter &&
12089
abs(PP.y) <= flake_diameter &&
12190
abs(PP.z) <= flake_diameter)
12291
{
12392
flake_priority = priority;
124-
flake_seed = seed;
93+
flake_cell = cell_pos;
12594
}
12695
}
12796
}
@@ -138,12 +107,12 @@ void mx_flake(
138107
}
139108

140109
// create a flake normal by importance sampling a microfacet distribution with given roughness
141-
uint seed = flake_seed;
142-
float xi0 = mx_uint_to_01(seed); seed = mx_flake_xorshift32(seed);
143-
float xi1 = mx_uint_to_01(seed); seed = mx_flake_xorshift32(seed);
110+
vec3 flake_noise = mx_cell_noise_vec3(vec4(flake_cell, 2.0));
111+
float xi0 = flake_noise.x;
112+
float xi1 = flake_noise.y;
144113

145-
id = int(seed); // not ideal but MaterialX does not support unsigned integer type
146-
rand = mx_uint_to_01(seed);
114+
rand = flake_noise.z;
115+
id = int(rand * 2147483647.0);
147116
presence = flake_priority;
148117

149118
float phi = M_PI * 2.0 * xi0;

libraries/stdlib/genglsl/lib/mx_noise.glsl

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -378,10 +378,16 @@ vec3 mx_cell_noise_vec3(vec3 p)
378378
int ix = mx_floor(p.x);
379379
int iy = mx_floor(p.y);
380380
int iz = mx_floor(p.z);
381+
uint a, b, c;
382+
a = b = c = uint(0xdeadbeef) + (4u << 2u) + 13u;
383+
a += uint(ix);
384+
b += uint(iy);
385+
c += uint(iz);
386+
mx_bjmix(a, b, c);
381387
return vec3(
382-
mx_bits_to_01(mx_hash_int(ix, iy, iz, 0)),
383-
mx_bits_to_01(mx_hash_int(ix, iy, iz, 1)),
384-
mx_bits_to_01(mx_hash_int(ix, iy, iz, 2))
388+
mx_bits_to_01(mx_bjfinal(a, b, c)),
389+
mx_bits_to_01(mx_bjfinal(a + 1u, b, c)),
390+
mx_bits_to_01(mx_bjfinal(a + 2u, b, c))
385391
);
386392
}
387393

@@ -391,10 +397,17 @@ vec3 mx_cell_noise_vec3(vec4 p)
391397
int iy = mx_floor(p.y);
392398
int iz = mx_floor(p.z);
393399
int iw = mx_floor(p.w);
400+
uint a, b, c;
401+
a = b = c = uint(0xdeadbeef) + (5u << 2u) + 13u;
402+
a += uint(ix);
403+
b += uint(iy);
404+
c += uint(iz);
405+
mx_bjmix(a, b, c);
406+
a += uint(iw);
394407
return vec3(
395-
mx_bits_to_01(mx_hash_int(ix, iy, iz, iw, 0)),
396-
mx_bits_to_01(mx_hash_int(ix, iy, iz, iw, 1)),
397-
mx_bits_to_01(mx_hash_int(ix, iy, iz, iw, 2))
408+
mx_bits_to_01(mx_bjfinal(a, b, c)),
409+
mx_bits_to_01(mx_bjfinal(a, b + 1u, c)),
410+
mx_bits_to_01(mx_bjfinal(a, b + 2u, c))
398411
);
399412
}
400413

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// "Fast Random Rotation Matrices" by James Arvo, Graphics Gems3 P.117
2+
vector mx_rotate_flake(vector p, vector i)
3+
{
4+
float theta = M_PI * 2.0 * i[0];
5+
float phi = M_PI * 2.0 * i[1];
6+
float z = i[2] * 2.0;
7+
8+
float r = sqrt(z);
9+
float vx = sin(phi) * r;
10+
float vy = cos(phi) * r;
11+
float vz = sqrt(2.0 - z);
12+
13+
float s_theta = sin(theta);
14+
float c_theta = cos(theta);
15+
float sx = vx * c_theta - vy * s_theta;
16+
float sy = vx * s_theta + vy * c_theta;
17+
18+
// Matrix-vector multiply (matching GLSL mat3 column-major layout)
19+
vector result;
20+
result[0] = (vx * sx - c_theta) * p[0] + (vy * sx + s_theta) * p[1] + (vz * sx) * p[2];
21+
result[1] = (vx * sy - s_theta) * p[0] + (vy * sy - c_theta) * p[1] + (vz * sy) * p[2];
22+
result[2] = (vx * vz) * p[0] + (vy * vz) * p[1] + (1.0 - z) * p[2];
23+
24+
return result;
25+
}
26+
27+
// compute a flake probability for a given flake coverage density x
28+
float mx_flake_density_to_probability(float x)
29+
{
30+
// constants for numerical fitted curve to observed flake noise density behavior
31+
float a = -26.19771808;
32+
float b = 26.39663835;
33+
float c = 85.53857017;
34+
float d = -102.35069432;
35+
float e = -101.42634862;
36+
float f = 118.45082288;
37+
float xx = x * x;
38+
39+
return (a * xx + b * x) / (c * xx * x + d * xx + e * x + f);
40+
}
41+
42+
void mx_flake(
43+
float size,
44+
float roughness,
45+
float coverage,
46+
vector position,
47+
vector N,
48+
vector tangent,
49+
vector bitangent,
50+
output int id,
51+
output float rand,
52+
output float presence,
53+
output vector flakenormal
54+
)
55+
{
56+
float probability = mx_flake_density_to_probability(clamp(coverage, 0.0, 1.0));
57+
float flake_diameter = 1.5 / sqrt(3.0);
58+
59+
vector P = position / vector(size);
60+
point base_P = point(floor(P));
61+
62+
// flake priority in [0..1], 0: no flake, flakes with higher priority shadow flakes "below" them
63+
float flake_priority = 0.0;
64+
point flake_cell = point(0);
65+
66+
// Examine the 3x3x3 neighborhood of cells around the sample position, selecting the
67+
// highest-priority overlapping flake.
68+
for (int i = -1; i < 2; ++i)
69+
{
70+
for (int j = -1; j < 2; ++j)
71+
{
72+
for (int k = -1; k < 2; ++k)
73+
{
74+
point cell_pos = base_P + vector(i, j, k);
75+
76+
vector PP = P - vector(cell_pos) - vector(0.5);
77+
if (dot(PP, PP) >= flake_diameter * flake_diameter * 3.0)
78+
continue;
79+
80+
if ((float) cellnoise(cell_pos) > probability)
81+
continue;
82+
83+
float priority = (float) cellnoise(cell_pos, 3.0);
84+
if (priority < flake_priority)
85+
continue;
86+
87+
vector rot = (vector) cellnoise(cell_pos);
88+
PP = mx_rotate_flake(PP, rot);
89+
90+
if (abs(PP[0]) <= flake_diameter &&
91+
abs(PP[1]) <= flake_diameter &&
92+
abs(PP[2]) <= flake_diameter)
93+
{
94+
flake_priority = priority;
95+
flake_cell = cell_pos;
96+
}
97+
}
98+
}
99+
}
100+
101+
if (flake_priority <= 0.0)
102+
{
103+
// no flake
104+
id = 0;
105+
rand = 0.0;
106+
presence = 0.0;
107+
flakenormal = N;
108+
return;
109+
}
110+
111+
// create a flake normal by importance sampling a microfacet distribution with given roughness
112+
vector flake_noise = cellnoise(flake_cell, 2.0);
113+
float xi0 = flake_noise[0];
114+
float xi1 = flake_noise[1];
115+
116+
rand = flake_noise[2];
117+
id = (int)(rand * 2147483647.0);
118+
presence = flake_priority;
119+
120+
float phi = M_PI * 2.0 * xi0;
121+
float tan_theta = roughness * roughness * sqrt(xi1) / sqrt(1.0 - xi1); // GGX
122+
float sin_theta = tan_theta / sqrt(1.0 + tan_theta * tan_theta);
123+
float cos_theta = sqrt(1.0 - sin_theta * sin_theta);
124+
125+
flakenormal = tangent * cos(phi) * sin_theta +
126+
bitangent * sin(phi) * sin_theta +
127+
N * cos_theta;
128+
flakenormal = normalize(flakenormal);
129+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include "lib/mx_flake.osl"
2+
3+
void mx_flake2d(
4+
float size,
5+
float roughness,
6+
float coverage,
7+
vector2 texcoord,
8+
vector N,
9+
vector tangent,
10+
vector bitangent,
11+
output int id,
12+
output float rand,
13+
output float presence,
14+
output vector flakenormal
15+
)
16+
{
17+
// reuse the 3d flake implementation. this could be optimized by using a 2d flake implementation
18+
vector position = vector(texcoord.x, texcoord.y, 0.0);
19+
mx_flake(size, roughness, coverage, position, N, tangent, bitangent, id, rand, presence, flakenormal);
20+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#include "lib/mx_flake.osl"
2+
3+
void mx_flake3d(
4+
float size,
5+
float roughness,
6+
float coverage,
7+
vector position,
8+
vector N,
9+
vector tangent,
10+
vector bitangent,
11+
output int id,
12+
output float rand,
13+
output float presence,
14+
output vector flakenormal
15+
)
16+
{
17+
mx_flake(size, roughness, coverage, position, N, tangent, bitangent, id, rand, presence, flakenormal);
18+
}

libraries/stdlib/genosl/stdlib_genosl_impl.mtlx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,12 @@
176176
<implementation name="IM_worleynoise3d_vector2_genosl" nodedef="ND_worleynoise3d_vector2" file="mx_worleynoise3d_vector2.osl" function="mx_worleynoise3d_vector2" target="genosl" />
177177
<implementation name="IM_worleynoise3d_vector3_genosl" nodedef="ND_worleynoise3d_vector3" file="mx_worleynoise3d_vector3.osl" function="mx_worleynoise3d_vector3" target="genosl" />
178178

179+
<!-- <flake2d> -->
180+
<implementation name="IM_flake2d_genosl" nodedef="ND_flake2d" file="mx_flake2d.osl" function="mx_flake2d" target="genosl" />
181+
182+
<!-- <flake3d> -->
183+
<implementation name="IM_flake3d_genosl" nodedef="ND_flake3d" file="mx_flake3d.osl" function="mx_flake3d" target="genosl" />
184+
179185
<!-- ======================================================================== -->
180186
<!-- Geometric nodes -->
181187
<!-- ======================================================================== -->

0 commit comments

Comments
 (0)