rayzee 6.0.0 → 6.1.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.
- package/README.md +5 -0
- package/dist/assets/TexturesWorker-DBqGmVdR.js.map +1 -1
- package/dist/rayzee.es.js +2396 -2072
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +49 -53
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/EngineDefaults.js +3 -0
- package/src/PathTracerApp.js +18 -8
- package/src/Pipeline/RenderStage.js +3 -0
- package/src/Processor/IESParser.js +340 -0
- package/src/Processor/LightSerializer.js +32 -4
- package/src/Processor/SceneProcessor.js +0 -1
- package/src/Processor/ShaderBuilder.js +40 -1
- package/src/Processor/Workers/TexturesWorker.js +1 -1
- package/src/RenderSettings.js +3 -0
- package/src/Stages/NormalDepth.js +3 -19
- package/src/Stages/PathTracer.js +15 -9
- package/src/TSL/BVHTraversal.js +5 -18
- package/src/TSL/Clearcoat.js +1 -5
- package/src/TSL/Common.js +2 -2
- package/src/TSL/Debugger.js +0 -14
- package/src/TSL/EmissiveSampling.js +20 -22
- package/src/TSL/Environment.js +60 -14
- package/src/TSL/Fresnel.js +13 -4
- package/src/TSL/LightsCore.js +238 -5
- package/src/TSL/LightsDirect.js +16 -5
- package/src/TSL/LightsIndirect.js +6 -38
- package/src/TSL/LightsSampling.js +119 -185
- package/src/TSL/MaterialEvaluation.js +25 -14
- package/src/TSL/MaterialProperties.js +14 -34
- package/src/TSL/MaterialTransmission.js +100 -222
- package/src/TSL/PathTracer.js +5 -4
- package/src/TSL/PathTracerCore.js +152 -140
- package/src/TSL/Struct.js +7 -1
- package/src/TSL/TextureSampling.js +2 -2
- package/src/index.js +2 -0
- package/src/managers/AnimationManager.js +3 -6
- package/src/managers/DenoisingManager.js +1 -1
- package/src/managers/GoboManager.js +277 -0
- package/src/managers/IESManager.js +268 -0
- package/src/managers/LightManager.js +33 -1
- package/src/managers/TransformManager.js +3 -3
- package/src/managers/UniformManager.js +5 -5
package/src/TSL/LightsCore.js
CHANGED
|
@@ -3,14 +3,25 @@
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
Fn, wgslFn,
|
|
6
|
+
vec2,
|
|
6
7
|
vec3,
|
|
7
8
|
float,
|
|
9
|
+
int,
|
|
8
10
|
If,
|
|
9
11
|
dot,
|
|
10
12
|
normalize,
|
|
11
13
|
cross,
|
|
12
14
|
length,
|
|
13
15
|
abs,
|
|
16
|
+
select,
|
|
17
|
+
clamp,
|
|
18
|
+
max,
|
|
19
|
+
min,
|
|
20
|
+
mix,
|
|
21
|
+
tan,
|
|
22
|
+
acos,
|
|
23
|
+
atan,
|
|
24
|
+
texture,
|
|
14
25
|
} from 'three/tsl';
|
|
15
26
|
|
|
16
27
|
import { struct } from './patches.js';
|
|
@@ -24,6 +35,9 @@ export const DirectionalLight = struct( {
|
|
|
24
35
|
color: 'vec3',
|
|
25
36
|
intensity: 'float',
|
|
26
37
|
angle: 'float', // Angular diameter in radians
|
|
38
|
+
goboIndex: 'int', // -1 = no gobo, otherwise index into goboMaps array
|
|
39
|
+
goboIntensity: 'float', // signed: negative = inverted, |value| = strength
|
|
40
|
+
goboScale: 'float', // world units per gobo tile (used by directional projection)
|
|
27
41
|
} );
|
|
28
42
|
|
|
29
43
|
export const AreaLight = struct( {
|
|
@@ -53,6 +67,10 @@ export const SpotLight = struct( {
|
|
|
53
67
|
penumbra: 'float', // penumbra factor [0,1]
|
|
54
68
|
distance: 'float', // cutoff distance (0 = infinite)
|
|
55
69
|
decay: 'float', // decay exponent (2 = physically correct)
|
|
70
|
+
goboIndex: 'int', // -1 = no gobo, otherwise index into goboMaps array
|
|
71
|
+
goboIntensity: 'float', // mask strength multiplier (0 = no mask, 1 = full mask)
|
|
72
|
+
iesIndex: 'int', // -1 = no IES, otherwise index into iesProfiles array
|
|
73
|
+
iesIntensity: 'float', // blend [0,1] between flat (0) and full IES distribution (1)
|
|
56
74
|
} );
|
|
57
75
|
|
|
58
76
|
export const LightSample = struct( {
|
|
@@ -84,7 +102,7 @@ export const LIGHT_TYPE_SPOT = 3;
|
|
|
84
102
|
|
|
85
103
|
export const getDirectionalLight = Fn( ( [ directionalLightsBuffer, index ] ) => {
|
|
86
104
|
|
|
87
|
-
const baseIndex = index.mul(
|
|
105
|
+
const baseIndex = index.mul( 12 );
|
|
88
106
|
return DirectionalLight( {
|
|
89
107
|
direction: normalize( vec3(
|
|
90
108
|
directionalLightsBuffer.element( baseIndex ),
|
|
@@ -98,6 +116,9 @@ export const getDirectionalLight = Fn( ( [ directionalLightsBuffer, index ] ) =>
|
|
|
98
116
|
),
|
|
99
117
|
intensity: directionalLightsBuffer.element( baseIndex.add( 6 ) ),
|
|
100
118
|
angle: directionalLightsBuffer.element( baseIndex.add( 7 ) ),
|
|
119
|
+
goboIndex: int( directionalLightsBuffer.element( baseIndex.add( 8 ) ) ),
|
|
120
|
+
goboIntensity: directionalLightsBuffer.element( baseIndex.add( 9 ) ),
|
|
121
|
+
goboScale: directionalLightsBuffer.element( baseIndex.add( 10 ) ),
|
|
101
122
|
} );
|
|
102
123
|
|
|
103
124
|
} );
|
|
@@ -161,7 +182,7 @@ export const getPointLight = Fn( ( [ pointLightsBuffer, index ] ) => {
|
|
|
161
182
|
|
|
162
183
|
export const getSpotLight = Fn( ( [ spotLightsBuffer, index ] ) => {
|
|
163
184
|
|
|
164
|
-
const baseIndex = index.mul(
|
|
185
|
+
const baseIndex = index.mul( 20 );
|
|
165
186
|
return SpotLight( {
|
|
166
187
|
position: vec3(
|
|
167
188
|
spotLightsBuffer.element( baseIndex ),
|
|
@@ -183,6 +204,11 @@ export const getSpotLight = Fn( ( [ spotLightsBuffer, index ] ) => {
|
|
|
183
204
|
penumbra: spotLightsBuffer.element( baseIndex.add( 11 ) ),
|
|
184
205
|
distance: spotLightsBuffer.element( baseIndex.add( 12 ) ),
|
|
185
206
|
decay: spotLightsBuffer.element( baseIndex.add( 13 ) ),
|
|
207
|
+
goboIndex: int( spotLightsBuffer.element( baseIndex.add( 14 ) ) ),
|
|
208
|
+
goboIntensity: spotLightsBuffer.element( baseIndex.add( 15 ) ),
|
|
209
|
+
iesIndex: int( spotLightsBuffer.element( baseIndex.add( 16 ) ) ),
|
|
210
|
+
iesIntensity: spotLightsBuffer.element( baseIndex.add( 17 ) ),
|
|
211
|
+
// slots 18, 19 reserved (vec4 padding)
|
|
186
212
|
} );
|
|
187
213
|
|
|
188
214
|
} );
|
|
@@ -198,13 +224,17 @@ export const isDirectionValid = /*@__PURE__*/ wgslFn( `
|
|
|
198
224
|
}
|
|
199
225
|
` );
|
|
200
226
|
|
|
201
|
-
// Distance attenuation based on Frostbite PBR
|
|
227
|
+
// Distance attenuation based on Frostbite PBR. Integer exponents factored as
|
|
228
|
+
// repeated multiplies — pow(x, 4) and pow(x, 2) are far cheaper this way.
|
|
202
229
|
export const getDistanceAttenuation = /*@__PURE__*/ wgslFn( `
|
|
203
230
|
fn getDistanceAttenuation( lightDistance: f32, cutoffDistance: f32, decayExponent: f32 ) -> f32 {
|
|
204
231
|
var distanceFalloff = 1.0f / max( pow( lightDistance, decayExponent ), 0.01f );
|
|
205
232
|
if ( cutoffDistance > 0.0f ) {
|
|
206
|
-
let
|
|
207
|
-
|
|
233
|
+
let r = lightDistance / cutoffDistance;
|
|
234
|
+
let r2 = r * r;
|
|
235
|
+
let ratio = r2 * r2;
|
|
236
|
+
let window = clamp( 1.0f - ratio, 0.0f, 1.0f );
|
|
237
|
+
distanceFalloff *= window * window;
|
|
208
238
|
}
|
|
209
239
|
return distanceFalloff;
|
|
210
240
|
}
|
|
@@ -218,6 +248,209 @@ export const getSpotAttenuation = /*@__PURE__*/ wgslFn( `
|
|
|
218
248
|
` );
|
|
219
249
|
|
|
220
250
|
|
|
251
|
+
// ================================================================================
|
|
252
|
+
// SPOT LIGHT GOBO (PROJECTION MASK) SAMPLING
|
|
253
|
+
// ================================================================================
|
|
254
|
+
|
|
255
|
+
// Module-level state for spot light gobo masks.
|
|
256
|
+
// Set by ShaderBuilder before graph construction.
|
|
257
|
+
let _goboMapsTexNode = null;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Set the DataArrayTexture node used to sample spot light gobo masks.
|
|
261
|
+
* Must be called before the shader graph is constructed.
|
|
262
|
+
* @param {TextureNode} node - TSL texture node for the gobo DataArrayTexture
|
|
263
|
+
*/
|
|
264
|
+
export function setGoboMapsTexture( node ) {
|
|
265
|
+
|
|
266
|
+
_goboMapsTexNode = node;
|
|
267
|
+
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Sample a spot light's gobo mask. Returns 1.0 if no gobo assigned.
|
|
271
|
+
// Projects the surface direction onto a plane perpendicular to the light's
|
|
272
|
+
// forward axis at unit distance; cone edge maps to UV ±0.5 around centre.
|
|
273
|
+
//
|
|
274
|
+
// `lightDir` = unit direction from surface TO light (matches `LightSample.direction`).
|
|
275
|
+
export const sampleSpotGoboMask = /*@__PURE__*/ Fn( ( [ light, lightDir ] ) => {
|
|
276
|
+
|
|
277
|
+
const mask = float( 1.0 ).toVar();
|
|
278
|
+
|
|
279
|
+
If( light.goboIndex.greaterThanEqual( int( 0 ) ), () => {
|
|
280
|
+
|
|
281
|
+
const forward = light.direction.toVar();
|
|
282
|
+
const toSurface = lightDir.negate().toVar();
|
|
283
|
+
const cosAlpha = dot( toSurface, forward ).toVar();
|
|
284
|
+
|
|
285
|
+
If( cosAlpha.greaterThan( 0.0 ), () => {
|
|
286
|
+
|
|
287
|
+
// Orthonormal basis around forward axis
|
|
288
|
+
const up = select(
|
|
289
|
+
abs( forward.z ).lessThan( 0.999 ),
|
|
290
|
+
vec3( 0.0, 0.0, 1.0 ),
|
|
291
|
+
vec3( 1.0, 0.0, 0.0 ),
|
|
292
|
+
);
|
|
293
|
+
const T = normalize( cross( up, forward ) ).toVar();
|
|
294
|
+
const B = cross( forward, T ).toVar();
|
|
295
|
+
|
|
296
|
+
// Project onto plane perpendicular to forward at distance 1
|
|
297
|
+
const invCos = float( 1.0 ).div( cosAlpha ).toVar();
|
|
298
|
+
const px = dot( toSurface, T ).mul( invCos ).toVar();
|
|
299
|
+
const py = dot( toSurface, B ).mul( invCos ).toVar();
|
|
300
|
+
|
|
301
|
+
// Cone edge → ±tan(angle); map to UV [0,1]
|
|
302
|
+
const tanA = max( tan( light.angle ), float( 1e-4 ) ).toVar();
|
|
303
|
+
const invTan = float( 0.5 ).div( tanA ).toVar();
|
|
304
|
+
const u = clamp( px.mul( invTan ).add( 0.5 ), float( 0.0 ), float( 1.0 ) ).toVar();
|
|
305
|
+
const v = clamp( py.mul( invTan ).add( 0.5 ), float( 0.0 ), float( 1.0 ) ).toVar();
|
|
306
|
+
|
|
307
|
+
if ( _goboMapsTexNode ) {
|
|
308
|
+
|
|
309
|
+
// Sample min(.r, .a) so masks encoded in either RGB-luminance
|
|
310
|
+
// or alpha (Kenney's "Transparent" variants store the shape in alpha
|
|
311
|
+
// with RGB=white) both produce the expected result.
|
|
312
|
+
// Sign of goboIntensity encodes inversion: negative = inverted, |value| = strength.
|
|
313
|
+
const tex = texture( _goboMapsTexNode, vec2( u, v ) ).depth( light.goboIndex );
|
|
314
|
+
const sample = min( tex.r, tex.a );
|
|
315
|
+
const inverted = light.goboIntensity.lessThan( 0.0 );
|
|
316
|
+
const effective = select( inverted, float( 1.0 ).sub( sample ), sample );
|
|
317
|
+
const strength = clamp( abs( light.goboIntensity ), float( 0.0 ), float( 1.0 ) );
|
|
318
|
+
mask.assign( mix( float( 1.0 ), effective, strength ) );
|
|
319
|
+
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
} ).Else( () => {
|
|
323
|
+
|
|
324
|
+
// Behind the light — emit zero so back-hemisphere is dark.
|
|
325
|
+
mask.assign( 0.0 );
|
|
326
|
+
|
|
327
|
+
} );
|
|
328
|
+
|
|
329
|
+
} );
|
|
330
|
+
|
|
331
|
+
return mask;
|
|
332
|
+
|
|
333
|
+
} );
|
|
334
|
+
|
|
335
|
+
// Sample a directional light's gobo mask. Returns 1.0 if no gobo assigned.
|
|
336
|
+
// Projects the shading point onto a plane perpendicular to the light direction;
|
|
337
|
+
// the mask is tiled at `light.goboScale` world units per tile so a single mask
|
|
338
|
+
// can cover any scene size by adjusting the scale.
|
|
339
|
+
//
|
|
340
|
+
// `surfacePoint` = world-space position of the surface being shaded.
|
|
341
|
+
export const sampleDirectionalGoboMask = /*@__PURE__*/ Fn( ( [ light, surfacePoint ] ) => {
|
|
342
|
+
|
|
343
|
+
const mask = float( 1.0 ).toVar();
|
|
344
|
+
|
|
345
|
+
If( light.goboIndex.greaterThanEqual( int( 0 ) ), () => {
|
|
346
|
+
|
|
347
|
+
// `light.direction` in this engine points FROM target TOWARD the light.
|
|
348
|
+
// Project surface point onto plane perpendicular to that axis.
|
|
349
|
+
const axis = light.direction.toVar();
|
|
350
|
+
|
|
351
|
+
const up = select(
|
|
352
|
+
abs( axis.z ).lessThan( 0.999 ),
|
|
353
|
+
vec3( 0.0, 0.0, 1.0 ),
|
|
354
|
+
vec3( 1.0, 0.0, 0.0 ),
|
|
355
|
+
);
|
|
356
|
+
const T = normalize( cross( up, axis ) ).toVar();
|
|
357
|
+
const B = cross( axis, T ).toVar();
|
|
358
|
+
|
|
359
|
+
const invScale = float( 1.0 ).div( max( light.goboScale, float( 1e-4 ) ) ).toVar();
|
|
360
|
+
const u = dot( surfacePoint, T ).mul( invScale ).add( 0.5 ).toVar();
|
|
361
|
+
const v = dot( surfacePoint, B ).mul( invScale ).add( 0.5 ).toVar();
|
|
362
|
+
|
|
363
|
+
// Tile by fract so a single mask can cover any scene size.
|
|
364
|
+
const uTiled = u.sub( u.floor() ).toVar();
|
|
365
|
+
const vTiled = v.sub( v.floor() ).toVar();
|
|
366
|
+
|
|
367
|
+
if ( _goboMapsTexNode ) {
|
|
368
|
+
|
|
369
|
+
const tex = texture( _goboMapsTexNode, vec2( uTiled, vTiled ) ).depth( light.goboIndex );
|
|
370
|
+
const sample = min( tex.r, tex.a );
|
|
371
|
+
const inverted = light.goboIntensity.lessThan( 0.0 );
|
|
372
|
+
const effective = select( inverted, float( 1.0 ).sub( sample ), sample );
|
|
373
|
+
const strength = clamp( abs( light.goboIntensity ), float( 0.0 ), float( 1.0 ) );
|
|
374
|
+
mask.assign( mix( float( 1.0 ), effective, strength ) );
|
|
375
|
+
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
} );
|
|
379
|
+
|
|
380
|
+
return mask;
|
|
381
|
+
|
|
382
|
+
} );
|
|
383
|
+
|
|
384
|
+
// ================================================================================
|
|
385
|
+
// IES PROFILE (PHOTOMETRIC INTENSITY) SAMPLING
|
|
386
|
+
// ================================================================================
|
|
387
|
+
|
|
388
|
+
// Module-level texture node for IES profile DataArrayTexture.
|
|
389
|
+
// Set by ShaderBuilder before graph construction.
|
|
390
|
+
let _iesProfilesTexNode = null;
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Bind the DataArrayTexture node carrying all loaded IES profiles.
|
|
394
|
+
* @param {TextureNode} node
|
|
395
|
+
*/
|
|
396
|
+
export function setIESProfilesTexture( node ) {
|
|
397
|
+
|
|
398
|
+
_iesProfilesTexNode = node;
|
|
399
|
+
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Sample a spot light's IES profile. Returns a normalized multiplier in [0,1]
|
|
403
|
+
// (or 1.0 if no profile assigned).
|
|
404
|
+
//
|
|
405
|
+
// IES texture layout: U = horizontal angle (0..360°), V = vertical angle (0..180°)
|
|
406
|
+
// where V=0 is along the light's "forward" axis (the spot's direction).
|
|
407
|
+
//
|
|
408
|
+
// `lightDir` = unit direction from surface TO light (matches LightSample.direction).
|
|
409
|
+
export const sampleIESProfile = /*@__PURE__*/ Fn( ( [ light, lightDir ] ) => {
|
|
410
|
+
|
|
411
|
+
const result = float( 1.0 ).toVar();
|
|
412
|
+
|
|
413
|
+
If( light.iesIndex.greaterThanEqual( int( 0 ) ), () => {
|
|
414
|
+
|
|
415
|
+
const forward = light.direction.toVar();
|
|
416
|
+
const toSurface = lightDir.negate().toVar();
|
|
417
|
+
|
|
418
|
+
// Vertical angle: between forward axis and emission direction. 0 = on axis (V=0),
|
|
419
|
+
// PI = anti-axis (V=1).
|
|
420
|
+
const cosV = clamp( dot( toSurface, forward ), float( - 1.0 ), float( 1.0 ) ).toVar();
|
|
421
|
+
const vAngle = acos( cosV ).toVar();
|
|
422
|
+
const v = clamp( vAngle.div( float( Math.PI ) ), float( 0.0 ), float( 1.0 ) ).toVar();
|
|
423
|
+
|
|
424
|
+
// Horizontal angle: project emission direction onto plane perpendicular to forward.
|
|
425
|
+
const up = select(
|
|
426
|
+
abs( forward.z ).lessThan( 0.999 ),
|
|
427
|
+
vec3( 0.0, 0.0, 1.0 ),
|
|
428
|
+
vec3( 1.0, 0.0, 0.0 ),
|
|
429
|
+
);
|
|
430
|
+
const T = normalize( cross( up, forward ) ).toVar();
|
|
431
|
+
const B = cross( forward, T ).toVar();
|
|
432
|
+
|
|
433
|
+
const px = dot( toSurface, T );
|
|
434
|
+
const py = dot( toSurface, B );
|
|
435
|
+
// atan2 → [-PI, PI]; remap to [0, 2PI] then to [0, 1].
|
|
436
|
+
const phi = atan( py, px );
|
|
437
|
+
const u = phi.div( float( 2.0 * Math.PI ) ).add( 0.5 ).toVar();
|
|
438
|
+
|
|
439
|
+
if ( _iesProfilesTexNode ) {
|
|
440
|
+
|
|
441
|
+
const sample = texture( _iesProfilesTexNode, vec2( u, v ) ).depth( light.iesIndex ).r;
|
|
442
|
+
// Blend between flat (1.0) and full profile by iesIntensity.
|
|
443
|
+
const strength = clamp( light.iesIntensity, float( 0.0 ), float( 1.0 ) );
|
|
444
|
+
result.assign( mix( float( 1.0 ), sample, strength ) );
|
|
445
|
+
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
} );
|
|
449
|
+
|
|
450
|
+
return result;
|
|
451
|
+
|
|
452
|
+
} );
|
|
453
|
+
|
|
221
454
|
// ================================================================================
|
|
222
455
|
// CONE SAMPLING FOR SOFT DIRECTIONAL SHADOWS
|
|
223
456
|
// ================================================================================
|
package/src/TSL/LightsDirect.js
CHANGED
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
Loop,
|
|
12
12
|
Break,
|
|
13
13
|
dot,
|
|
14
|
+
cross,
|
|
15
|
+
normalize,
|
|
14
16
|
abs,
|
|
15
17
|
max,
|
|
16
18
|
min,
|
|
@@ -61,7 +63,7 @@ export function setAlphaShadowsUniform( node ) {
|
|
|
61
63
|
|
|
62
64
|
// Note: traverseBVH is passed as a parameter to avoid circular dependency
|
|
63
65
|
export const traceShadowRay = Fn( ( [
|
|
64
|
-
origin, dir, maxDist,
|
|
66
|
+
origin, dir, maxDist,
|
|
65
67
|
// BVH traversal function and textures passed as parameters
|
|
66
68
|
traverseBVHShadowFn,
|
|
67
69
|
bvhBuffer,
|
|
@@ -83,7 +85,6 @@ export const traceShadowRay = Fn( ( [
|
|
|
83
85
|
shadowRay,
|
|
84
86
|
bvhBuffer,
|
|
85
87
|
triangleBuffer,
|
|
86
|
-
materialBuffer,
|
|
87
88
|
remainingDist,
|
|
88
89
|
) );
|
|
89
90
|
|
|
@@ -184,8 +185,18 @@ export const traceShadowRay = Fn( ( [
|
|
|
184
185
|
|
|
185
186
|
} ).ElseIf( shadowMaterial.transmission.greaterThan( 0.0 ), () => {
|
|
186
187
|
|
|
187
|
-
|
|
188
|
-
|
|
188
|
+
// Deferred geometric-normal compute — refetch triangle positions and
|
|
189
|
+
// derive the normal here so opaque/alpha-cutout shadow hits don't pay
|
|
190
|
+
// the cross+normalize cost in BVH traversal.
|
|
191
|
+
const TRI_STRIDE_N = int( 8 );
|
|
192
|
+
const pA = getDatafromStorageBuffer( triangleBuffer, shadowHit.triangleIndex, int( 0 ), TRI_STRIDE_N ).xyz;
|
|
193
|
+
const pB = getDatafromStorageBuffer( triangleBuffer, shadowHit.triangleIndex, int( 1 ), TRI_STRIDE_N ).xyz;
|
|
194
|
+
const pC = getDatafromStorageBuffer( triangleBuffer, shadowHit.triangleIndex, int( 2 ), TRI_STRIDE_N ).xyz;
|
|
195
|
+
const geomNormal = normalize( cross( pB.sub( pA ), pC.sub( pA ) ) );
|
|
196
|
+
shadowHit.normal.assign( geomNormal );
|
|
197
|
+
|
|
198
|
+
const entering = dot( dir, geomNormal ).lessThan( 0.0 );
|
|
199
|
+
const N = select( entering, geomNormal, geomNormal.negate() );
|
|
189
200
|
|
|
190
201
|
// Apply absorption if exiting medium
|
|
191
202
|
If( entering.not().and( shadowMaterial.attenuationDistance.greaterThan( 0.0 ) ), () => {
|
|
@@ -285,7 +296,7 @@ export const calculateRayOffset = Fn( ( [ hitPoint, normal, material ] ) => {
|
|
|
285
296
|
// LIGHT IMPORTANCE ESTIMATION
|
|
286
297
|
// ================================================================================
|
|
287
298
|
|
|
288
|
-
export const calculateDirectionalLightImportance = Fn( ( [ light,
|
|
299
|
+
export const calculateDirectionalLightImportance = Fn( ( [ light, normal, material, bounceIndex ] ) => {
|
|
289
300
|
|
|
290
301
|
const NoL = max( float( 0.0 ), dot( normal, light.direction ) );
|
|
291
302
|
const result = float( 0.0 ).toVar();
|
|
@@ -19,18 +19,13 @@ import {
|
|
|
19
19
|
float,
|
|
20
20
|
vec2,
|
|
21
21
|
vec3,
|
|
22
|
-
vec4,
|
|
23
22
|
int,
|
|
24
|
-
uint,
|
|
25
23
|
bool as tslBool,
|
|
26
24
|
max,
|
|
27
|
-
min,
|
|
28
25
|
abs,
|
|
29
26
|
sqrt,
|
|
30
27
|
dot,
|
|
31
28
|
normalize,
|
|
32
|
-
length,
|
|
33
|
-
clamp,
|
|
34
29
|
If,
|
|
35
30
|
select,
|
|
36
31
|
} from 'three/tsl';
|
|
@@ -40,10 +35,8 @@ import {
|
|
|
40
35
|
} from './LightsCore.js';
|
|
41
36
|
import {
|
|
42
37
|
SamplingStrategyWeights,
|
|
43
|
-
ImportanceSamplingInfo,
|
|
44
38
|
} from './Struct.js';
|
|
45
39
|
import {
|
|
46
|
-
PI,
|
|
47
40
|
PI_INV,
|
|
48
41
|
EPSILON,
|
|
49
42
|
MIN_PDF,
|
|
@@ -51,7 +44,6 @@ import {
|
|
|
51
44
|
import { DistributionGGX, calculateVNDFPDF } from './MaterialProperties.js';
|
|
52
45
|
import { evaluateMaterialResponse } from './MaterialEvaluation.js';
|
|
53
46
|
import { RandomValue } from './Random.js';
|
|
54
|
-
import { sampleEquirectProbability, sampleEquirect } from './Environment.js';
|
|
55
47
|
import { sampleMicrofacetTransmission, MicrofacetTransmissionResult } from './MaterialTransmission.js';
|
|
56
48
|
import { cosineWeightedSample } from './MaterialSampling.js';
|
|
57
49
|
|
|
@@ -115,8 +107,7 @@ export const calculateClearcoatPDF = Fn( ( [ V, L, N, clearcoatRoughness ] ) =>
|
|
|
115
107
|
|
|
116
108
|
// Compute normalized strategy weights based on importance info
|
|
117
109
|
export const computeSamplingInfo = Fn( ( [
|
|
118
|
-
samplingInfo,
|
|
119
|
-
enableEnvironmentLight, useEnvMapIS,
|
|
110
|
+
samplingInfo,
|
|
120
111
|
] ) => {
|
|
121
112
|
|
|
122
113
|
// Material-only strategies (env handled via deterministic NEE in direct lighting)
|
|
@@ -177,13 +168,11 @@ export const computeSamplingInfo = Fn( ( [
|
|
|
177
168
|
// Strategy Selection via Cumulative Distribution
|
|
178
169
|
// =============================================================================
|
|
179
170
|
|
|
180
|
-
// Returns vec2(selectedStrategy, strategyPdf)
|
|
181
171
|
// Strategy IDs: 1=specular, 2=diffuse, 3=transmission, 4=clearcoat
|
|
182
172
|
// (env removed — handled via deterministic NEE in direct lighting)
|
|
183
173
|
export const selectSamplingStrategy = Fn( ( [ weights, randomValue ] ) => {
|
|
184
174
|
|
|
185
175
|
const selectedStrategy = int( 2 ).toVar(); // Default: diffuse
|
|
186
|
-
const strategyPdf = float( 1.0 ).toVar();
|
|
187
176
|
|
|
188
177
|
const cumulative = float( 0.0 ).toVar();
|
|
189
178
|
const found = tslBool( false ).toVar();
|
|
@@ -195,7 +184,6 @@ export const selectSamplingStrategy = Fn( ( [ weights, randomValue ] ) => {
|
|
|
195
184
|
If( randomValue.lessThan( cumulative ), () => {
|
|
196
185
|
|
|
197
186
|
selectedStrategy.assign( 1 );
|
|
198
|
-
strategyPdf.assign( weights.specularWeight );
|
|
199
187
|
found.assign( tslBool( true ) );
|
|
200
188
|
|
|
201
189
|
} );
|
|
@@ -209,7 +197,6 @@ export const selectSamplingStrategy = Fn( ( [ weights, randomValue ] ) => {
|
|
|
209
197
|
If( randomValue.lessThan( cumulative ), () => {
|
|
210
198
|
|
|
211
199
|
selectedStrategy.assign( 2 );
|
|
212
|
-
strategyPdf.assign( weights.diffuseWeight );
|
|
213
200
|
found.assign( tslBool( true ) );
|
|
214
201
|
|
|
215
202
|
} );
|
|
@@ -223,7 +210,6 @@ export const selectSamplingStrategy = Fn( ( [ weights, randomValue ] ) => {
|
|
|
223
210
|
If( randomValue.lessThan( cumulative ), () => {
|
|
224
211
|
|
|
225
212
|
selectedStrategy.assign( 3 );
|
|
226
|
-
strategyPdf.assign( weights.transmissionWeight );
|
|
227
213
|
found.assign( tslBool( true ) );
|
|
228
214
|
|
|
229
215
|
} );
|
|
@@ -233,20 +219,10 @@ export const selectSamplingStrategy = Fn( ( [ weights, randomValue ] ) => {
|
|
|
233
219
|
If( weights.useClearcoat.and( found.not() ), () => {
|
|
234
220
|
|
|
235
221
|
selectedStrategy.assign( 4 );
|
|
236
|
-
strategyPdf.assign( weights.clearcoatWeight );
|
|
237
|
-
found.assign( tslBool( true ) );
|
|
238
|
-
|
|
239
|
-
} );
|
|
240
|
-
|
|
241
|
-
// Fallback
|
|
242
|
-
If( found.not(), () => {
|
|
243
|
-
|
|
244
|
-
selectedStrategy.assign( 2 ); // Diffuse
|
|
245
|
-
strategyPdf.assign( select( weights.useDiffuse, weights.diffuseWeight, float( 1.0 ) ) );
|
|
246
222
|
|
|
247
223
|
} );
|
|
248
224
|
|
|
249
|
-
return
|
|
225
|
+
return selectedStrategy;
|
|
250
226
|
|
|
251
227
|
} );
|
|
252
228
|
|
|
@@ -268,13 +244,8 @@ export const calculateIndirectLighting = Fn( ( [
|
|
|
268
244
|
V, N, material,
|
|
269
245
|
// brdfSample fields (DirectionSample)
|
|
270
246
|
brdfSampleDirection, brdfSamplePdf, brdfSampleValue,
|
|
271
|
-
sampleIndex, bounceIndex,
|
|
272
247
|
rngState,
|
|
273
248
|
samplingInfo,
|
|
274
|
-
// Environment resources
|
|
275
|
-
envTexture, environmentIntensity, envMatrix,
|
|
276
|
-
envTotalSum, envCompensationDelta, envResolution,
|
|
277
|
-
enableEnvironmentLight, useEnvMapIS,
|
|
278
249
|
] ) => {
|
|
279
250
|
|
|
280
251
|
// Initialize result
|
|
@@ -289,7 +260,6 @@ export const calculateIndirectLighting = Fn( ( [
|
|
|
289
260
|
.and( samplingInfo.specularImportance.greaterThanEqual( 0.0 ) )
|
|
290
261
|
.and( samplingInfo.transmissionImportance.greaterThanEqual( 0.0 ) )
|
|
291
262
|
.and( samplingInfo.clearcoatImportance.greaterThanEqual( 0.0 ) )
|
|
292
|
-
.and( samplingInfo.envmapImportance.greaterThanEqual( 0.0 ) )
|
|
293
263
|
.toVar();
|
|
294
264
|
|
|
295
265
|
If( validInput.not(), () => {
|
|
@@ -307,8 +277,7 @@ export const calculateIndirectLighting = Fn( ( [
|
|
|
307
277
|
|
|
308
278
|
// Use corrected sampling info
|
|
309
279
|
const weights = SamplingStrategyWeights.wrap( computeSamplingInfo(
|
|
310
|
-
samplingInfo,
|
|
311
|
-
enableEnvironmentLight, useEnvMapIS,
|
|
280
|
+
samplingInfo,
|
|
312
281
|
).toVar() );
|
|
313
282
|
|
|
314
283
|
const selectionRand = RandomValue( rngState ).toVar();
|
|
@@ -317,9 +286,7 @@ export const calculateIndirectLighting = Fn( ( [
|
|
|
317
286
|
const sampleRand = vec2( r1, r2 ).toVar();
|
|
318
287
|
|
|
319
288
|
// Strategy selection
|
|
320
|
-
const
|
|
321
|
-
const selectedStrategy = int( strategyResult.x ).toVar();
|
|
322
|
-
const strategySelectionPdf = strategyResult.y.toVar();
|
|
289
|
+
const selectedStrategy = selectSamplingStrategy( weights, selectionRand ).toVar();
|
|
323
290
|
|
|
324
291
|
const sampleDir = vec3( 0.0 ).toVar();
|
|
325
292
|
const samplePdf = float( 0.0 ).toVar();
|
|
@@ -346,8 +313,9 @@ export const calculateIndirectLighting = Fn( ( [
|
|
|
346
313
|
|
|
347
314
|
// Strategy 3: Transmission
|
|
348
315
|
const entering = dot( V, N ).greaterThan( 0.0 ).toVar();
|
|
316
|
+
// pathWavelength=0 — MIS evaluation reads only direction/PDF, no spectral tint
|
|
349
317
|
const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission(
|
|
350
|
-
V, N, material.ior, material.roughness, entering, material.dispersion, sampleRand, rngState
|
|
318
|
+
V, N, material.ior, material.roughness, entering, material.dispersion, sampleRand, rngState, float( 0.0 )
|
|
351
319
|
).toVar() );
|
|
352
320
|
sampleDir.assign( mtResult.direction );
|
|
353
321
|
samplePdf.assign( mtResult.pdf );
|