rayzee 7.20.2 → 7.21.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.
@@ -1,22 +1,11 @@
1
- import { Fn, float, vec2, vec3, int, If, dot, max, min, sqrt, cos, exp, mix, clamp, smoothstep } from 'three/tsl';
1
+ import { Fn, texture, float, vec2, vec3, If, dot, max, min, sqrt, cos, exp, mix, clamp, smoothstep } from 'three/tsl';
2
+ import { ClampToEdgeWrapping, DataTexture, FloatType, LinearFilter, RGBAFormat } from 'three';
2
3
 
3
- import {
4
- BRDFWeights,
5
- MaterialCache,
6
- ImportanceSamplingInfo,
7
- DFGResult,
8
- DotProducts,
4
+ import { BRDFWeights, MaterialCache, MaterialClassification, DFGResult } from './Struct.js';
9
5
 
10
- } from './Struct.js';
6
+ import { PI, PI_INV, TWO_PI, EPSILON, MIN_ROUGHNESS, MIN_CLEARCOAT_ROUGHNESS, MAX_ROUGHNESS, XYZ_TO_REC709, square, classifyMaterial } from './Common.js';
11
7
 
12
- import {
13
- PI, TWO_PI, EPSILON, MIN_ROUGHNESS,
14
- XYZ_TO_REC709, square, computeDotProductsAniso,
15
- } from './Common.js';
16
-
17
- import {
18
- fresnelSchlickFloat, fresnel0ToIor, iorToFresnel0Vec3, iorToFresnel0, dielectricF0,
19
- } from './Fresnel.js';
8
+ import { fresnelSchlickFloat, fresnel0ToIor, iorToFresnel0Vec3, iorToFresnel0 } from './Fresnel.js';
20
9
 
21
10
  // -----------------------------------------------------------------------------
22
11
  // Microfacet Distribution Functions
@@ -31,13 +20,39 @@ export const DistributionGGX = Fn( ( [ NoH, roughness ] ) => {
31
20
 
32
21
  } );
33
22
 
23
+ // Inverted-GGX sheen lobe (Estevez & Kulla 2017): GGX with α² replaced by 1/α², which turns the
24
+ // forward peak into the broad grazing rim sheen needs. Sampled by sheenSamplingRoughness below.
25
+ //
26
+ // The old `min( ..., 100 )` firefly guard is gone: it truncated the peak for sheenRoughness below
27
+ // ~0.2, and because the same function supplies the sampling pdf, a clamped D is a pdf the sampler
28
+ // never drew from. Importance sampling cancels D against its own pdf, so the raw value is safe.
34
29
  export const SheenDistribution = Fn( ( [ NoH, roughness ] ) => {
35
30
 
36
31
  const clampedRoughness = max( roughness, MIN_ROUGHNESS );
37
32
  const alpha = clampedRoughness.mul( clampedRoughness );
38
- const invAlpha = float( 1.0 ).div( alpha );
39
- const d = NoH.mul( NoH ).mul( invAlpha.mul( invAlpha ).sub( 1.0 ) ).add( 1.0 );
40
- return min( invAlpha.mul( invAlpha ).div( max( float( PI ).mul( d ).mul( d ), EPSILON ) ), 100.0 );
33
+ const invAlpha2 = float( 1.0 ).div( alpha.mul( alpha ) );
34
+ const d = NoH.mul( NoH ).mul( invAlpha2.sub( 1.0 ) ).add( 1.0 );
35
+ return invAlpha2.div( max( float( PI ).mul( d ).mul( d ), EPSILON ) );
36
+
37
+ } );
38
+
39
+ // The `roughness` to hand ImportanceSampleGGX so it draws from SheenDistribution. That helper
40
+ // builds A² = roughness⁴ while the sheen lobe is A² = 1/sheenRoughness⁴, so the parameter is the
41
+ // reciprocal. Passing sheenRoughness directly — as this used to — samples the ordinary forward
42
+ // GGX lobe and reports the inverted-GGX density for it, which is simply a different distribution.
43
+ export const sheenSamplingRoughness = Fn( ( [ roughness ] ) => {
44
+
45
+ return float( 1.0 ).div( max( roughness, MIN_ROUGHNESS ) );
46
+
47
+ } );
48
+
49
+ // Ashikhmin/Neubelt sheen visibility — carries the 1/(4·NoV·NoL) denominator like the GGX
50
+ // visibility terms, so the lobe is D · V, never D alone.
51
+ export const VisibilitySheen = Fn( ( [ NoV, NoL ] ) => {
52
+
53
+ return clamp( float( 1.0 ).div(
54
+ max( float( 4.0 ).mul( NoL.add( NoV ).sub( NoL.mul( NoV ) ) ), EPSILON )
55
+ ), 0.0, 1.0 );
41
56
 
42
57
  } );
43
58
 
@@ -45,19 +60,107 @@ export const SheenDistribution = Fn( ( [ NoH, roughness ] ) => {
45
60
  // Geometry Terms
46
61
  // -----------------------------------------------------------------------------
47
62
 
63
+ // Smith masking G1 for GGX: G1 = 2·NoV / (NoV + sqrt(α² + (1−α²)·NoV²)), α = roughness².
64
+ //
65
+ // This was `k = (roughness + 1)² / 8` — Karis's remap for ANALYTIC LIGHTS in a split-sum IBL
66
+ // pipeline, not a Smith G — and it landed in both the BRDF's G and, via calculateVNDFPDF, the
67
+ // sampling pdf. The anisotropic siblings always used the correct form, so a material shifted
68
+ // brightness between anisotropy 0 and 0.001.
48
69
  export const GeometrySchlickGGX = Fn( ( [ NdotV, roughness ] ) => {
49
70
 
50
- const r = roughness.add( 1.0 );
51
- const k = r.mul( r ).div( 8.0 );
52
- return NdotV.div( max( NdotV.mul( float( 1.0 ).sub( k ) ).add( k ), EPSILON ) );
71
+ const alpha = roughness.mul( roughness );
72
+ const alpha2 = alpha.mul( alpha );
73
+ const denom = NdotV.add( sqrt( alpha2.add( float( 1.0 ).sub( alpha2 ).mul( NdotV.mul( NdotV ) ) ) ) );
74
+ return float( 2.0 ).mul( NdotV ).div( max( denom, EPSILON ) );
75
+
76
+ } );
77
+
78
+ // Height-correlated Smith visibility for GGX — the isotropic twin of VisibilityGGXAniso, and
79
+ // like it this ALREADY carries the 1/(4·NoV·NoL) denominator: multiply straight by D and F.
80
+ //
81
+ // The isotropic BRDF used to assemble D·G·F/(4·NoV·NoL) with the SEPARABLE Smith G1(V)·G1(L),
82
+ // which assumes masking and shadowing are independent. They are not, and it overestimates G.
83
+ export const VisibilityGGXSmithCorrelated = Fn( ( [ NoV, NoL, roughness ] ) => {
84
+
85
+ const alpha = roughness.mul( roughness );
86
+ const alpha2 = alpha.mul( alpha );
87
+ const lambdaV = NoL.mul( sqrt( alpha2.add( float( 1.0 ).sub( alpha2 ).mul( NoV.mul( NoV ) ) ) ) );
88
+ const lambdaL = NoV.mul( sqrt( alpha2.add( float( 1.0 ).sub( alpha2 ).mul( NoL.mul( NoL ) ) ) ) );
89
+ return float( 0.5 ).div( max( lambdaV.add( lambdaL ), EPSILON ) );
53
90
 
54
91
  } );
55
92
 
56
- export const GeometrySmith = Fn( ( [ NoV, NoL, roughness ] ) => {
57
93
 
58
- const ggx2 = GeometrySchlickGGX( NoV, roughness );
59
- const ggx1 = GeometrySchlickGGX( NoL, roughness );
60
- return ggx1.mul( ggx2 );
94
+ // -----------------------------------------------------------------------------
95
+ // Directional albedo LUT
96
+ // -----------------------------------------------------------------------------
97
+ // R specular E(NoV, roughness) at F0 = 1 — the divisor the multiscatter compensation needs
98
+ // G sheen E(NoV, sheenRoughness) — inverted-GGX lobe with Ashikhmin/Neubelt
99
+ // visibility; what the base must be attenuated by
100
+ // B specular E(NoV, roughness) at F0 = 0 — Schlick's bias term
101
+ // A unused (RGBA because RGB float textures are not portable)
102
+ //
103
+ // All hemisphere integrals of this renderer's own lobes. Schlick is linear in F0, so
104
+ // E(F0) = F0·(R - B) + B is exact for any F0 — no fitted shape anywhere in the chain.
105
+ //
106
+ // This replaces Karis's analytic split-sum polynomial. That polynomial is a fit for a DIFFERENT
107
+ // integral (the split-sum IBL approximation) and is simply not the albedo of the BSDF integrated
108
+ // here: measured against this table it is off by up to 0.31 absolute, over-correcting at mid
109
+ // roughness (implying 0.725 where the truth is 0.899) and under-correcting at high roughness
110
+ // (0.45 where the truth is 0.343). Those two are exactly the +11 % and -26 % the white furnace
111
+ // reported on metal.
112
+ //
113
+ // 16×16, row = NoV, column = roughness, sampled at ENDPOINTS (index i is i/15), so the table
114
+ // covers roughness 1.0 rather than stopping at the last texel centre — that shortfall alone left
115
+ // rough metal 8.6 pp dark. Bilinear reconstruction lands within 0.002 rms / 0.018 max of a
116
+ // 262k-sample reference. Regenerate with `npm run bench:lut` if a lobe or its sampler changes —
117
+ // the table is only valid for the exact BRDF/sampler pair it was integrated from, and drifting out
118
+ // of calibration is exactly the failure this replaced.
119
+ const DFG_LUT_SIZE = 16;
120
+ const _dfgLutData = new Float32Array( [
121
+ 1.0000, 0.0302, 0.9039, 1.0000, 0.9712, 0.0898, 0.8635, 1.0000, 0.8914, 0.6742, 0.6952, 1.0000, 0.9176, 1.1716, 0.5703, 1.0000, 0.9447, 1.3763, 0.4529, 1.0000, 0.9592, 1.3948, 0.3509, 1.0000, 0.9658, 1.3111, 0.2690, 1.0000, 0.9679, 1.1830, 0.2061, 1.0000, 0.9669, 1.0453, 0.1588, 1.0000, 0.9639, 0.9156, 0.1237, 1.0000, 0.9592, 0.8006, 0.0978, 1.0000, 0.9533, 0.7013, 0.0786, 1.0000, 0.9465, 0.6167, 0.0644, 1.0000, 0.9388, 0.5447, 0.0537, 1.0000, 0.9304, 0.4836, 0.0457, 1.0000, 0.9214, 0.4314, 0.0395, 1.0000,
122
+ 1.0000, 0.0026, 0.7082, 1.0000, 0.9976, 0.0086, 0.7027, 1.0000, 0.9605, 0.1171, 0.6408, 1.0000, 0.9009, 0.4003, 0.5153, 1.0000, 0.8888, 0.6973, 0.4009, 1.0000, 0.9003, 0.8744, 0.3086, 1.0000, 0.9117, 0.9308, 0.2352, 1.0000, 0.9171, 0.9098, 0.1786, 1.0000, 0.9163, 0.8501, 0.1361, 1.0000, 0.9105, 0.7761, 0.1045, 1.0000, 0.9007, 0.7008, 0.0813, 1.0000, 0.8876, 0.6301, 0.0641, 1.0000, 0.8720, 0.5661, 0.0514, 1.0000, 0.8545, 0.5094, 0.0418, 1.0000, 0.8354, 0.4596, 0.0345, 1.0000, 0.8152, 0.4160, 0.0290, 1.0000,
123
+ 1.0000, 0.0003, 0.4889, 1.0000, 0.9994, 0.0020, 0.4874, 1.0000, 0.9894, 0.0281, 0.4696, 1.0000, 0.9511, 0.1226, 0.4131, 1.0000, 0.9046, 0.2856, 0.3306, 1.0000, 0.8809, 0.4505, 0.2533, 1.0000, 0.8752, 0.5628, 0.1913, 1.0000, 0.8737, 0.6152, 0.1439, 1.0000, 0.8693, 0.6237, 0.1084, 1.0000, 0.8596, 0.6057, 0.0821, 1.0000, 0.8444, 0.5742, 0.0629, 1.0000, 0.8245, 0.5370, 0.0487, 1.0000, 0.8007, 0.4985, 0.0382, 1.0000, 0.7739, 0.4613, 0.0303, 1.0000, 0.7450, 0.4264, 0.0244, 1.0000, 0.7147, 0.3943, 0.0199, 1.0000,
124
+ 1.0000, 0.0000, 0.3277, 1.0000, 0.9999, 0.0007, 0.3272, 1.0000, 0.9955, 0.0104, 0.3205, 1.0000, 0.9751, 0.0486, 0.2969, 1.0000, 0.9352, 0.1290, 0.2521, 1.0000, 0.8955, 0.2363, 0.1989, 1.0000, 0.8698, 0.3371, 0.1513, 1.0000, 0.8545, 0.4098, 0.1136, 1.0000, 0.8416, 0.4511, 0.0850, 1.0000, 0.8258, 0.4674, 0.0639, 1.0000, 0.8050, 0.4664, 0.0484, 1.0000, 0.7792, 0.4547, 0.0369, 1.0000, 0.7489, 0.4370, 0.0285, 1.0000, 0.7152, 0.4164, 0.0223, 1.0000, 0.6791, 0.3948, 0.0176, 1.0000, 0.6416, 0.3732, 0.0140, 1.0000,
125
+ 1.0000, 0.0000, 0.2121, 1.0000, 0.9999, 0.0000, 0.2119, 1.0000, 0.9976, 0.0047, 0.2095, 1.0000, 0.9854, 0.0224, 0.1999, 1.0000, 0.9564, 0.0638, 0.1784, 1.0000, 0.9163, 0.1289, 0.1472, 1.0000, 0.8792, 0.2046, 0.1149, 1.0000, 0.8508, 0.2736, 0.0871, 1.0000, 0.8274, 0.3262, 0.0653, 1.0000, 0.8038, 0.3603, 0.0489, 1.0000, 0.7767, 0.3787, 0.0368, 1.0000, 0.7450, 0.3853, 0.0278, 1.0000, 0.7089, 0.3836, 0.0213, 1.0000, 0.6693, 0.3766, 0.0164, 1.0000, 0.6275, 0.3662, 0.0127, 1.0000, 0.5845, 0.3541, 0.0100, 1.0000,
126
+ 1.0000, 0.0000, 0.1317, 1.0000, 1.0000, 0.0000, 0.1317, 1.0000, 0.9985, 0.0025, 0.1311, 1.0000, 0.9904, 0.0121, 0.1277, 1.0000, 0.9694, 0.0356, 0.1186, 1.0000, 0.9343, 0.0766, 0.1026, 1.0000, 0.8935, 0.1309, 0.0831, 1.0000, 0.8558, 0.1889, 0.0644, 1.0000, 0.8226, 0.2410, 0.0488, 1.0000, 0.7906, 0.2821, 0.0366, 1.0000, 0.7566, 0.3112, 0.0275, 1.0000, 0.7188, 0.3296, 0.0207, 1.0000, 0.6771, 0.3395, 0.0157, 1.0000, 0.6322, 0.3430, 0.0120, 1.0000, 0.5854, 0.3420, 0.0092, 1.0000, 0.5379, 0.3380, 0.0071, 1.0000,
127
+ 1.0000, 0.0000, 0.0778, 1.0000, 1.0000, 0.0000, 0.0778, 1.0000, 0.9990, 0.0015, 0.0779, 1.0000, 0.9931, 0.0073, 0.0773, 1.0000, 0.9774, 0.0220, 0.0743, 1.0000, 0.9481, 0.0490, 0.0673, 1.0000, 0.9079, 0.0883, 0.0569, 1.0000, 0.8651, 0.1349, 0.0456, 1.0000, 0.8239, 0.1820, 0.0352, 1.0000, 0.7839, 0.2242, 0.0267, 1.0000, 0.7427, 0.2585, 0.0201, 1.0000, 0.6986, 0.2844, 0.0152, 1.0000, 0.6513, 0.3026, 0.0114, 1.0000, 0.6014, 0.3144, 0.0087, 1.0000, 0.5501, 0.3212, 0.0066, 1.0000, 0.4989, 0.3243, 0.0051, 1.0000,
128
+ 1.0000, 0.0000, 0.0432, 1.0000, 1.0000, 0.0000, 0.0432, 1.0000, 0.9992, 0.0010, 0.0435, 1.0000, 0.9947, 0.0048, 0.0440, 1.0000, 0.9826, 0.0145, 0.0437, 1.0000, 0.9582, 0.0333, 0.0415, 1.0000, 0.9208, 0.0622, 0.0368, 1.0000, 0.8760, 0.0993, 0.0307, 1.0000, 0.8291, 0.1403, 0.0244, 1.0000, 0.7818, 0.1806, 0.0189, 1.0000, 0.7336, 0.2168, 0.0144, 1.0000, 0.6832, 0.2471, 0.0109, 1.0000, 0.6303, 0.2712, 0.0082, 1.0000, 0.5755, 0.2895, 0.0062, 1.0000, 0.5201, 0.3029, 0.0047, 1.0000, 0.4656, 0.3123, 0.0036, 1.0000,
129
+ 1.0000, 0.0000, 0.0221, 1.0000, 1.0000, 0.0000, 0.0222, 1.0000, 0.9995, 0.0007, 0.0225, 1.0000, 0.9958, 0.0033, 0.0232, 1.0000, 0.9860, 0.0102, 0.0239, 1.0000, 0.9656, 0.0237, 0.0238, 1.0000, 0.9316, 0.0454, 0.0224, 1.0000, 0.8869, 0.0750, 0.0196, 1.0000, 0.8363, 0.1101, 0.0162, 1.0000, 0.7831, 0.1473, 0.0128, 1.0000, 0.7282, 0.1833, 0.0099, 1.0000, 0.6715, 0.2160, 0.0076, 1.0000, 0.6130, 0.2442, 0.0057, 1.0000, 0.5535, 0.2676, 0.0043, 1.0000, 0.4942, 0.2866, 0.0033, 1.0000, 0.4368, 0.3017, 0.0025, 1.0000,
130
+ 1.0000, 0.0000, 0.0102, 1.0000, 1.0000, 0.0000, 0.0103, 1.0000, 0.9998, 0.0004, 0.0105, 1.0000, 0.9965, 0.0024, 0.0111, 1.0000, 0.9884, 0.0074, 0.0120, 1.0000, 0.9710, 0.0175, 0.0127, 1.0000, 0.9404, 0.0342, 0.0126, 1.0000, 0.8971, 0.0580, 0.0117, 1.0000, 0.8446, 0.0879, 0.0101, 1.0000, 0.7867, 0.1215, 0.0083, 1.0000, 0.7258, 0.1562, 0.0066, 1.0000, 0.6629, 0.1898, 0.0051, 1.0000, 0.5988, 0.2207, 0.0039, 1.0000, 0.5345, 0.2482, 0.0030, 1.0000, 0.4716, 0.2719, 0.0022, 1.0000, 0.4115, 0.2922, 0.0017, 1.0000,
131
+ 1.0000, 0.0000, 0.0041, 1.0000, 1.0000, 0.0000, 0.0041, 1.0000, 0.9999, 0.0003, 0.0043, 1.0000, 0.9970, 0.0018, 0.0047, 1.0000, 0.9901, 0.0056, 0.0054, 1.0000, 0.9751, 0.0133, 0.0061, 1.0000, 0.9476, 0.0265, 0.0065, 1.0000, 0.9063, 0.0458, 0.0065, 1.0000, 0.8531, 0.0712, 0.0059, 1.0000, 0.7918, 0.1013, 0.0051, 1.0000, 0.7256, 0.1341, 0.0041, 1.0000, 0.6568, 0.1676, 0.0033, 1.0000, 0.5871, 0.2002, 0.0025, 1.0000, 0.5182, 0.2308, 0.0019, 1.0000, 0.4517, 0.2586, 0.0015, 1.0000, 0.3891, 0.2836, 0.0011, 1.0000,
132
+ 1.0000, 0.0000, 0.0013, 1.0000, 1.0000, 0.0000, 0.0014, 1.0000, 0.9999, 0.0002, 0.0014, 1.0000, 0.9974, 0.0014, 0.0017, 1.0000, 0.9913, 0.0043, 0.0021, 1.0000, 0.9783, 0.0104, 0.0026, 1.0000, 0.9534, 0.0210, 0.0030, 1.0000, 0.9143, 0.0369, 0.0033, 1.0000, 0.8615, 0.0585, 0.0032, 1.0000, 0.7979, 0.0852, 0.0029, 1.0000, 0.7271, 0.1159, 0.0024, 1.0000, 0.6528, 0.1487, 0.0020, 1.0000, 0.5776, 0.1822, 0.0016, 1.0000, 0.5040, 0.2151, 0.0012, 1.0000, 0.4339, 0.2465, 0.0009, 1.0000, 0.3692, 0.2758, 0.0007, 1.0000,
133
+ 1.0000, 0.0000, 0.0003, 1.0000, 1.0000, 0.0000, 0.0003, 1.0000, 1.0000, 0.0001, 0.0004, 1.0000, 0.9977, 0.0010, 0.0005, 1.0000, 0.9923, 0.0034, 0.0007, 1.0000, 0.9807, 0.0083, 0.0010, 1.0000, 0.9581, 0.0169, 0.0012, 1.0000, 0.9214, 0.0301, 0.0015, 1.0000, 0.8695, 0.0486, 0.0015, 1.0000, 0.8044, 0.0724, 0.0015, 1.0000, 0.7299, 0.1007, 0.0013, 1.0000, 0.6504, 0.1325, 0.0011, 1.0000, 0.5699, 0.1663, 0.0009, 1.0000, 0.4916, 0.2010, 0.0007, 1.0000, 0.4181, 0.2353, 0.0006, 1.0000, 0.3513, 0.2686, 0.0004, 1.0000,
134
+ 1.0000, 0.0000, 0.0000, 1.0000, 1.0000, 0.0000, 0.0000, 1.0000, 1.0000, 0.0000, 0.0001, 1.0000, 0.9979, 0.0008, 0.0001, 1.0000, 0.9930, 0.0028, 0.0002, 1.0000, 0.9826, 0.0068, 0.0003, 1.0000, 0.9619, 0.0139, 0.0004, 1.0000, 0.9274, 0.0250, 0.0006, 1.0000, 0.8769, 0.0409, 0.0006, 1.0000, 0.8112, 0.0620, 0.0007, 1.0000, 0.7337, 0.0881, 0.0006, 1.0000, 0.6495, 0.1185, 0.0005, 1.0000, 0.5637, 0.1522, 0.0005, 1.0000, 0.4808, 0.1881, 0.0004, 1.0000, 0.4039, 0.2250, 0.0003, 1.0000, 0.3350, 0.2619, 0.0002, 1.0000,
135
+ 1.0000, 0.0000, 0.0000, 1.0000, 1.0000, 0.0000, 0.0000, 1.0000, 1.0000, 0.0000, 0.0000, 1.0000, 0.9980, 0.0006, 0.0000, 1.0000, 0.9936, 0.0023, 0.0000, 1.0000, 0.9841, 0.0056, 0.0001, 1.0000, 0.9651, 0.0115, 0.0001, 1.0000, 0.9327, 0.0210, 0.0002, 1.0000, 0.8837, 0.0348, 0.0002, 1.0000, 0.8179, 0.0535, 0.0002, 1.0000, 0.7381, 0.0775, 0.0002, 1.0000, 0.6497, 0.1064, 0.0002, 1.0000, 0.5589, 0.1396, 0.0002, 1.0000, 0.4713, 0.1763, 0.0002, 1.0000, 0.3911, 0.2154, 0.0001, 1.0000, 0.3203, 0.2557, 0.0001, 1.0000,
136
+ 1.0000, 0.0000, 0.0000, 1.0000, 1.0000, 0.0000, 0.0000, 1.0000, 1.0000, 0.0000, 0.0000, 1.0000, 0.9980, 0.0005, 0.0000, 1.0000, 0.9939, 0.0019, 0.0000, 1.0000, 0.9854, 0.0047, 0.0000, 1.0000, 0.9677, 0.0097, 0.0000, 1.0000, 0.9372, 0.0178, 0.0000, 1.0000, 0.8899, 0.0298, 0.0000, 1.0000, 0.8245, 0.0466, 0.0000, 1.0000, 0.7430, 0.0685, 0.0000, 1.0000, 0.6508, 0.0959, 0.0000, 1.0000, 0.5552, 0.1284, 0.0000, 1.0000, 0.4631, 0.1656, 0.0000, 1.0000, 0.3794, 0.2065, 0.0000, 1.0000, 0.3069, 0.2500, 0.0000, 1.0000,
137
+ ] );
138
+
139
+ const _dfgLutTexture = new DataTexture( _dfgLutData, DFG_LUT_SIZE, DFG_LUT_SIZE, RGBAFormat, FloatType );
140
+ _dfgLutTexture.minFilter = LinearFilter;
141
+ _dfgLutTexture.magFilter = LinearFilter;
142
+ _dfgLutTexture.wrapS = ClampToEdgeWrapping;
143
+ _dfgLutTexture.wrapT = ClampToEdgeWrapping;
144
+ _dfgLutTexture.generateMipmaps = false;
145
+ _dfgLutTexture.needsUpdate = true;
146
+
147
+ const dfgLutNode = texture( _dfgLutTexture );
148
+ dfgLutNode.setUpdateMatrix( false );
149
+
150
+ // Remaps a (roughness, NoV) query onto the LUT's texel centres. Entry i holds the value at
151
+ // i/(N-1), so a raw fetch would read the endpoints half a texel early at both ends.
152
+ const dfgLutUV = ( roughness, NoV ) => vec2( roughness, NoV )
153
+ .mul( ( DFG_LUT_SIZE - 1 ) / DFG_LUT_SIZE )
154
+ .add( 0.5 / DFG_LUT_SIZE );
155
+
156
+ /**
157
+ * Directional albedo of the sheen lobe — what fraction of incoming light it reflects, and so
158
+ * exactly what the base layer underneath must be attenuated by for the pair to conserve energy.
159
+ * Near zero head-on and rising toward grazing, because sheen is a rim lobe.
160
+ */
161
+ export const sheenDirectionalAlbedo = Fn( ( [ NoV, sheenRoughness ] ) => {
162
+
163
+ return clamp( dfgLutNode.sample( dfgLutUV( sheenRoughness, NoV ) ).g, 0.0, 1.0 );
61
164
 
62
165
  } );
63
166
 
@@ -76,26 +179,20 @@ export const GeometrySmith = Fn( ( [ NoV, NoL, roughness ] ) => {
76
179
  // halves the polynomial work versus calling two separate functions.
77
180
  export const evaluateDFG = Fn( ( [ F0, NoV, roughness ] ) => {
78
181
 
79
- // Analytical DFG approximation (Karis 2014)
80
- const r0 = float( 1.0 ).sub( roughness );
81
- const r1 = roughness.mul( - 0.0275 ).add( 0.0425 );
82
- const r2 = roughness.mul( - 0.572 ).add( 1.04 );
83
- const r3 = roughness.mul( 0.022 ).sub( 0.04 );
84
-
85
- // exp2(-9.28 * NoV) = exp(-6.4308 * NoV)
86
- const a004 = min( r0.mul( r0 ), exp( float( - 6.4308 ).mul( NoV ) ) ).mul( r0 ).add( r1 );
182
+ const lut = dfgLutNode.sample( dfgLutUV( roughness, NoV ) ).toVar();
87
183
 
88
- const dfgScale = float( - 1.04 ).mul( a004 ).add( r2 );
89
- const dfgBias = float( 1.04 ).mul( a004 ).add( r3 );
184
+ // Directional albedo at F0 = 1 and F0 = 0. Schlick is linear in F0, so these two pin the
185
+ // whole family exactly: E(F0) = F0·(Ew - bias) + bias. This used to be Karis's analytic
186
+ // split-sum polynomial, which fits a DIFFERENT integral — off by up to 0.31 absolute against
187
+ // this BSDF, and the source of metal's +11 %/-26 % white-furnace error.
188
+ const Ew = max( lut.r, 0.05 ).toVar();
189
+ const bias = clamp( lut.b, 0.0, 1.0 ).toVar();
190
+ const scale = max( Ew.sub( bias ), 0.0 );
90
191
 
91
- // Directional albedo at F0=1 (white furnace test)
92
- const Ew = max( dfgScale.add( dfgBias ), 0.1 );
93
-
94
- // Energy compensation: 1 + F0 * (1/Ew - 1)
192
+ // Kulla-Conty: 1 + F0·(1/Ew - 1).
95
193
  const compensation = vec3( 1.0 ).add( F0.mul( float( 1.0 ).div( Ew ).sub( 1.0 ) ) );
96
194
 
97
- // Single-scatter directional albedo per channel, then total with compensation
98
- const E_ss = max( F0.mul( dfgScale ).add( vec3( dfgBias ) ), vec3( 0.0 ) );
195
+ const E_ss = max( F0.mul( scale ).add( vec3( bias ) ), vec3( 0.0 ) );
99
196
  const E_total = clamp( E_ss.mul( compensation ), vec3( 0.0 ), vec3( 1.0 ) );
100
197
 
101
198
  return DFGResult( { compensation, E_total } );
@@ -106,14 +203,6 @@ export const evaluateDFG = Fn( ( [ F0, NoV, roughness ] ) => {
106
203
  // PDF Calculation Helpers
107
204
  // -----------------------------------------------------------------------------
108
205
 
109
- // Calculate PDF for standard GGX importance sampling
110
- // Formula: D(H) * NoH / (4 * VoH)
111
- export const calculateGGXPDF = Fn( ( [ NoH, VoH, roughness ] ) => {
112
-
113
- const D = DistributionGGX( NoH, roughness );
114
- return D.mul( NoH ).div( max( float( 4.0 ).mul( VoH ), EPSILON ) );
115
-
116
- } );
117
206
 
118
207
  // Calculate PDF for VNDF sampling
119
208
  // Formula: G1(V) * D(H) / (NoV * 4)
@@ -181,24 +270,6 @@ export const calculateVNDFPDFAniso = Fn( ( [ alphaT, alphaB, NoH, ToH, BoH, NoV,
181
270
 
182
271
  } );
183
272
 
184
- // Specular VNDF pdf (iso/aniso auto-select) for MIS sites that only have (N, V, L).
185
- export const specularVNDFPDFForDir = Fn( ( [ material, N, V, L ] ) => {
186
-
187
- const dots = DotProducts.wrap( computeDotProductsAniso( N, V, L, material ) );
188
- const pdf = float( 0.0 ).toVar();
189
- If( material.anisotropy.greaterThan( 0.0 ), () => {
190
-
191
- const a = computeAnisoAlphas( material.roughness, material.anisotropy );
192
- pdf.assign( calculateVNDFPDFAniso( a.x, a.y, dots.NoH, dots.ToH, dots.BoH, dots.NoV, dots.ToV, dots.BoV ) );
193
-
194
- } ).Else( () => {
195
-
196
- pdf.assign( calculateVNDFPDF( dots.NoH, dots.NoV, max( material.roughness, 0.02 ) ) );
197
-
198
- } );
199
- return pdf;
200
-
201
- } );
202
273
 
203
274
  // -----------------------------------------------------------------------------
204
275
  // Iridescence Evaluation
@@ -323,7 +394,13 @@ export const calculateBRDFWeights = Fn( ( [ material, mc, cache ] ) => {
323
394
  } );
324
395
 
325
396
  const specular = baseSpecularWeight.mul( material.specularIntensity ).toVar();
326
- const diffuse = float( 1.0 ).sub( baseSpecularWeight ).mul( float( 1.0 ).sub( material.metalness ) ).toVar();
397
+ // (1 - transmission) is load-bearing: a fully transmissive dielectric has no diffuse lobe, and
398
+ // without this glass spent ~23 % of its samples proposing cosine directions whose BRDF value is
399
+ // ~0 while refraction went under-sampled. The old inline MIS weights in LightsSampling had this
400
+ // factor; lobe selection never did.
401
+ const diffuse = float( 1.0 ).sub( baseSpecularWeight )
402
+ .mul( float( 1.0 ).sub( material.metalness ) )
403
+ .mul( float( 1.0 ).sub( material.transmission ) ).toVar();
327
404
  const sheen = material.sheen.mul( cache.maxSheenColor ).toVar();
328
405
 
329
406
  const clearcoat = float( 0.0 ).toVar();
@@ -382,143 +459,99 @@ export const calculateBRDFWeights = Fn( ( [ material, mc, cache ] ) => {
382
459
  } );
383
460
 
384
461
  // -----------------------------------------------------------------------------
385
- // Importance Sampling Info
462
+ // BSDF sampling density — the single source of truth for MIS
386
463
  // -----------------------------------------------------------------------------
464
+ // The exact density of the direction generateSampledDirection produces, for the upper hemisphere.
465
+ //
466
+ // That sampler picks lobe j with probability weights.j then samples lobe j, so the density of a
467
+ // resulting direction is the sum over every lobe that could have produced it. Two separate
468
+ // requirements force every MIS site through this one function: the weights must sum to 1, which
469
+ // needs both strategies evaluating the same p_bsdf; and each estimator must divide by its own
470
+ // true density.
471
+ //
472
+ // Transmission is absent because it only produces NoL < 0. `weights` is normalised over all
473
+ // lobes including transmission, so omitting its term is exact, not an approximation.
474
+ export const calculateBSDFSamplingPDF = Fn( ( [ material, weights, dots ] ) => {
387
475
 
388
- export const getImportanceSamplingInfo = Fn( ( [
389
- material, bounceIndex, mc,
390
- ] ) => {
391
-
392
- // Base BRDF weights using temporary cache
393
- const tempInvRoughness = float( 1.0 ).sub( material.roughness );
394
- const tempMetalFactor = float( 0.5 ).add( float( 0.5 ).mul( material.metalness ) );
395
- const tempIorFactor = min( float( 2.0 ).div( material.ior ), 1.0 );
396
- const tempMaxSheenColor = max( material.sheenColor.r, max( material.sheenColor.g, material.sheenColor.b ) );
397
-
398
- const tempCache = MaterialCache( {
399
- F0: dielectricF0( material.ior ),
400
- NoV: float( 0.5 ),
401
- diffuseColor: material.color.rgb,
402
- isPurelyDiffuse: false,
403
- alpha: material.roughness.mul( material.roughness ),
404
- k: material.roughness.add( 1.0 ).mul( material.roughness.add( 1.0 ) ).div( 8.0 ),
405
- alpha2: material.roughness.mul( material.roughness ).mul( material.roughness ).mul( material.roughness ),
406
- invRoughness: tempInvRoughness,
407
- metalFactor: tempMetalFactor,
408
- iorFactor: tempIorFactor,
409
- maxSheenColor: tempMaxSheenColor,
410
- } );
476
+ const pdf = float( 0.0 ).toVar();
411
477
 
412
- const weights = BRDFWeights.wrap( calculateBRDFWeights( material, mc, tempCache ) );
478
+ If( dots.NoL.greaterThan( 0.0 ), () => {
413
479
 
414
- const diffuseImportance = weights.diffuse.toVar();
415
- const specularImportance = weights.specular.toVar();
416
- const transmissionImportance = weights.transmission.toVar();
417
- const clearcoatImportance = weights.clearcoat.toVar();
480
+ pdf.addAssign( weights.diffuse.mul( dots.NoL ).mul( PI_INV ) );
418
481
 
419
- // Depth adjustments
420
- If( bounceIndex.greaterThan( int( 2 ) ), () => {
482
+ const specPdf = float( 0.0 ).toVar();
483
+ If( material.anisotropy.greaterThan( 0.0 ), () => {
421
484
 
422
- const depthFactor = float( 1.0 ).div( float( bounceIndex ).sub( 1.0 ) );
423
- specularImportance.mulAssign( float( 0.8 ).add( depthFactor.mul( 0.2 ) ) );
424
- clearcoatImportance.mulAssign( float( 0.7 ).add( depthFactor.mul( 0.3 ) ) );
425
- diffuseImportance.mulAssign( float( 1.0 ).add( depthFactor.mul( 0.2 ) ) );
485
+ const a = computeAnisoAlphas( material.roughness, material.anisotropy );
486
+ specPdf.assign( calculateVNDFPDFAniso( a.x, a.y, dots.NoH, dots.ToH, dots.BoH, dots.NoV, dots.ToV, dots.BoV ) );
426
487
 
427
- } );
488
+ } ).Else( () => {
428
489
 
429
- // Material-specific boosts
430
- If( mc.isMetallic.and( bounceIndex.lessThan( int( 3 ) ) ), () => {
490
+ // Raw roughness, not clamped: sampleGGXVNDF is called with material.roughness, and a
491
+ // clamp here would report a density the sampler never drew from.
492
+ specPdf.assign( calculateVNDFPDF( dots.NoH, dots.NoV, material.roughness ) );
431
493
 
432
- specularImportance.assign( max( specularImportance, 0.6 ) );
433
- diffuseImportance.mulAssign( 0.4 );
494
+ } );
495
+ pdf.addAssign( weights.specular.mul( specPdf ) );
434
496
 
435
- } );
497
+ If( weights.sheen.greaterThan( 0.0 ), () => {
436
498
 
437
- If( mc.isTransmissive, () => {
499
+ const sheenPdf = SheenDistribution( dots.NoH, material.sheenRoughness )
500
+ .mul( dots.NoH ).div( max( float( 4.0 ).mul( dots.VoH ), EPSILON ) );
501
+ pdf.addAssign( weights.sheen.mul( sheenPdf ) );
438
502
 
439
- transmissionImportance.assign( max( transmissionImportance, 0.8 ) );
440
- diffuseImportance.mulAssign( 0.2 );
441
- specularImportance.mulAssign( 0.6 );
503
+ } );
442
504
 
443
- } );
505
+ If( weights.clearcoat.greaterThan( 0.0 ), () => {
444
506
 
445
- If( mc.hasClearcoat, () => {
507
+ const ccRoughness = clamp( material.clearcoatRoughness, MIN_CLEARCOAT_ROUGHNESS, MAX_ROUGHNESS );
508
+ pdf.addAssign( weights.clearcoat.mul( calculateVNDFPDF( dots.NoH, dots.NoV, ccRoughness ) ) );
446
509
 
447
- clearcoatImportance.assign( max( clearcoatImportance, 0.4 ) );
510
+ } );
448
511
 
449
512
  } );
450
513
 
451
- // Normalize material strategies to sum to 1.0
452
- // (environment is handled via deterministic NEE in direct lighting, not an indirect strategy)
453
- const sum = diffuseImportance.add( specularImportance ).add( transmissionImportance )
454
- .add( clearcoatImportance ).toVar();
455
-
456
- If( sum.greaterThan( 0.001 ), () => {
514
+ return pdf;
457
515
 
458
- const invSum = float( 1.0 ).div( sum );
459
- diffuseImportance.mulAssign( invSum );
460
- specularImportance.mulAssign( invSum );
461
- transmissionImportance.mulAssign( invSum );
462
- clearcoatImportance.mulAssign( invSum );
516
+ } );
463
517
 
464
- } ).Else( () => {
518
+ // Selection probabilities from `material` alone, for samplers outside the wavefront's
519
+ // classify-once path. Must be the same weights whose density calculateBSDFSamplingPDF reports.
520
+ export const calculateBRDFWeightsFromMaterial = Fn( ( [ material ] ) => {
465
521
 
466
- diffuseImportance.assign( 1.0 );
467
- specularImportance.assign( 0.0 );
468
- transmissionImportance.assign( 0.0 );
469
- clearcoatImportance.assign( 0.0 );
522
+ const mc = MaterialClassification.wrap( classifyMaterial(
523
+ material.metalness, material.roughness, material.transmission,
524
+ material.clearcoat, material.emissive, material.subsurface,
525
+ ) );
470
526
 
527
+ const cache = MaterialCache( {
528
+ invRoughness: float( 1.0 ).sub( material.roughness ),
529
+ metalFactor: float( 0.5 ).add( float( 0.5 ).mul( material.metalness ) ),
530
+ iorFactor: min( float( 2.0 ).div( material.ior ), 1.0 ),
531
+ maxSheenColor: max( material.sheenColor.x, max( material.sheenColor.y, material.sheenColor.z ) ),
471
532
  } );
472
533
 
473
- return ImportanceSamplingInfo( {
474
- diffuseImportance,
475
- specularImportance,
476
- transmissionImportance,
477
- clearcoatImportance,
478
- } );
534
+ return calculateBRDFWeights( material, mc, cache );
479
535
 
480
536
  } );
481
537
 
482
- // -----------------------------------------------------------------------------
483
- // Material Cache Creation
484
- // -----------------------------------------------------------------------------
538
+ // calculateBSDFSamplingPDF for callers holding only `material` and `dots`. Rebuilds the
539
+ // classification and weight cache, so prefer the struct-taking form where those already exist
540
+ // (the wavefront classifies once per shade).
541
+ export const calculateBSDFSamplingPDFFromMaterial = Fn( ( [ material, dots ] ) => {
485
542
 
486
- export const createMaterialCache = Fn( ( [ N, V, material, samples, mc ] ) => {
543
+ const weights = BRDFWeights.wrap( calculateBRDFWeightsFromMaterial( material ) );
544
+ return calculateBSDFSamplingPDF( material, weights, dots );
487
545
 
488
- const NoV = max( dot( N, V ), 0.001 ).toVar();
546
+ } );
489
547
 
490
- const isPurelyDiffuse = mc.isRough.and( mc.isMetallic.not() )
491
- .and( material.transmission.equal( 0.0 ) )
492
- .and( material.clearcoat.equal( 0.0 ) )
493
- .toVar();
548
+ // -----------------------------------------------------------------------------
549
+ // Importance Sampling Info
550
+ // -----------------------------------------------------------------------------
494
551
 
495
- const alpha = samples.roughness.mul( samples.roughness ).toVar();
496
- const alpha2 = alpha.mul( alpha ).toVar();
497
- const r = samples.roughness.add( 1.0 );
498
- const k = r.mul( r ).div( 8.0 ).toVar();
499
-
500
- const baseF0 = dielectricF0( material.ior ).mul( material.specularColor );
501
- const F0 = mix( baseF0, samples.albedo.rgb, samples.metalness ).mul( material.specularIntensity ).toVar();
502
- const diffuseColor = samples.albedo.rgb.mul( float( 1.0 ).sub( samples.metalness ) ).toVar();
503
-
504
- const invRoughness = float( 1.0 ).sub( samples.roughness ).toVar();
505
- const metalFactor = float( 0.5 ).add( float( 0.5 ).mul( samples.metalness ) ).toVar();
506
- const iorFactor = min( float( 2.0 ).div( material.ior ), 1.0 ).toVar();
507
- const maxSheenColor = max( material.sheenColor.r, max( material.sheenColor.g, material.sheenColor.b ) ).toVar();
508
-
509
- return MaterialCache( {
510
- F0,
511
- NoV,
512
- diffuseColor,
513
- isPurelyDiffuse,
514
- alpha,
515
- k,
516
- alpha2,
517
- invRoughness,
518
- metalFactor,
519
- iorFactor,
520
- maxSheenColor,
521
- } );
522
552
 
523
- } );
553
+ // -----------------------------------------------------------------------------
554
+ // Material Cache Creation
555
+ // -----------------------------------------------------------------------------
556
+
524
557
 
@@ -1,32 +1,14 @@
1
1
  // Material Transmission & Refraction - Ported from material_transmission.fs
2
2
  // Handles both volumetric transmission AND opacity-based transparency
3
3
 
4
- import {
5
- Fn,
6
- wgslFn,
7
- vec2,
8
- vec3,
9
- float,
10
- int,
11
- bool as tslBool,
12
- If,
13
- select,
14
- abs,
15
- dot,
16
- reflect,
17
- refract,
18
- max,
19
- mix,
20
- clamp,
21
- exp,
22
- } from 'three/tsl';
4
+ import { Fn, wgslFn, vec3, float, int, bool as tslBool, If, select, abs, dot, reflect, refract, max, mix, clamp, exp } from 'three/tsl';
23
5
 
24
6
  import { struct } from './patches.js';
25
7
  import { EPSILON, MIN_ROUGHNESS, MIN_PDF } from './Common.js';
26
8
  import { iorToFresnel0, fresnelSchlickFloat } from './Fresnel.js';
27
9
  import { DistributionGGX } from './MaterialProperties.js';
28
10
  import { ImportanceSampleGGX } from './MaterialSampling.js';
29
- import { RandomValue, pcgHash } from './Random.js';
11
+ import { getRandomSample1D, getRandomSample2D, pcgHash } from './Random.js';
30
12
  import { handleSubsurfaceEntry, SubsurfaceEntryResult } from './Subsurface.js';
31
13
 
32
14
  // ================================================================================
@@ -215,7 +197,8 @@ export const calculateShadowTransmittance = Fn( ( [ rayDir, normal, material, en
215
197
  // ================================================================================
216
198
 
217
199
  export const sampleMicrofacetTransmission = Fn( ( [
218
- V, N, ior, roughness, entering, dispersion, xi, rngState, pathWavelength
200
+ V, N, ior, roughness, entering, dispersion, xi, rngState, pathWavelength,
201
+ pixelCoord, resolution, frame, dimBase,
219
202
  ] ) => {
220
203
 
221
204
  const result = MicrofacetTransmissionResult( {
@@ -244,7 +227,7 @@ export const sampleMicrofacetTransmission = Fn( ( [
244
227
 
245
228
  } ).Else( () => {
246
229
 
247
- const spectralSample = SpectralSample.wrap( sampleWavelengthForDispersion( ior, dispersion, RandomValue( rngState ) ) );
230
+ const spectralSample = SpectralSample.wrap( sampleWavelengthForDispersion( ior, dispersion, getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 5 ) ), rngState, resolution, frame ) ) );
248
231
  etaRatio.assign( select( entering, float( 1.0 ).div( spectralSample.ior ), spectralSample.ior ) );
249
232
  result.colorWeight.assign( spectralSample.colorWeight );
250
233
  result.pathWavelength.assign( spectralSample.wavelength );
@@ -290,7 +273,7 @@ export const sampleMicrofacetTransmission = Fn( ( [
290
273
 
291
274
  } ).Else( () => {
292
275
 
293
- const spectralSample = SpectralSample.wrap( sampleWavelengthForDispersion( ior, dispersion, RandomValue( rngState ) ) );
276
+ const spectralSample = SpectralSample.wrap( sampleWavelengthForDispersion( ior, dispersion, getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 5 ) ), rngState, resolution, frame ) ) );
294
277
  etaRatio.assign( select( entering, float( 1.0 ).div( spectralSample.ior ), spectralSample.ior ) );
295
278
  result.colorWeight.assign( spectralSample.colorWeight );
296
279
  result.pathWavelength.assign( spectralSample.wavelength );
@@ -348,6 +331,7 @@ export const sampleMicrofacetTransmission = Fn( ( [
348
331
 
349
332
  export const handleTransmission = Fn( ( [
350
333
  rayDir, normal, material, entering, rngState,
334
+ pixelCoord, resolution, frame, dimBase,
351
335
  currentMediumIOR, previousMediumIOR, pathWavelength,
352
336
  ] ) => {
353
337
 
@@ -400,20 +384,18 @@ export const handleTransmission = Fn( ( [
400
384
  reflectProb.assign( clamp( reflectProb, 0.05, 0.95 ) );
401
385
 
402
386
  // Force reflection if TIR, otherwise probabilistically choose
403
- const doReflect = totalInternalReflection.or( RandomValue( rngState ).lessThan( reflectProb ) ).toVar();
387
+ const doReflect = totalInternalReflection.or( getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 4 ) ), rngState, resolution, frame ).lessThan( reflectProb ) ).toVar();
404
388
  result.didReflect.assign( doReflect );
405
389
 
406
390
  // Choose random sample for microfacet sampling
407
- const xi_r1 = RandomValue( rngState ).toVar();
408
- const xi_r2 = RandomValue( rngState ).toVar();
409
- const xi = vec2( xi_r1, xi_r2 );
391
+ const xi = getRandomSample2D( pixelCoord, int( 0 ), dimBase.add( int( 4 ) ), rngState, resolution, frame ).toVar();
410
392
 
411
393
  If( doReflect, () => {
412
394
 
413
395
  // Reflection at a transmissive surface — no wavelength locking
414
396
  If( material.roughness.greaterThan( 0.05 ), () => {
415
397
 
416
- const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission( V, N, material.ior, material.roughness, entering, float( 0.0 ), xi, rngState, float( 0.0 ) ) );
398
+ const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission( V, N, material.ior, material.roughness, entering, float( 0.0 ), xi, rngState, float( 0.0 ), pixelCoord, resolution, frame, dimBase ) );
417
399
  result.direction.assign( mtResult.direction );
418
400
 
419
401
  } ).Else( () => {
@@ -430,7 +412,7 @@ export const handleTransmission = Fn( ( [
430
412
  // Transmission/refraction path
431
413
  If( material.roughness.greaterThan( 0.05 ).or( material.dispersion.greaterThan( 0.0 ) ), () => {
432
414
 
433
- const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission( V, N, material.ior, material.roughness, entering, material.dispersion, xi, rngState, pathWavelength ) );
415
+ const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission( V, N, material.ior, material.roughness, entering, material.dispersion, xi, rngState, pathWavelength, pixelCoord, resolution, frame, dimBase ) );
434
416
  result.pathWavelength.assign( mtResult.pathWavelength );
435
417
 
436
418
  If( mtResult.didReflect, () => {
@@ -486,6 +468,7 @@ export const handleTransmission = Fn( ( [
486
468
 
487
469
  export const handleMaterialTransparency = Fn( ( [
488
470
  ray, normal, material, rngState,
471
+ pixelCoord, resolution, frame, dimBase,
489
472
  transmissiveTraversals,
490
473
  currentMediumIOR, previousMediumIOR,
491
474
  pathWavelength,
@@ -512,8 +495,8 @@ export const handleMaterialTransparency = Fn( ( [
512
495
 
513
496
  } ).Else( () => {
514
497
 
515
- const alphaRand = RandomValue( rngState );
516
- const transmissionRand = RandomValue( rngState );
498
+ const alphaRand = getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 6 ) ), rngState, resolution, frame );
499
+ const transmissionRand = getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 7 ) ), rngState, resolution, frame );
517
500
  const transmissionSeed = pcgHash( { state: rngState } );
518
501
 
519
502
  const handled = tslBool( false ).toVar();
@@ -574,6 +557,7 @@ export const handleMaterialTransparency = Fn( ( [
574
557
 
575
558
  const transResult = TransmissionResult.wrap( handleTransmission(
576
559
  ray.direction, normal, material, entering, transmissionSeed,
560
+ pixelCoord, resolution, frame, dimBase,
577
561
  currentMediumIOR, previousMediumIOR, pathWavelength,
578
562
  ) );
579
563
 
@@ -595,12 +579,13 @@ export const handleMaterialTransparency = Fn( ( [
595
579
  If( handled.not().and( material.subsurface.greaterThan( 0.0 ) ), () => {
596
580
 
597
581
  const entering = dot( ray.direction, normal ).lessThan( 0.0 );
598
- const doEnter = entering.not().or( RandomValue( rngState ).lessThan( material.subsurface ) );
582
+ const doEnter = entering.not().or( getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 8 ) ), rngState, resolution, frame ).lessThan( material.subsurface ) );
599
583
 
600
584
  If( doEnter, () => {
601
585
 
602
586
  const ssResult = SubsurfaceEntryResult.wrap( handleSubsurfaceEntry(
603
587
  ray.direction, normal, material, entering, rngState,
588
+ pixelCoord, resolution, frame, dimBase,
604
589
  currentMediumIOR, previousMediumIOR,
605
590
  ) ).toVar();
606
591