rayzee 7.20.2 → 7.20.3
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/dist/rayzee.es.js +3977 -3125
- package/dist/rayzee.umd.js +87 -115
- package/package.json +1 -1
- package/src/EngineDefaults.js +1 -1
- package/src/TSL/Clearcoat.js +29 -40
- package/src/TSL/EmissiveSampling.js +8 -7
- package/src/TSL/LightBVHSampling.js +16 -12
- package/src/TSL/LightsCore.js +0 -2
- package/src/TSL/LightsIndirect.js +36 -355
- package/src/TSL/LightsSampling.js +28 -72
- package/src/TSL/MaterialEvaluation.js +44 -71
- package/src/TSL/MaterialProperties.js +209 -176
- package/src/TSL/MaterialTransmission.js +17 -32
- package/src/TSL/PathTracerCore.js +49 -64
- package/src/TSL/Random.js +202 -132
- package/src/TSL/ShadeKernel.js +50 -39
- package/src/TSL/Struct.js +6 -28
- package/src/TSL/Subsurface.js +13 -8
|
@@ -7,398 +7,79 @@
|
|
|
7
7
|
* strategy — it is handled via deterministic NEE in LightsSampling.js.
|
|
8
8
|
*
|
|
9
9
|
* Contains:
|
|
10
|
-
* - calculateTransmissionPDF — transmission PDF for MIS
|
|
11
|
-
* - calculateClearcoatPDF — clearcoat PDF for MIS
|
|
12
|
-
* - computeSamplingInfo — compute importance weights for material strategies
|
|
13
|
-
* - selectSamplingStrategy — CDF-based strategy selection (specular, diffuse, transmission, clearcoat)
|
|
14
10
|
* - calculateIndirectLighting — material multi-strategy MIS for bounce direction
|
|
15
11
|
*/
|
|
16
12
|
|
|
17
|
-
import {
|
|
18
|
-
Fn,
|
|
19
|
-
float,
|
|
20
|
-
vec2,
|
|
21
|
-
vec3,
|
|
22
|
-
int,
|
|
23
|
-
bool as tslBool,
|
|
24
|
-
max,
|
|
25
|
-
abs,
|
|
26
|
-
sqrt,
|
|
27
|
-
dot,
|
|
28
|
-
If,
|
|
29
|
-
select,
|
|
30
|
-
} from 'three/tsl';
|
|
13
|
+
import { Fn, float, vec3, int, max, dot, If, select } from 'three/tsl';
|
|
31
14
|
|
|
32
|
-
import {
|
|
33
|
-
|
|
34
|
-
} from './
|
|
35
|
-
import {
|
|
36
|
-
SamplingStrategyWeights,
|
|
37
|
-
} from './Struct.js';
|
|
38
|
-
import {
|
|
39
|
-
PI_INV,
|
|
40
|
-
EPSILON,
|
|
41
|
-
MIN_PDF,
|
|
42
|
-
} from './Common.js';
|
|
43
|
-
import { DistributionGGX, calculateVNDFPDF, specularVNDFPDFForDir } from './MaterialProperties.js';
|
|
44
|
-
import { evaluateMaterialResponse } from './MaterialEvaluation.js';
|
|
45
|
-
import { RandomValue } from './Random.js';
|
|
46
|
-
import { sampleMicrofacetTransmission, MicrofacetTransmissionResult } from './MaterialTransmission.js';
|
|
15
|
+
import { IndirectLightingResult } from './LightsCore.js';
|
|
16
|
+
import { MIN_PDF } from './Common.js';
|
|
17
|
+
import { getRandomSample2D } from './Random.js';
|
|
47
18
|
import { cosineWeightedSample } from './MaterialSampling.js';
|
|
48
19
|
|
|
49
|
-
// =============================================================================
|
|
50
|
-
// PDF Calculation Helpers
|
|
51
|
-
// =============================================================================
|
|
52
|
-
|
|
53
|
-
// Transmission PDF (Walter et al. 2007)
|
|
54
|
-
export const calculateTransmissionPDF = Fn( ( [ V, L, N, ior, roughness, entering ] ) => {
|
|
55
|
-
|
|
56
|
-
// eta is the relative IOR: eta_transmitted / eta_incident
|
|
57
|
-
// When entering: air(1.0) -> material(ior), so eta = ior
|
|
58
|
-
// When exiting: material(ior) -> air(1.0), so eta = 1.0/ior
|
|
59
|
-
const eta = select( entering, ior, float( 1.0 ).div( ior ) ).toVar();
|
|
60
|
-
|
|
61
|
-
// Transmission half-vector formula
|
|
62
|
-
const H_raw = V.add( L.mul( eta ) ).toVar();
|
|
63
|
-
const lenSq = dot( H_raw, H_raw ).toVar();
|
|
64
|
-
const H = select( lenSq.greaterThan( EPSILON ), H_raw.div( sqrt( lenSq ) ), N ).toVar();
|
|
65
|
-
|
|
66
|
-
// Ensure H points into the correct hemisphere
|
|
67
|
-
If( dot( H, N ).lessThan( 0.0 ), () => {
|
|
68
|
-
|
|
69
|
-
H.assign( H.negate() );
|
|
70
|
-
|
|
71
|
-
} );
|
|
72
|
-
|
|
73
|
-
const VoH = abs( dot( V, H ) );
|
|
74
|
-
const LoH = abs( dot( L, H ) ).toVar();
|
|
75
|
-
const NoH = abs( dot( N, H ) ).toVar();
|
|
76
|
-
|
|
77
|
-
// GGX distribution
|
|
78
|
-
const D = DistributionGGX( NoH, roughness );
|
|
79
|
-
|
|
80
|
-
// Jacobian for transmission
|
|
81
|
-
const denom_inner = VoH.add( LoH.mul( eta ) ).toVar();
|
|
82
|
-
const denom = denom_inner.mul( denom_inner );
|
|
83
|
-
const jacobian = LoH.mul( eta ).mul( eta ).div( max( denom, EPSILON ) );
|
|
84
|
-
|
|
85
|
-
return D.mul( NoH ).mul( jacobian );
|
|
86
|
-
|
|
87
|
-
} );
|
|
88
|
-
|
|
89
|
-
// Clearcoat PDF
|
|
90
|
-
export const calculateClearcoatPDF = Fn( ( [ V, L, N, clearcoatRoughness ] ) => {
|
|
91
|
-
|
|
92
|
-
const H_raw = V.add( L ).toVar();
|
|
93
|
-
const lenSq = dot( H_raw, H_raw ).toVar();
|
|
94
|
-
const H = select( lenSq.greaterThan( EPSILON ), H_raw.div( sqrt( lenSq ) ), N );
|
|
95
|
-
|
|
96
|
-
const NoH = max( dot( N, H ), 0.0 );
|
|
97
|
-
const NoV = max( dot( N, V ), 0.0 );
|
|
98
|
-
|
|
99
|
-
return calculateVNDFPDF( NoH, NoV, clearcoatRoughness );
|
|
100
|
-
|
|
101
|
-
} );
|
|
102
|
-
|
|
103
|
-
// =============================================================================
|
|
104
|
-
// Sampling Strategy Computation
|
|
105
|
-
// =============================================================================
|
|
106
|
-
|
|
107
|
-
// Compute normalized strategy weights based on importance info
|
|
108
|
-
export const computeSamplingInfo = Fn( ( [
|
|
109
|
-
samplingInfo,
|
|
110
|
-
] ) => {
|
|
111
|
-
|
|
112
|
-
// Material-only strategies (env handled via deterministic NEE in direct lighting)
|
|
113
|
-
const specularW = samplingInfo.specularImportance.toVar();
|
|
114
|
-
const useSpecular = specularW.greaterThan( 0.001 ).toVar();
|
|
115
|
-
|
|
116
|
-
const diffuseW = samplingInfo.diffuseImportance.toVar();
|
|
117
|
-
const useDiffuse = diffuseW.greaterThan( 0.001 ).toVar();
|
|
118
|
-
|
|
119
|
-
const transmissionW = samplingInfo.transmissionImportance.toVar();
|
|
120
|
-
const useTransmission = transmissionW.greaterThan( 0.001 ).toVar();
|
|
121
|
-
|
|
122
|
-
const clearcoatW = samplingInfo.clearcoatImportance.toVar();
|
|
123
|
-
const useClearcoat = clearcoatW.greaterThan( 0.001 ).toVar();
|
|
124
|
-
|
|
125
|
-
const totalW = specularW.add( diffuseW ).add( transmissionW ).add( clearcoatW ).toVar();
|
|
126
|
-
|
|
127
|
-
If( totalW.lessThan( 0.001 ), () => {
|
|
128
|
-
|
|
129
|
-
specularW.assign( 0.0 );
|
|
130
|
-
diffuseW.assign( 1.0 );
|
|
131
|
-
transmissionW.assign( 0.0 );
|
|
132
|
-
clearcoatW.assign( 0.0 );
|
|
133
|
-
totalW.assign( 1.0 );
|
|
134
|
-
useSpecular.assign( tslBool( false ) );
|
|
135
|
-
useDiffuse.assign( tslBool( true ) );
|
|
136
|
-
useTransmission.assign( tslBool( false ) );
|
|
137
|
-
useClearcoat.assign( tslBool( false ) );
|
|
138
|
-
|
|
139
|
-
} ).Else( () => {
|
|
140
|
-
|
|
141
|
-
const invTotal = float( 1.0 ).div( totalW ).toVar();
|
|
142
|
-
specularW.mulAssign( invTotal );
|
|
143
|
-
diffuseW.mulAssign( invTotal );
|
|
144
|
-
transmissionW.mulAssign( invTotal );
|
|
145
|
-
clearcoatW.mulAssign( invTotal );
|
|
146
|
-
totalW.assign( 1.0 );
|
|
147
|
-
|
|
148
|
-
} );
|
|
149
|
-
|
|
150
|
-
return SamplingStrategyWeights( {
|
|
151
|
-
envWeight: float( 0.0 ),
|
|
152
|
-
specularWeight: specularW,
|
|
153
|
-
diffuseWeight: diffuseW,
|
|
154
|
-
transmissionWeight: transmissionW,
|
|
155
|
-
clearcoatWeight: clearcoatW,
|
|
156
|
-
totalWeight: totalW,
|
|
157
|
-
useEnv: tslBool( false ),
|
|
158
|
-
useSpecular,
|
|
159
|
-
useDiffuse,
|
|
160
|
-
useTransmission,
|
|
161
|
-
useClearcoat,
|
|
162
|
-
} );
|
|
163
|
-
|
|
164
|
-
} );
|
|
165
|
-
|
|
166
|
-
// =============================================================================
|
|
167
|
-
// Strategy Selection via Cumulative Distribution
|
|
168
|
-
// =============================================================================
|
|
169
|
-
|
|
170
|
-
// Strategy IDs: 1=specular, 2=diffuse, 3=transmission, 4=clearcoat
|
|
171
|
-
// (env removed — handled via deterministic NEE in direct lighting)
|
|
172
|
-
export const selectSamplingStrategy = Fn( ( [ weights, randomValue ] ) => {
|
|
173
|
-
|
|
174
|
-
const selectedStrategy = int( 2 ).toVar(); // Default: diffuse
|
|
175
|
-
|
|
176
|
-
const cumulative = float( 0.0 ).toVar();
|
|
177
|
-
const found = tslBool( false ).toVar();
|
|
178
|
-
|
|
179
|
-
If( weights.useSpecular.and( found.not() ), () => {
|
|
180
|
-
|
|
181
|
-
cumulative.addAssign( weights.specularWeight );
|
|
182
|
-
|
|
183
|
-
If( randomValue.lessThan( cumulative ), () => {
|
|
184
|
-
|
|
185
|
-
selectedStrategy.assign( 1 );
|
|
186
|
-
found.assign( tslBool( true ) );
|
|
187
|
-
|
|
188
|
-
} );
|
|
189
|
-
|
|
190
|
-
} );
|
|
191
|
-
|
|
192
|
-
If( weights.useDiffuse.and( found.not() ), () => {
|
|
193
20
|
|
|
194
|
-
cumulative.addAssign( weights.diffuseWeight );
|
|
195
21
|
|
|
196
|
-
If( randomValue.lessThan( cumulative ), () => {
|
|
197
22
|
|
|
198
|
-
selectedStrategy.assign( 2 );
|
|
199
|
-
found.assign( tslBool( true ) );
|
|
200
23
|
|
|
201
|
-
} );
|
|
202
|
-
|
|
203
|
-
} );
|
|
204
|
-
|
|
205
|
-
If( weights.useTransmission.and( found.not() ), () => {
|
|
206
|
-
|
|
207
|
-
cumulative.addAssign( weights.transmissionWeight );
|
|
208
|
-
|
|
209
|
-
If( randomValue.lessThan( cumulative ), () => {
|
|
210
|
-
|
|
211
|
-
selectedStrategy.assign( 3 );
|
|
212
|
-
found.assign( tslBool( true ) );
|
|
213
|
-
|
|
214
|
-
} );
|
|
215
|
-
|
|
216
|
-
} );
|
|
217
|
-
|
|
218
|
-
If( weights.useClearcoat.and( found.not() ), () => {
|
|
219
|
-
|
|
220
|
-
selectedStrategy.assign( 4 );
|
|
221
|
-
|
|
222
|
-
} );
|
|
223
|
-
|
|
224
|
-
return selectedStrategy;
|
|
225
|
-
|
|
226
|
-
} );
|
|
227
|
-
|
|
228
|
-
// =============================================================================
|
|
229
|
-
// Cosine Weighted PDF helper
|
|
230
|
-
// =============================================================================
|
|
231
|
-
|
|
232
|
-
const cosineWeightedPDF = Fn( ( [ NoL ] ) => {
|
|
233
|
-
|
|
234
|
-
return max( NoL, 0.0 ).mul( PI_INV );
|
|
235
|
-
|
|
236
|
-
} );
|
|
237
24
|
|
|
238
25
|
// =============================================================================
|
|
239
26
|
// Indirect Lighting Calculation
|
|
240
27
|
// =============================================================================
|
|
241
28
|
|
|
242
29
|
export const calculateIndirectLighting = Fn( ( [
|
|
243
|
-
|
|
30
|
+
N, material,
|
|
244
31
|
// brdfSample fields (DirectionSample)
|
|
245
32
|
brdfSampleDirection, brdfSamplePdf, brdfSampleValue,
|
|
33
|
+
// DirectionSample carries these so refraction throughput does not need a second sampler run.
|
|
34
|
+
isTransmission, brdfSampleColorWeight,
|
|
246
35
|
rngState,
|
|
247
|
-
|
|
36
|
+
pixelCoord, resolution, frame, dimBase,
|
|
248
37
|
] ) => {
|
|
249
38
|
|
|
250
39
|
// Initialize result
|
|
251
40
|
const r_direction = vec3( 0.0 ).toVar();
|
|
252
41
|
const r_throughput = vec3( 0.0 ).toVar();
|
|
253
|
-
const r_misWeight = float( 0.0 ).toVar();
|
|
254
|
-
const r_pdf = float( 0.0 ).toVar();
|
|
255
42
|
const r_combinedPdf = float( 0.0 ).toVar();
|
|
256
43
|
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
.and( samplingInfo.transmissionImportance.greaterThanEqual( 0.0 ) )
|
|
261
|
-
.and( samplingInfo.clearcoatImportance.greaterThanEqual( 0.0 ) );
|
|
44
|
+
// A non-finite pdf would poison the accumulation buffer permanently, so fall back to a
|
|
45
|
+
// cosine bounce rather than dividing by it.
|
|
46
|
+
If( brdfSamplePdf.greaterThan( 0.0 ).not(), () => {
|
|
262
47
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
// Fallback to diffuse sampling
|
|
266
|
-
const r1_fb = RandomValue( rngState ).toVar();
|
|
267
|
-
const r2_fb = RandomValue( rngState ).toVar();
|
|
268
|
-
const sampleRand = vec2( r1_fb, r2_fb );
|
|
48
|
+
const sampleRand = getRandomSample2D( pixelCoord, int( 0 ), dimBase.add( int( 8 ) ), rngState, resolution, frame );
|
|
269
49
|
r_direction.assign( cosineWeightedSample( N, sampleRand ) );
|
|
270
50
|
r_throughput.assign( material.color.xyz );
|
|
271
|
-
|
|
272
|
-
r_pdf.assign( 1.0 );
|
|
51
|
+
r_combinedPdf.assign( 1.0 );
|
|
273
52
|
|
|
274
53
|
} ).Else( () => {
|
|
275
54
|
|
|
276
|
-
//
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
//
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
55
|
+
// generateSampledDirection IS the sampler — this used to re-select a strategy on top of
|
|
56
|
+
// it, and its strategies 1 and 4 both just returned brdfSampleDirection. So a sample that
|
|
57
|
+
// the inner selection had drawn from the diffuse or sheen lobe was reported to MIS as a
|
|
58
|
+
// specular-VNDF draw, making combinedPdf something no strategy actually sampled from.
|
|
59
|
+
// Since prevBouncePdf is that value, and the NEE sites evaluate a mixture, the two MIS
|
|
60
|
+
// strategies' weights stopped summing to 1.
|
|
61
|
+
//
|
|
62
|
+
// brdfSamplePdf is now the true mixture density (calculateBSDFSamplingPDF), so the
|
|
63
|
+
// estimator is just f·NoL/pdf and the MIS pairs share one function by construction.
|
|
64
|
+
const NoL = max( dot( N, brdfSampleDirection ), 0.0 ).toVar();
|
|
65
|
+
const pdf = max( brdfSamplePdf, MIN_PDF ).toVar();
|
|
66
|
+
|
|
67
|
+
r_direction.assign( brdfSampleDirection );
|
|
68
|
+
r_combinedPdf.assign( pdf );
|
|
69
|
+
|
|
70
|
+
// The reflection BRDF eval is invalid below the surface, so refraction carries the
|
|
71
|
+
// sampler's own tint (material.color × colorWeight), mirroring handleTransmission.
|
|
72
|
+
r_throughput.assign( select(
|
|
73
|
+
isTransmission,
|
|
74
|
+
material.color.xyz.mul( brdfSampleColorWeight ),
|
|
75
|
+
brdfSampleValue.mul( NoL ).div( pdf ),
|
|
76
|
+
) );
|
|
298
77
|
|
|
299
|
-
|
|
300
|
-
If( selectedStrategy.equal( int( 1 ) ), () => {
|
|
301
|
-
|
|
302
|
-
sampleDir.assign( brdfSampleDirection );
|
|
303
|
-
samplePdf.assign( brdfSamplePdf );
|
|
304
|
-
sampleBrdfValue.assign( brdfSampleValue );
|
|
305
|
-
|
|
306
|
-
} ).ElseIf( selectedStrategy.equal( int( 2 ) ), () => {
|
|
307
|
-
|
|
308
|
-
// Strategy 2: Diffuse
|
|
309
|
-
sampleDir.assign( cosineWeightedSample( N, sampleRand ) );
|
|
310
|
-
samplePdf.assign( cosineWeightedPDF( max( dot( N, sampleDir ), 0.0 ) ) );
|
|
311
|
-
sampleBrdfValue.assign( evaluateMaterialResponse( V, sampleDir, N, material ) );
|
|
312
|
-
|
|
313
|
-
} ).ElseIf( selectedStrategy.equal( int( 3 ) ), () => {
|
|
314
|
-
|
|
315
|
-
// Strategy 3: Transmission
|
|
316
|
-
const entering = dot( V, N ).greaterThan( 0.0 );
|
|
317
|
-
const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission(
|
|
318
|
-
V, N, material.ior, material.roughness, entering, material.dispersion, sampleRand, rngState, float( 0.0 )
|
|
319
|
-
).toVar() );
|
|
320
|
-
sampleDir.assign( mtResult.direction );
|
|
321
|
-
samplePdf.assign( mtResult.pdf );
|
|
322
|
-
// evaluateMaterialResponse is reflection-only and returns a non-physical value for
|
|
323
|
-
// the below-surface refraction direction (its dots are floored to 0.001). Carry the
|
|
324
|
-
// sampler's spectral tint and apply an energy-consistent transmission throughput
|
|
325
|
-
// below (mirrors handleTransmission's material.color × colorWeight convention).
|
|
326
|
-
transColorWeight.assign( mtResult.colorWeight );
|
|
327
|
-
|
|
328
|
-
} ).Else( () => {
|
|
329
|
-
|
|
330
|
-
// Strategy 4: Clearcoat (fallback)
|
|
331
|
-
sampleDir.assign( brdfSampleDirection );
|
|
332
|
-
samplePdf.assign( brdfSamplePdf );
|
|
333
|
-
sampleBrdfValue.assign( brdfSampleValue );
|
|
334
|
-
|
|
335
|
-
} );
|
|
336
|
-
|
|
337
|
-
const NoL = max( dot( N, sampleDir ), 0.0 ).toVar();
|
|
338
|
-
|
|
339
|
-
// Calculate combined PDF for MIS (material strategies only)
|
|
340
|
-
const combinedPdf = float( 0.0 ).toVar();
|
|
341
|
-
|
|
342
|
-
If( weights.useSpecular, () => {
|
|
343
|
-
|
|
344
|
-
// Evaluate specular PDF at sampleDir (not brdfSampleDirection which may differ);
|
|
345
|
-
// auto-selects iso/aniso to stay consistent with the BRDF sampler.
|
|
346
|
-
const specPdfAtSampleDir = specularVNDFPDFForDir( material, N, V, sampleDir );
|
|
347
|
-
combinedPdf.addAssign( weights.specularWeight.mul( specPdfAtSampleDir ) );
|
|
348
|
-
|
|
349
|
-
} );
|
|
350
|
-
|
|
351
|
-
If( weights.useDiffuse, () => {
|
|
352
|
-
|
|
353
|
-
combinedPdf.addAssign( weights.diffuseWeight.mul( cosineWeightedPDF( NoL ) ) );
|
|
354
|
-
|
|
355
|
-
} );
|
|
356
|
-
|
|
357
|
-
If( weights.useTransmission.and( material.transmission.greaterThan( 0.0 ) ), () => {
|
|
358
|
-
|
|
359
|
-
// Calculate transmission PDF for this direction
|
|
360
|
-
const entering = dot( V, N ).greaterThan( 0.0 );
|
|
361
|
-
combinedPdf.addAssign( weights.transmissionWeight.mul(
|
|
362
|
-
calculateTransmissionPDF( V, sampleDir, N, material.ior, material.roughness, entering )
|
|
363
|
-
) );
|
|
364
|
-
|
|
365
|
-
} );
|
|
366
|
-
|
|
367
|
-
If( weights.useClearcoat.and( material.clearcoat.greaterThan( 0.0 ) ), () => {
|
|
368
|
-
|
|
369
|
-
// Calculate clearcoat PDF for this direction
|
|
370
|
-
combinedPdf.addAssign( weights.clearcoatWeight.mul(
|
|
371
|
-
calculateClearcoatPDF( V, sampleDir, N, material.clearcoatRoughness )
|
|
372
|
-
) );
|
|
373
|
-
|
|
374
|
-
} );
|
|
375
|
-
|
|
376
|
-
// Ensure valid PDFs
|
|
377
|
-
samplePdf.assign( max( samplePdf, MIN_PDF ) );
|
|
378
|
-
combinedPdf.assign( max( combinedPdf, MIN_PDF ) );
|
|
379
|
-
|
|
380
|
-
// MIS weight calculation
|
|
381
|
-
const misWeight = samplePdf.div( combinedPdf ).toVar();
|
|
382
|
-
|
|
383
|
-
// Reflection lobes: f·NoL·mis/pdf. Transmission lobe: the sampler's energy-consistent
|
|
384
|
-
// weight (material tint × dispersion colorWeight × mis) — the reflection BRDF value is
|
|
385
|
-
// invalid below the surface, so it is not used for transmission.
|
|
386
|
-
const reflThroughput = sampleBrdfValue.mul( NoL ).mul( misWeight ).div( samplePdf );
|
|
387
|
-
const transThroughput = material.color.xyz.mul( transColorWeight ).mul( misWeight );
|
|
388
|
-
|
|
389
|
-
r_direction.assign( sampleDir );
|
|
390
|
-
r_throughput.assign( select( selectedStrategy.equal( int( 3 ) ), transThroughput, reflThroughput ) );
|
|
391
|
-
r_misWeight.assign( misWeight );
|
|
392
|
-
r_pdf.assign( samplePdf );
|
|
393
|
-
r_combinedPdf.assign( combinedPdf );
|
|
394
|
-
|
|
395
|
-
} ); // End validInput check
|
|
78
|
+
} );
|
|
396
79
|
|
|
397
80
|
return IndirectLightingResult( {
|
|
398
81
|
direction: r_direction,
|
|
399
82
|
throughput: r_throughput,
|
|
400
|
-
misWeight: r_misWeight,
|
|
401
|
-
pdf: r_pdf,
|
|
402
83
|
combinedPdf: r_combinedPdf,
|
|
403
84
|
} );
|
|
404
85
|
|
|
@@ -72,8 +72,8 @@ import {
|
|
|
72
72
|
|
|
73
73
|
import { traverseBVHShadow } from './BVHTraversal.js';
|
|
74
74
|
import { evaluateMaterialResponseFromDots } from './MaterialEvaluation.js';
|
|
75
|
-
import {
|
|
76
|
-
import {
|
|
75
|
+
import { calculateBSDFSamplingPDFFromMaterial } from './MaterialProperties.js';
|
|
76
|
+
import { getRandomSample1D, getRandomSample2D, SAMPLER_DIM_AUX_BASE } from './Random.js';
|
|
77
77
|
import {
|
|
78
78
|
selectOptimalMISStrategy,
|
|
79
79
|
PI,
|
|
@@ -340,6 +340,7 @@ export const samplePointLightWithAttenuation = Fn( ( [ light, rayOrigin, lightSe
|
|
|
340
340
|
// unbiased; PDF = winnerImportance / totalWeight, identical to the CDF form.
|
|
341
341
|
export const sampleLightWithImportance = Fn( ( [
|
|
342
342
|
rayOrigin, normal, material, randomSeed, bounceIndex, rngState,
|
|
343
|
+
pixelCoord, resolution, frame, dimBase,
|
|
343
344
|
// Light buffers + counts
|
|
344
345
|
directionalLightsBuffer, numDirectionalLights,
|
|
345
346
|
areaLightsBuffer, numAreaLights,
|
|
@@ -380,7 +381,9 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
380
381
|
const importance = calculateDirectionalLightImportance( light, normal, material, bounceIndex ).toVar();
|
|
381
382
|
totalWeight.addAssign( importance );
|
|
382
383
|
If( importance.greaterThan( 0.0 ).and(
|
|
383
|
-
|
|
384
|
+
getRandomSample1D( pixelCoord, int( 0 ),
|
|
385
|
+
int( SAMPLER_DIM_AUX_BASE + 64 + 0 * 16 ).add( i ), rngState, resolution, frame
|
|
386
|
+
).mul( totalWeight ).lessThan( importance )
|
|
384
387
|
), () => {
|
|
385
388
|
|
|
386
389
|
selectedType.assign( 0 );
|
|
@@ -405,7 +408,9 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
405
408
|
const importance = select( light.intensity.greaterThan( 0.0 ), estimateLightImportance( light, rayOrigin, normal, material ), float( 0.0 ) ).toVar();
|
|
406
409
|
totalWeight.addAssign( importance );
|
|
407
410
|
If( importance.greaterThan( 0.0 ).and(
|
|
408
|
-
|
|
411
|
+
getRandomSample1D( pixelCoord, int( 0 ),
|
|
412
|
+
int( SAMPLER_DIM_AUX_BASE + 64 + 1 * 16 ).add( i ), rngState, resolution, frame
|
|
413
|
+
).mul( totalWeight ).lessThan( importance )
|
|
409
414
|
), () => {
|
|
410
415
|
|
|
411
416
|
selectedType.assign( 1 );
|
|
@@ -430,7 +435,9 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
430
435
|
const importance = calculatePointLightImportance( light, rayOrigin, normal, material ).toVar();
|
|
431
436
|
totalWeight.addAssign( importance );
|
|
432
437
|
If( importance.greaterThan( 0.0 ).and(
|
|
433
|
-
|
|
438
|
+
getRandomSample1D( pixelCoord, int( 0 ),
|
|
439
|
+
int( SAMPLER_DIM_AUX_BASE + 64 + 2 * 16 ).add( i ), rngState, resolution, frame
|
|
440
|
+
).mul( totalWeight ).lessThan( importance )
|
|
434
441
|
), () => {
|
|
435
442
|
|
|
436
443
|
selectedType.assign( 2 );
|
|
@@ -455,7 +462,9 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
455
462
|
const importance = calculateSpotLightImportance( light, rayOrigin, normal, material ).toVar();
|
|
456
463
|
totalWeight.addAssign( importance );
|
|
457
464
|
If( importance.greaterThan( 0.0 ).and(
|
|
458
|
-
|
|
465
|
+
getRandomSample1D( pixelCoord, int( 0 ),
|
|
466
|
+
int( SAMPLER_DIM_AUX_BASE + 64 + 3 * 16 ).add( i ), rngState, resolution, frame
|
|
467
|
+
).mul( totalWeight ).lessThan( importance )
|
|
459
468
|
), () => {
|
|
460
469
|
|
|
461
470
|
selectedType.assign( 3 );
|
|
@@ -518,7 +527,7 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
518
527
|
|
|
519
528
|
If( light.intensity.greaterThan( 0.0 ), () => {
|
|
520
529
|
|
|
521
|
-
const uv = vec2( randomSeed.y,
|
|
530
|
+
const uv = vec2( randomSeed.y, getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 15 ) ), rngState, resolution, frame ) ).toVar();
|
|
522
531
|
const areaSample = LightSample.wrap( sampleRectAreaLight( light, rayOrigin, uv, lightSelectionPdf ) );
|
|
523
532
|
r_valid.assign( areaSample.valid );
|
|
524
533
|
r_direction.assign( areaSample.direction );
|
|
@@ -607,7 +616,7 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
607
616
|
const cosHalfAngle = cos( light.angle.mul( 0.5 ) ).toVar();
|
|
608
617
|
const cosTheta = mix( cosHalfAngle, float( 1.0 ), randomSeed.y ).toVar();
|
|
609
618
|
const sinTheta = sqrt( max( float( 0.0 ), float( 1.0 ).sub( cosTheta.mul( cosTheta ) ) ) ).toVar();
|
|
610
|
-
const phi = float( TWO_PI ).mul(
|
|
619
|
+
const phi = float( TWO_PI ).mul( getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 14 ) ), rngState, resolution, frame ) ).toVar();
|
|
611
620
|
|
|
612
621
|
const w = normalize( light.direction ).toVar();
|
|
613
622
|
const u = normalize( cross(
|
|
@@ -639,7 +648,7 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
639
648
|
If( selectedType.equal( int( 1 ) ).and( selectedIdx.greaterThanEqual( int( 0 ) ) ), () => {
|
|
640
649
|
|
|
641
650
|
const light = AreaLight.wrap( getAreaLight( areaLightsBuffer, selectedIdx ) );
|
|
642
|
-
const uv = vec2( randomSeed.y,
|
|
651
|
+
const uv = vec2( randomSeed.y, getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 15 ) ), rngState, resolution, frame ) ).toVar();
|
|
643
652
|
const areaSample = LightSample.wrap( sampleRectAreaLight( light, rayOrigin, uv, pdf ) );
|
|
644
653
|
r_valid.assign( areaSample.valid );
|
|
645
654
|
r_direction.assign( areaSample.direction );
|
|
@@ -700,66 +709,14 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
700
709
|
// PDF computation given precomputed dot products. Use this when the caller
|
|
701
710
|
// already has dots from a paired evaluateMaterialResponseFromDots invocation
|
|
702
711
|
// to avoid recomputing H + dots.
|
|
712
|
+
// BSDF-sampling density for MIS — must be the exact function the sampler draws from, or the two
|
|
713
|
+
// strategies' weights stop summing to 1.
|
|
703
714
|
export const calculateMaterialPDFFromDots = Fn( ( [ material, dots ] ) => {
|
|
704
715
|
|
|
705
|
-
|
|
706
|
-
const NoL = dots.NoL.toVar();
|
|
707
|
-
const NoH = dots.NoH;
|
|
708
|
-
|
|
709
|
-
// Calculate lobe weights
|
|
710
|
-
const diffuseWeight = float( 1.0 ).sub( material.metalness ).mul(
|
|
711
|
-
float( 1.0 ).sub( material.transmission )
|
|
712
|
-
).toVar();
|
|
713
|
-
|
|
714
|
-
const specularWeight = float( 1.0 ).sub(
|
|
715
|
-
diffuseWeight.mul( float( 1.0 ).sub( material.metalness ) )
|
|
716
|
-
).toVar();
|
|
717
|
-
|
|
718
|
-
const totalWeight = diffuseWeight.add( specularWeight ).toVar();
|
|
719
|
-
|
|
720
|
-
const pdf = float( 0.0 ).toVar();
|
|
721
|
-
|
|
722
|
-
If( totalWeight.greaterThan( 0.0 ), () => {
|
|
723
|
-
|
|
724
|
-
// Guard division
|
|
725
|
-
const invTotalWeight = float( 1.0 ).div( max( totalWeight, 1e-10 ) ).toVar();
|
|
726
|
-
diffuseWeight.mulAssign( invTotalWeight );
|
|
727
|
-
specularWeight.mulAssign( invTotalWeight );
|
|
728
|
-
|
|
729
|
-
// Diffuse PDF (cosine-weighted hemisphere)
|
|
730
|
-
If( diffuseWeight.greaterThan( 0.0 ).and( NoL.greaterThan( 0.0 ) ), () => {
|
|
731
|
-
|
|
732
|
-
pdf.addAssign( diffuseWeight.mul( NoL ).mul( PI_INV ) );
|
|
733
|
-
|
|
734
|
-
} );
|
|
735
|
-
|
|
736
|
-
// Specular PDF (VNDF sampling used in path tracer)
|
|
737
|
-
If( specularWeight.greaterThan( 0.0 ).and( NoL.greaterThan( 0.0 ) ), () => {
|
|
738
|
-
|
|
739
|
-
const roughness = max( material.roughness, 0.02 );
|
|
740
|
-
const specPdf = float( 0.0 ).toVar();
|
|
741
|
-
If( material.anisotropy.greaterThan( 0.0 ), () => {
|
|
742
|
-
|
|
743
|
-
const a = computeAnisoAlphas( material.roughness, material.anisotropy );
|
|
744
|
-
specPdf.assign( calculateVNDFPDFAniso( a.x, a.y, dots.NoH, dots.ToH, dots.BoH, dots.NoV, dots.ToV, dots.BoV ) );
|
|
745
|
-
|
|
746
|
-
} ).Else( () => {
|
|
747
|
-
|
|
748
|
-
specPdf.assign( calculateVNDFPDF( NoH, NoV, roughness ) );
|
|
749
|
-
|
|
750
|
-
} );
|
|
751
|
-
pdf.addAssign( specularWeight.mul( specPdf ) );
|
|
752
|
-
|
|
753
|
-
} );
|
|
754
|
-
|
|
755
|
-
} );
|
|
756
|
-
|
|
757
|
-
return max( pdf, 1e-8 );
|
|
716
|
+
return max( calculateBSDFSamplingPDFFromMaterial( material, dots ), 1e-8 );
|
|
758
717
|
|
|
759
718
|
} );
|
|
760
719
|
|
|
761
|
-
// Wrapper that computes dots internally. Use this when the caller doesn't
|
|
762
|
-
// already have dots; otherwise prefer calculateMaterialPDFFromDots.
|
|
763
720
|
export const calculateMaterialPDF = Fn( ( [ viewDir, lightDir, normal, material ] ) => {
|
|
764
721
|
|
|
765
722
|
const dots = DotProducts.wrap( computeDotProductsAniso( normal, viewDir, lightDir, material ) );
|
|
@@ -861,6 +818,9 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
861
818
|
brdfSampleDirection, brdfSamplePdf, brdfSampleValue,
|
|
862
819
|
// Tracing context
|
|
863
820
|
bounceIndex, rngState,
|
|
821
|
+
// Sampler context — NEE's continuous variates come off the sampler keyed on
|
|
822
|
+
// (pixel, dimension, frame). dimBase is this bounce's block; see Random.js budget.
|
|
823
|
+
pixelCoord, resolution, frame, dimBase,
|
|
864
824
|
// Light data
|
|
865
825
|
directionalLightsBuffer, numDirectionalLights,
|
|
866
826
|
areaLightsBuffer, numAreaLights,
|
|
@@ -937,11 +897,8 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
937
897
|
|
|
938
898
|
} );
|
|
939
899
|
|
|
940
|
-
const stratRand1 = RandomValue( rngState ).toVar();
|
|
941
|
-
const stratRand2 = RandomValue( rngState ).toVar();
|
|
942
|
-
|
|
943
900
|
// Determine sampling technique: stochastic {lights, BRDF}
|
|
944
|
-
const rand =
|
|
901
|
+
const rand = getRandomSample1D( pixelCoord, int( 0 ), dimBase, rngState, resolution, frame ).toVar();
|
|
945
902
|
const sampleLights = tslBool( false ).toVar();
|
|
946
903
|
const sampleBRDF = tslBool( false ).toVar();
|
|
947
904
|
|
|
@@ -973,9 +930,10 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
973
930
|
If( sampleLights, () => {
|
|
974
931
|
|
|
975
932
|
// Importance-weighted light sampling
|
|
976
|
-
const lightRandom =
|
|
933
|
+
const lightRandom = getRandomSample2D( pixelCoord, int( 0 ), dimBase.add( int( 2 ) ), rngState, resolution, frame ).toVar();
|
|
977
934
|
const lightSample = LightSample.wrap( sampleLightWithImportance(
|
|
978
935
|
rayOrigin, hitNormal, material, lightRandom, bounceIndex, rngState,
|
|
936
|
+
pixelCoord, resolution, frame, dimBase,
|
|
979
937
|
directionalLightsBuffer, numDirectionalLights,
|
|
980
938
|
areaLightsBuffer, numAreaLights,
|
|
981
939
|
pointLightsBuffer, numPointLights,
|
|
@@ -1172,9 +1130,7 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
1172
1130
|
|
|
1173
1131
|
If( enableEnvironmentLight, () => {
|
|
1174
1132
|
|
|
1175
|
-
const
|
|
1176
|
-
const env_r2 = RandomValue( rngState ).toVar();
|
|
1177
|
-
const envRandom = vec2( env_r1, env_r2 ).toVar();
|
|
1133
|
+
const envRandom = getRandomSample2D( pixelCoord, int( 0 ), dimBase.add( int( 1 ) ), rngState, resolution, frame ).toVar();
|
|
1178
1134
|
const envColor = vec3( 0.0 ).toVar();
|
|
1179
1135
|
|
|
1180
1136
|
// Sample direction + PDF + color from importance-sampled environment
|