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.
- package/dist/rayzee.es.js +3855 -3065
- package/dist/rayzee.umd.js +83 -109
- package/package.json +1 -1
- package/src/EngineDefaults.js +4 -3
- package/src/PathTracerApp.js +7 -0
- package/src/Pipeline/BindingAudit.js +178 -0
- package/src/Pipeline/RenderPipeline.js +5 -2
- package/src/Processor/CameraOptimizer.js +0 -5
- package/src/Processor/PackedRayBuffer.js +33 -8
- package/src/Stages/ASVGF.js +10 -10
- package/src/Stages/BilateralFilter.js +70 -11
- package/src/Stages/EdgeFilter.js +44 -6
- package/src/Stages/NormalDepth.js +10 -2
- package/src/Stages/PathTracer.js +7 -3
- package/src/Stages/PathTracerStage.js +15 -46
- package/src/TSL/Clearcoat.js +29 -40
- package/src/TSL/Common.js +1 -47
- package/src/TSL/EmissiveSampling.js +47 -50
- package/src/TSL/LightBVHSampling.js +16 -12
- package/src/TSL/LightsCore.js +3 -2
- package/src/TSL/LightsDirect.js +4 -4
- package/src/TSL/LightsIndirect.js +36 -355
- package/src/TSL/LightsSampling.js +153 -386
- 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 +51 -66
- package/src/TSL/Random.js +202 -132
- package/src/TSL/ShadeKernel.js +89 -56
- package/src/TSL/Struct.js +6 -38
- package/src/TSL/Subsurface.js +13 -8
- package/src/index.js +5 -0
- package/src/managers/EnvironmentManager.js +0 -5
- package/src/managers/UniformManager.js +4 -1
package/src/TSL/ShadeKernel.js
CHANGED
|
@@ -27,7 +27,7 @@ import { sampleChromaticCollision, sampleHenyeyGreenstein, subsurfaceCoefficient
|
|
|
27
27
|
import { calculateIndirectLighting } from './LightsIndirect.js';
|
|
28
28
|
import { IndirectLightingResult, sampleCone } from './LightsCore.js';
|
|
29
29
|
import { regularizePathContribution, generateSampledDirection, computeNDCDepth, handleRussianRoulette } from './PathTracerCore.js';
|
|
30
|
-
import {
|
|
30
|
+
import { evaluateDFG } from './MaterialProperties.js';
|
|
31
31
|
import { dielectricF0 } from './Fresnel.js';
|
|
32
32
|
import { sampleClearcoat, ClearcoatResult } from './Clearcoat.js';
|
|
33
33
|
import { refineDisplacedIntersection, DisplacementResult } from './Displacement.js';
|
|
@@ -40,20 +40,20 @@ import {
|
|
|
40
40
|
MaterialSamples,
|
|
41
41
|
ExtMapResult,
|
|
42
42
|
DirectionSample,
|
|
43
|
-
ImportanceSamplingInfo,
|
|
44
43
|
MaterialClassification,
|
|
45
44
|
BRDFWeights,
|
|
46
45
|
MaterialCache,
|
|
47
46
|
DirectLightingDual,
|
|
48
47
|
DFGResult,
|
|
49
48
|
} from './Struct.js';
|
|
50
|
-
import {
|
|
49
|
+
import { getRandomSample, getRandomSample1D, getRandomSample2D, SAMPLER_DIMS_PER_BOUNCE, SAMPLER_DIM_AUX_BASE } from './Random.js';
|
|
51
50
|
import { RAY_FLAG, COUNTER } from '../Processor/QueueManager.js';
|
|
52
51
|
import {
|
|
53
52
|
readRayOrigin, readRayDirection, readRayBounceFlags, readRayThroughput, readRayPdf,
|
|
54
53
|
readMediumStack, writeMediumStack, readMediumSigmaA, writeMediumSigmaA,
|
|
55
54
|
readPathBounces, readSssSteps, readSSSMedium, writeSSSMedium,
|
|
56
55
|
readTransparentCount,
|
|
56
|
+
readMisRayT,
|
|
57
57
|
readHitDistance, readHitBarycentrics, readHitNormal,
|
|
58
58
|
readHitMaterialIndex, readHitTriangleIndex,
|
|
59
59
|
writeRayOriginMeta, writeRayDirFlags, writeRayThroughputPdf, writeRayRadiance,
|
|
@@ -80,7 +80,7 @@ export function buildShadeKernel( params ) {
|
|
|
80
80
|
counters,
|
|
81
81
|
activeIndicesRO,
|
|
82
82
|
envTexture, environmentIntensity, envMatrix,
|
|
83
|
-
enableEnvironmentLight,
|
|
83
|
+
enableEnvironmentLight,
|
|
84
84
|
groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight, groundProjectionLevel,
|
|
85
85
|
enableGroundCatcher, groundCatcherHeight,
|
|
86
86
|
envTotalSum, envCompensationDelta, envResolution,
|
|
@@ -95,6 +95,10 @@ export function buildShadeKernel( params ) {
|
|
|
95
95
|
globalIlluminationIntensity,
|
|
96
96
|
cameraProjectionMatrix, cameraViewMatrix,
|
|
97
97
|
fireflyThreshold, frame, resolution,
|
|
98
|
+
// Accumulation index, NOT the free-running seed counter `frame` rides. The firefly clamp
|
|
99
|
+
// relaxes as sqrt(frame+1) so its bias decays as a render converges; keying that off the
|
|
100
|
+
// seed axis let it grow for the whole session and disabled suppression outright.
|
|
101
|
+
accumFrame,
|
|
98
102
|
chunkRowBase, // chunked path pool: global pixel = chunkRowBase·W + localSlot (rayID). See spec.
|
|
99
103
|
emissiveTriangleCount, emissiveVec4Offset, emissiveTotalPower,
|
|
100
104
|
emissiveBoost, totalTriangleCount, enableEmissiveTriangleSampling,
|
|
@@ -114,17 +118,17 @@ export function buildShadeKernel( params ) {
|
|
|
114
118
|
// miss branch and the shadow catcher so their blur stays in lockstep (no horizon seam). normalize() the
|
|
115
119
|
// center direction — ground projection can return a non-unit vector, which would skew sampleCone's basis.
|
|
116
120
|
// Clamp the tap count to ≥1 so a 0 forced via the engine API can't produce a 0/0 NaN backdrop.
|
|
117
|
-
const sampleEnvBlurred = ( centerDir, halfAngle, samples, rng ) => {
|
|
121
|
+
const sampleEnvBlurred = ( centerDir, halfAngle, samples, rng, pixelCoord ) => {
|
|
118
122
|
|
|
119
123
|
const axis = normalize( centerDir ).toVar();
|
|
120
124
|
const n = max( samples, int( 1 ) ).toVar();
|
|
121
125
|
const acc = vec3( 0.0 ).toVar();
|
|
122
|
-
Loop( { start: int( 0 ), end: n, type: 'int', condition: '<' }, () => {
|
|
126
|
+
Loop( { start: int( 0 ), end: n, type: 'int', condition: '<' }, ( { i } ) => {
|
|
123
127
|
|
|
124
|
-
// per
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
const jDir = sampleCone( axis, halfAngle,
|
|
128
|
+
// Tap count varies per pixel, so these cannot sit in a per-bounce block without
|
|
129
|
+
// running over into the next bounce's dimensions — they get the AUX range instead.
|
|
130
|
+
const jXi = getRandomSample2D( pixelCoord, int( 0 ), int( SAMPLER_DIM_AUX_BASE ).add( i ), rng, resolution, frame ).toVar();
|
|
131
|
+
const jDir = sampleCone( axis, halfAngle, jXi ).toVar();
|
|
128
132
|
acc.addAssign( sampleEnvironment( {
|
|
129
133
|
tex: envTexture,
|
|
130
134
|
samp: sampler( envTexture ),
|
|
@@ -189,6 +193,19 @@ export function buildShadeKernel( params ) {
|
|
|
189
193
|
const pixelIndex = rayID;
|
|
190
194
|
const rngState = rngBufferRW.element( rayID ).toVar();
|
|
191
195
|
|
|
196
|
+
// STBN keyed on (GLOBAL pixel, dimension, frame). pixelIndex is the LOCAL path slot; the global pixel
|
|
197
|
+
// = chunkRowBase·W + localSlot, so the blue-noise pattern stays spatially aligned across row-band chunks
|
|
198
|
+
// (and matches Generate's per-pixel RNG seed). chunkRowBase is 0 in the single-chunk case.
|
|
199
|
+
// Hoisted here rather than at the BSDF draw because the ground catcher runs NEE far earlier.
|
|
200
|
+
const _resX = int( resolution.x ).toVar();
|
|
201
|
+
const _globalPixel = int( pixelIndex ).add( chunkRowBase.mul( _resX ) );
|
|
202
|
+
const _pixelCoord = vec2(
|
|
203
|
+
float( _globalPixel.mod( _resX ) ).add( 0.5 ),
|
|
204
|
+
float( _globalPixel.div( _resX ) ).add( 0.5 ),
|
|
205
|
+
);
|
|
206
|
+
// This bounce's block of sampler dimensions — see the budget table in Random.js.
|
|
207
|
+
const dimBase = int( currentBounce ).mul( int( SAMPLER_DIMS_PER_BOUNCE ) );
|
|
208
|
+
|
|
192
209
|
// DDFA see-through aux tint carried across smooth glass/mirror; committed into the OIDN/ASVGF
|
|
193
210
|
// albedo guide at the first diffuse-enough surface (or env). Mutated locally, persisted on every
|
|
194
211
|
// deferring continue (a missed persist = stale tint). 1 = untinted (direct hit → today's albedo).
|
|
@@ -229,11 +246,9 @@ export function buildShadeKernel( params ) {
|
|
|
229
246
|
const planeMat = RayTracingMaterial.wrap( diffuseGroundMaterial() ).toVar();
|
|
230
247
|
|
|
231
248
|
// Cosine-weighted hemisphere BRDF sample about the plane normal (0,1,0); the helper
|
|
232
|
-
// puts cosθ along N, so bDir.y == cosθ.
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
const u2 = RandomValue( rngState ).toVar();
|
|
236
|
-
const bDir = cosineWeightedSample( planeN, vec2( u1, u2 ) ).toVar();
|
|
249
|
+
// puts cosθ along N, so bDir.y == cosθ.
|
|
250
|
+
const bXi = getRandomSample2D( _pixelCoord, int( 0 ), dimBase.add( int( 7 ) ), rngState, resolution, frame ).toVar();
|
|
251
|
+
const bDir = cosineWeightedSample( planeN, bXi ).toVar();
|
|
237
252
|
const bPdf = bDir.y.mul( PI_INV ).toVar(); // cosθ / π
|
|
238
253
|
const bVal = vec3( PI_INV ); // albedo(1) / π
|
|
239
254
|
|
|
@@ -243,6 +258,7 @@ export function buildShadeKernel( params ) {
|
|
|
243
258
|
planePoint, planeN, planeMat, planeV,
|
|
244
259
|
bDir, bPdf, bVal,
|
|
245
260
|
bounceIndex, rngState,
|
|
261
|
+
_pixelCoord, resolution, frame, dimBase,
|
|
246
262
|
directionalLightsBuffer, numDirectionalLights,
|
|
247
263
|
areaLightsBuffer, numAreaLights,
|
|
248
264
|
pointLightsBuffer, numPointLights,
|
|
@@ -289,7 +305,7 @@ export function buildShadeKernel( params ) {
|
|
|
289
305
|
|
|
290
306
|
If( backgroundBlurriness.greaterThan( 0.0 ), () => {
|
|
291
307
|
|
|
292
|
-
envBehind.assign( sampleEnvBlurred( catcherEnvDir, backgroundBlurriness.mul( 1.3 ), backgroundBlurSamples, rngState ) );
|
|
308
|
+
envBehind.assign( sampleEnvBlurred( catcherEnvDir, backgroundBlurriness.mul( 1.3 ), backgroundBlurSamples, rngState, _pixelCoord ) );
|
|
293
309
|
|
|
294
310
|
} ).Else( () => {
|
|
295
311
|
|
|
@@ -373,7 +389,7 @@ export function buildShadeKernel( params ) {
|
|
|
373
389
|
const envColor = vec3( 0.0 ).toVar();
|
|
374
390
|
If( isBackdropView.and( backgroundBlurriness.greaterThan( 0.0 ) ), () => {
|
|
375
391
|
|
|
376
|
-
envColor.assign( sampleEnvBlurred( envDir, backgroundBlurriness.mul( 1.3 ), backgroundBlurSamples, rngState ) );
|
|
392
|
+
envColor.assign( sampleEnvBlurred( envDir, backgroundBlurriness.mul( 1.3 ), backgroundBlurSamples, rngState, _pixelCoord ) );
|
|
377
393
|
|
|
378
394
|
} ).Else( () => {
|
|
379
395
|
|
|
@@ -394,7 +410,7 @@ export function buildShadeKernel( params ) {
|
|
|
394
410
|
|
|
395
411
|
// MIS weight for implicit env hit — prevents double-counting with NEE
|
|
396
412
|
const envMisWeight = float( 1.0 ).toVar();
|
|
397
|
-
If( isBackdropView.not()
|
|
413
|
+
If( isBackdropView.not(), () => {
|
|
398
414
|
|
|
399
415
|
const prevBouncePdf = readRayPdf( rayBufferRW, rayID );
|
|
400
416
|
If( prevBouncePdf.greaterThan( 0.0 ), () => {
|
|
@@ -429,7 +445,7 @@ export function buildShadeKernel( params ) {
|
|
|
429
445
|
currentRadiance.xyz.add(
|
|
430
446
|
regularizePathContribution(
|
|
431
447
|
throughput.mul( envColor ).mul( envScale ),
|
|
432
|
-
fireflyPathLen, fireflyThreshold, int(
|
|
448
|
+
fireflyPathLen, fireflyThreshold, int( accumFrame ),
|
|
433
449
|
),
|
|
434
450
|
),
|
|
435
451
|
currentRadiance.w
|
|
@@ -519,13 +535,14 @@ export function buildShadeKernel( params ) {
|
|
|
519
535
|
const mSigmaT = mSigmaA.add( mSigmaS );
|
|
520
536
|
const coll = CollisionSample.wrap( sampleChromaticCollision(
|
|
521
537
|
mSigmaT, mSigmaS, throughput, hitDist, rngState,
|
|
538
|
+
_pixelCoord, resolution, frame, dimBase,
|
|
522
539
|
) ).toVar();
|
|
523
540
|
throughput.mulAssign( coll.weight );
|
|
524
541
|
|
|
525
542
|
If( coll.didScatter, () => {
|
|
526
543
|
|
|
527
544
|
// scatter via Henyey-Greenstein, continue as a free bounce off the sssSteps budget
|
|
528
|
-
const xi2 =
|
|
545
|
+
const xi2 = getRandomSample2D( _pixelCoord, int( 0 ), dimBase.add( int( 6 ) ), rngState, resolution, frame );
|
|
529
546
|
const scatterPoint = origin.add( direction.mul( coll.t ) );
|
|
530
547
|
const newDir = sampleHenyeyGreenstein( direction, mG, xi2 ).toVar();
|
|
531
548
|
sssSteps.addAssign( 1 );
|
|
@@ -533,7 +550,7 @@ export function buildShadeKernel( params ) {
|
|
|
533
550
|
// terminate walk: step cap or Russian roulette
|
|
534
551
|
const rrP = clamp( max( max( throughput.x, throughput.y ), throughput.z ), 0.02, 1.0 ).toVar();
|
|
535
552
|
const terminate = sssSteps.greaterThanEqual( maxSubsurfaceSteps )
|
|
536
|
-
.or(
|
|
553
|
+
.or( getRandomSample1D( _pixelCoord, int( 0 ), dimBase.add( int( 12 ) ), rngState, resolution, frame ).greaterThan( rrP ) ).toVar();
|
|
537
554
|
|
|
538
555
|
If( terminate, () => {
|
|
539
556
|
|
|
@@ -728,6 +745,7 @@ export function buildShadeKernel( params ) {
|
|
|
728
745
|
const currentRay = Ray( { origin, direction } );
|
|
729
746
|
const interaction = MaterialInteractionResult.wrap( handleMaterialTransparency(
|
|
730
747
|
currentRay, N, material, rngState,
|
|
748
|
+
_pixelCoord, resolution, frame, dimBase,
|
|
731
749
|
int( transTraversals ),
|
|
732
750
|
currentMediumIOR, previousMediumIOR,
|
|
733
751
|
pathWavelength,
|
|
@@ -914,7 +932,10 @@ export function buildShadeKernel( params ) {
|
|
|
914
932
|
} );
|
|
915
933
|
|
|
916
934
|
} );
|
|
917
|
-
|
|
935
|
+
// Alpha skip keeps the direction, so the MIS origin is recoverable: accumulate the segment.
|
|
936
|
+
// Any redirection (refraction/reflection/SSS) breaks the line — reset.
|
|
937
|
+
const misRayT = select( interaction.isAlphaSkip, readMisRayT( rayBufferRW, rayID ).add( hitDist ), float( 0.0 ) );
|
|
938
|
+
writeRayOriginMeta( rayBufferRW, rayID, newOrigin, cameraDepth, sssSteps, transparentCount, misRayT );
|
|
918
939
|
writeRayDirFlags( rayBufferRW, rayID, interaction.direction, flags );
|
|
919
940
|
// Free bounce: preserve prevBouncePdf (megakernel keeps the last opaque-scatter pdf across
|
|
920
941
|
// transmission/alpha-skip/SSS-boundary). Writing 1.0 corrupts the next bounce's env/emissive MIS,
|
|
@@ -967,18 +988,24 @@ export function buildShadeKernel( params ) {
|
|
|
967
988
|
// MIS partner pdf MUST match the actual NEE sampler: re-walk the Light BVH descent
|
|
968
989
|
// when it is active, else use the flat-CDF pdf. Mismatching them breaks MIS
|
|
969
990
|
// partition-of-unity → a real bias (see calculateLightBVHPdf).
|
|
991
|
+
// Evaluate it at the scatter point that produced prevBouncePdf — alpha skips moved
|
|
992
|
+
// the ray origin, and the skip-point pdf under-counts dist² so full-Le BSDF hits
|
|
993
|
+
// pass nearly unweighted (fireflies on emitters inside alpha-blend shades).
|
|
994
|
+
const misT = readMisRayT( rayBufferRW, rayID ).toVar();
|
|
995
|
+
const misOrigin = origin.sub( direction.mul( misT ) ).toVar();
|
|
996
|
+
const misDist = hitDist.add( misT ).toVar();
|
|
970
997
|
const lightPdf = float( 0.0 ).toVar();
|
|
971
998
|
If( lightBVHNodeCount.greaterThan( int( 0 ) ), () => {
|
|
972
999
|
|
|
973
1000
|
lightPdf.assign( calculateLightBVHPdf(
|
|
974
|
-
int( hitTriIdx ),
|
|
1001
|
+
int( hitTriIdx ), misDist, direction, misOrigin,
|
|
975
1002
|
lightBuffer, emissiveVec4Offset, reverseMapVec4Offset, triangleBuffer,
|
|
976
1003
|
) );
|
|
977
1004
|
|
|
978
1005
|
} ).Else( () => {
|
|
979
1006
|
|
|
980
1007
|
lightPdf.assign( calculateEmissiveLightPdf(
|
|
981
|
-
int( hitTriIdx ),
|
|
1008
|
+
int( hitTriIdx ), misDist, direction, misOrigin,
|
|
982
1009
|
triangleBuffer, materialBuffer, emissiveTotalPower,
|
|
983
1010
|
) );
|
|
984
1011
|
|
|
@@ -995,7 +1022,7 @@ export function buildShadeKernel( params ) {
|
|
|
995
1022
|
currentRadiance.xyz.add(
|
|
996
1023
|
regularizePathContribution(
|
|
997
1024
|
emissive.mul( throughput ).mul( emissiveGiScale ).mul( emissiveMISWeight ),
|
|
998
|
-
float( bounceIndex ), fireflyThreshold, int(
|
|
1025
|
+
float( bounceIndex ), fireflyThreshold, int( accumFrame ),
|
|
999
1026
|
),
|
|
1000
1027
|
),
|
|
1001
1028
|
currentRadiance.w
|
|
@@ -1047,32 +1074,24 @@ export function buildShadeKernel( params ) {
|
|
|
1047
1074
|
material.clearcoat, material.emissive, material.subsurface,
|
|
1048
1075
|
) ).toVar();
|
|
1049
1076
|
|
|
1050
|
-
// STBN keyed on (GLOBAL pixel, bounceIndex, frame). pixelIndex is the LOCAL path slot; the global pixel
|
|
1051
|
-
// = chunkRowBase·W + localSlot, so the blue-noise pattern stays spatially aligned across row-band chunks
|
|
1052
|
-
// (and matches Generate's per-pixel RNG seed). chunkRowBase is 0 in the single-chunk case.
|
|
1053
|
-
const _resX = int( resolution.x ).toVar();
|
|
1054
|
-
const _globalPixel = int( pixelIndex ).add( chunkRowBase.mul( _resX ) );
|
|
1055
|
-
const _pixelCoord = vec2(
|
|
1056
|
-
float( _globalPixel.mod( _resX ) ).add( 0.5 ),
|
|
1057
|
-
float( _globalPixel.div( _resX ) ).add( 0.5 ),
|
|
1058
|
-
);
|
|
1059
1077
|
const xi = getRandomSample( _pixelCoord, int( 0 ), bounceIndex, rngState, int( - 1 ), resolution, frame ).toVar();
|
|
1078
|
+
const lobeXi = getRandomSample1D( _pixelCoord, int( 0 ), dimBase.add( int( 2 ) ), rngState, resolution, frame ).toVar();
|
|
1060
1079
|
const emptyWeights = BRDFWeights( {
|
|
1061
1080
|
specular: float( 0.0 ), diffuse: float( 0.0 ), sheen: float( 0.0 ),
|
|
1062
1081
|
clearcoat: float( 0.0 ), transmission: float( 0.0 ), iridescence: float( 0.0 ),
|
|
1063
1082
|
} );
|
|
1064
|
-
|
|
1083
|
+
|
|
1084
|
+
// Unused (weightsComputed / materialCacheCached are false) but must satisfy the signature.
|
|
1065
1085
|
const emptyCache = MaterialCache( {
|
|
1066
|
-
|
|
1067
|
-
diffuseColor: vec3( 0.0 ), isPurelyDiffuse: false,
|
|
1068
|
-
alpha: float( 0.0 ), k: float( 0.0 ), alpha2: float( 0.0 ),
|
|
1069
|
-
invRoughness: float( 0.5 ), metalFactor: float( 0.5 ),
|
|
1086
|
+
invRoughness: float( 0.0 ), metalFactor: float( 0.5 ),
|
|
1070
1087
|
iorFactor: float( 0.67 ), maxSheenColor: float( 0.0 ),
|
|
1071
1088
|
} );
|
|
1072
1089
|
|
|
1073
1090
|
const brdfDir = vec3( 0.0 ).toVar();
|
|
1074
1091
|
const brdfValue = vec3( 0.0 ).toVar();
|
|
1075
1092
|
const brdfPdf = float( 0.0 ).toVar();
|
|
1093
|
+
const brdfIsTransmission = tslBool( false ).toVar();
|
|
1094
|
+
const brdfColorWeight = vec3( 1.0 ).toVar();
|
|
1076
1095
|
|
|
1077
1096
|
If( material.clearcoat.greaterThan( 0.0 ), () => {
|
|
1078
1097
|
|
|
@@ -1084,6 +1103,7 @@ export function buildShadeKernel( params ) {
|
|
|
1084
1103
|
} );
|
|
1085
1104
|
const ccResult = ClearcoatResult.wrap( sampleClearcoat(
|
|
1086
1105
|
ccRay, ccHit, material, xi, rngState,
|
|
1106
|
+
_pixelCoord, resolution, frame, dimBase,
|
|
1087
1107
|
) );
|
|
1088
1108
|
brdfDir.assign( ccResult.L );
|
|
1089
1109
|
brdfValue.assign( ccResult.brdf );
|
|
@@ -1092,7 +1112,8 @@ export function buildShadeKernel( params ) {
|
|
|
1092
1112
|
} ).Else( () => {
|
|
1093
1113
|
|
|
1094
1114
|
const bs = DirectionSample.wrap( generateSampledDirection(
|
|
1095
|
-
V, N, material, xi, rngState,
|
|
1115
|
+
V, N, material, xi, lobeXi, rngState,
|
|
1116
|
+
_pixelCoord, resolution, frame, dimBase,
|
|
1096
1117
|
mc,
|
|
1097
1118
|
false, emptyWeights,
|
|
1098
1119
|
false, emptyCache,
|
|
@@ -1100,6 +1121,8 @@ export function buildShadeKernel( params ) {
|
|
|
1100
1121
|
brdfDir.assign( bs.direction );
|
|
1101
1122
|
brdfValue.assign( bs.value );
|
|
1102
1123
|
brdfPdf.assign( bs.pdf );
|
|
1124
|
+
brdfIsTransmission.assign( bs.isTransmission );
|
|
1125
|
+
brdfColorWeight.assign( bs.colorWeight );
|
|
1103
1126
|
|
|
1104
1127
|
} );
|
|
1105
1128
|
|
|
@@ -1107,6 +1130,7 @@ export function buildShadeKernel( params ) {
|
|
|
1107
1130
|
hitPoint, N, material, V,
|
|
1108
1131
|
brdfDir, brdfPdf, brdfValue,
|
|
1109
1132
|
bounceIndex, rngState,
|
|
1133
|
+
_pixelCoord, resolution, frame, dimBase,
|
|
1110
1134
|
directionalLightsBuffer, numDirectionalLights,
|
|
1111
1135
|
areaLightsBuffer, numAreaLights,
|
|
1112
1136
|
pointLightsBuffer, numPointLights,
|
|
@@ -1127,7 +1151,7 @@ export function buildShadeKernel( params ) {
|
|
|
1127
1151
|
currentRadiance.xyz.add(
|
|
1128
1152
|
regularizePathContribution(
|
|
1129
1153
|
throughput.mul( directLight ).mul( giScale ),
|
|
1130
|
-
float( bounceIndex ), fireflyThreshold, int(
|
|
1154
|
+
float( bounceIndex ), fireflyThreshold, int( accumFrame ),
|
|
1131
1155
|
),
|
|
1132
1156
|
),
|
|
1133
1157
|
currentRadiance.w
|
|
@@ -1156,18 +1180,16 @@ export function buildShadeKernel( params ) {
|
|
|
1156
1180
|
const emissiveSample = EmissiveSample.wrap( sampleLightBVHTriangle(
|
|
1157
1181
|
hitPoint, N,
|
|
1158
1182
|
rngState,
|
|
1183
|
+
_pixelCoord, resolution, frame, dimBase,
|
|
1159
1184
|
lightBuffer,
|
|
1160
1185
|
lightBuffer,
|
|
1161
1186
|
emissiveVec4Offset,
|
|
1162
1187
|
triangleBuffer,
|
|
1163
1188
|
) );
|
|
1164
1189
|
|
|
1165
|
-
//
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
.and( material.metalness.lessThan( 0.1 ) );
|
|
1169
|
-
|
|
1170
|
-
If( skip.not().and( emissiveSample.valid ).and( emissiveSample.pdf.greaterThan( 0.0 ) ), () => {
|
|
1190
|
+
// No rough-diffuse secondary-bounce skip here: dropping NEE while the emissive-hit
|
|
1191
|
+
// MIS still down-weights BSDF hits deletes emitter energy from deep GI.
|
|
1192
|
+
If( emissiveSample.valid.and( emissiveSample.pdf.greaterThan( 0.0 ) ), () => {
|
|
1171
1193
|
|
|
1172
1194
|
const NoL = max( float( 0.0 ), dot( N, emissiveSample.direction ) );
|
|
1173
1195
|
|
|
@@ -1199,7 +1221,7 @@ export function buildShadeKernel( params ) {
|
|
|
1199
1221
|
currentRadiance.xyz.add(
|
|
1200
1222
|
regularizePathContribution(
|
|
1201
1223
|
emissiveLight.mul( throughput ).mul( giScale ),
|
|
1202
|
-
float( bounceIndex ), fireflyThreshold, int(
|
|
1224
|
+
float( bounceIndex ), fireflyThreshold, int( accumFrame ),
|
|
1203
1225
|
),
|
|
1204
1226
|
),
|
|
1205
1227
|
currentRadiance.w,
|
|
@@ -1216,6 +1238,7 @@ export function buildShadeKernel( params ) {
|
|
|
1216
1238
|
const emissiveLight = calculateEmissiveTriangleContribution(
|
|
1217
1239
|
hitPoint, N, V, material,
|
|
1218
1240
|
bounceIndex, rngState,
|
|
1241
|
+
_pixelCoord, resolution, frame, dimBase,
|
|
1219
1242
|
emissiveBoost,
|
|
1220
1243
|
lightBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
1221
1244
|
triangleBuffer,
|
|
@@ -1227,7 +1250,7 @@ export function buildShadeKernel( params ) {
|
|
|
1227
1250
|
currentRadiance.xyz.add(
|
|
1228
1251
|
regularizePathContribution(
|
|
1229
1252
|
emissiveLight.mul( throughput ).mul( giScale ),
|
|
1230
|
-
float( bounceIndex ), fireflyThreshold, int(
|
|
1253
|
+
float( bounceIndex ), fireflyThreshold, int( accumFrame ),
|
|
1231
1254
|
),
|
|
1232
1255
|
),
|
|
1233
1256
|
currentRadiance.w,
|
|
@@ -1244,17 +1267,26 @@ export function buildShadeKernel( params ) {
|
|
|
1244
1267
|
// per-term (env / emissive-hit / direct-light / emissive-NEE), matching the megakernel which never
|
|
1245
1268
|
// re-suppresses the running radiance.
|
|
1246
1269
|
|
|
1247
|
-
const samplingInfo = ImportanceSamplingInfo.wrap( getImportanceSamplingInfo(
|
|
1248
|
-
material, bounceIndex, mc,
|
|
1249
|
-
) ).toVar();
|
|
1250
|
-
|
|
1251
1270
|
const indirectResult = IndirectLightingResult.wrap( calculateIndirectLighting(
|
|
1252
|
-
|
|
1271
|
+
N, material,
|
|
1253
1272
|
brdfDir, brdfPdf, brdfValue,
|
|
1254
|
-
|
|
1273
|
+
brdfIsTransmission, brdfColorWeight,
|
|
1274
|
+
rngState,
|
|
1275
|
+
_pixelCoord, resolution, frame, dimBase,
|
|
1255
1276
|
) ).toVar();
|
|
1256
1277
|
|
|
1257
1278
|
const bounceDir = indirectResult.direction.toVar();
|
|
1279
|
+
|
|
1280
|
+
// Shading-normal leak guard: a normal-mapped lobe can sample below the geometric surface;
|
|
1281
|
+
// tracing that ray tunnels through single-sided shells onto whatever sits behind them.
|
|
1282
|
+
const NgeoFF = select( dot( hitNormal, V ).lessThan( 0.0 ), hitNormal.negate(), hitNormal );
|
|
1283
|
+
If( brdfIsTransmission.not().and( dot( bounceDir, NgeoFF ).lessThanEqual( 0.0 ) ), () => {
|
|
1284
|
+
|
|
1285
|
+
commitDeferredAux( N );
|
|
1286
|
+
terminatePath();
|
|
1287
|
+
Return();
|
|
1288
|
+
|
|
1289
|
+
} );
|
|
1258
1290
|
// combinedPdf is stored as next bounce's prevBouncePdf for NEE↔implicit-env MIS
|
|
1259
1291
|
const bouncePdf = max( indirectResult.combinedPdf, 0.001 ).toVar();
|
|
1260
1292
|
throughput.mulAssign( indirectResult.throughput );
|
|
@@ -1265,7 +1297,8 @@ export function buildShadeKernel( params ) {
|
|
|
1265
1297
|
// terminate. Subsumes the old compensated low-throughput kill (#12). Unbiased; just terminates smarter.
|
|
1266
1298
|
const rrSurvival = handleRussianRoulette(
|
|
1267
1299
|
bounceIndex, throughput, mc, bounceDir, rngState,
|
|
1268
|
-
|
|
1300
|
+
_pixelCoord, resolution, frame, dimBase,
|
|
1301
|
+
enableEnvironmentLight,
|
|
1269
1302
|
).toVar();
|
|
1270
1303
|
If( rrSurvival.lessThanEqual( 0.0 ), () => {
|
|
1271
1304
|
|
package/src/TSL/Struct.js
CHANGED
|
@@ -151,6 +151,11 @@ export const DirectionSample = struct( {
|
|
|
151
151
|
direction: 'vec3',
|
|
152
152
|
value: 'vec3',
|
|
153
153
|
pdf: 'float',
|
|
154
|
+
// Refraction went below the surface, so `value` (a reflection-only BRDF eval) is not usable
|
|
155
|
+
// and the consumer must take the transmission throughput path instead.
|
|
156
|
+
isTransmission: 'bool',
|
|
157
|
+
// Spectral tint from the transmission sampler; vec3(1) for every reflection lobe.
|
|
158
|
+
colorWeight: 'vec3',
|
|
154
159
|
} );
|
|
155
160
|
|
|
156
161
|
export const BRDFWeights = struct( {
|
|
@@ -162,12 +167,6 @@ export const BRDFWeights = struct( {
|
|
|
162
167
|
iridescence: 'float',
|
|
163
168
|
} );
|
|
164
169
|
|
|
165
|
-
export const ImportanceSamplingInfo = struct( {
|
|
166
|
-
diffuseImportance: 'float',
|
|
167
|
-
specularImportance: 'float',
|
|
168
|
-
transmissionImportance: 'float',
|
|
169
|
-
clearcoatImportance: 'float',
|
|
170
|
-
} );
|
|
171
170
|
|
|
172
171
|
// Anisotropy tangent frame in world space (rotated ONB). Shared by the sampler and
|
|
173
172
|
// the eval/PDF so both derive the identical frame — see anisoTangentFrame().
|
|
@@ -234,45 +233,14 @@ export const UVCache = struct( {
|
|
|
234
233
|
// Fields are split into two groups:
|
|
235
234
|
// 1. BRDF evaluation: F0, NoV, diffuseColor, isPurelyDiffuse, alpha, k, alpha2
|
|
236
235
|
// 2. BRDF weight calc: invRoughness, metalFactor, iorFactor, maxSheenColor
|
|
236
|
+
// Shared inputs to calculateBRDFWeights, which is the only reader.
|
|
237
237
|
export const MaterialCache = struct( {
|
|
238
|
-
F0: 'vec3', // Base reflectance
|
|
239
|
-
NoV: 'float', // Normal dot View
|
|
240
|
-
diffuseColor: 'vec3', // Precomputed diffuse color (only for isPurelyDiffuse fast path)
|
|
241
|
-
isPurelyDiffuse: 'bool', // Optimized path flag
|
|
242
|
-
alpha: 'float', // roughness squared
|
|
243
|
-
k: 'float', // Geometry term constant
|
|
244
|
-
alpha2: 'float', // roughness to the fourth power
|
|
245
|
-
// BRDF weight calculation: precomputed shared values
|
|
246
238
|
invRoughness: 'float', // 1.0 - roughness
|
|
247
239
|
metalFactor: 'float', // 0.5 + 0.5 * metalness
|
|
248
240
|
iorFactor: 'float', // min(2.0 / ior, 1.0)
|
|
249
241
|
maxSheenColor: 'float', // max component of sheen color
|
|
250
242
|
} );
|
|
251
243
|
|
|
252
|
-
export const SamplingStrategyWeights = struct( {
|
|
253
|
-
envWeight: 'float',
|
|
254
|
-
specularWeight: 'float',
|
|
255
|
-
diffuseWeight: 'float',
|
|
256
|
-
transmissionWeight: 'float',
|
|
257
|
-
clearcoatWeight: 'float',
|
|
258
|
-
totalWeight: 'float',
|
|
259
|
-
|
|
260
|
-
useEnv: 'bool',
|
|
261
|
-
useSpecular: 'bool',
|
|
262
|
-
useDiffuse: 'bool',
|
|
263
|
-
useTransmission: 'bool',
|
|
264
|
-
useClearcoat: 'bool',
|
|
265
|
-
} );
|
|
266
|
-
|
|
267
|
-
// IMPROVEMENT: Dynamic MIS strategy based on material properties
|
|
268
|
-
export const MISStrategy = struct( {
|
|
269
|
-
brdfWeight: 'float',
|
|
270
|
-
lightWeight: 'float',
|
|
271
|
-
envWeight: 'float',
|
|
272
|
-
useBRDFSampling: 'bool',
|
|
273
|
-
useLightSampling: 'bool',
|
|
274
|
-
useEnvSampling: 'bool',
|
|
275
|
-
} );
|
|
276
244
|
|
|
277
245
|
// General rendering state (used across all rendering paths)
|
|
278
246
|
export const RenderState = struct( {
|
package/src/TSL/Subsurface.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import {
|
|
10
10
|
Fn,
|
|
11
11
|
float,
|
|
12
|
-
|
|
12
|
+
int,
|
|
13
13
|
vec3,
|
|
14
14
|
If,
|
|
15
15
|
select,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
|
|
30
30
|
import { struct } from './patches.js';
|
|
31
31
|
import { TWO_PI, EPSILON, constructTBN } from './Common.js';
|
|
32
|
-
import {
|
|
32
|
+
import { getRandomSample1D, getRandomSample2D } from './Random.js';
|
|
33
33
|
import { iorToFresnel0, fresnelSchlickFloat } from './Fresnel.js';
|
|
34
34
|
import { ImportanceSampleGGX } from './MaterialSampling.js';
|
|
35
35
|
|
|
@@ -95,13 +95,16 @@ export const sampleHenyeyGreenstein = Fn( ( [ wi, g, xi ] ) => {
|
|
|
95
95
|
// Per-channel sigma_t can't be represented by one scalar distance. Pick a channel
|
|
96
96
|
// ∝ throughput, sample t against it, and weight by the balance-heuristic combined pdf
|
|
97
97
|
// p̄ = Σ pmf_c·p_c — the shared scalar p̄ is what suppresses color fireflies.
|
|
98
|
-
export const sampleChromaticCollision = Fn( ( [
|
|
98
|
+
export const sampleChromaticCollision = Fn( ( [
|
|
99
|
+
sigmaT, sigmaS, beta, surfaceDist, rngState,
|
|
100
|
+
pixelCoord, resolution, frame, dimBase,
|
|
101
|
+
] ) => {
|
|
99
102
|
|
|
100
103
|
const w = max( beta, vec3( 1e-4 ) ); // floor so no channel goes unsampled
|
|
101
104
|
const pmf = w.div( w.x.add( w.y ).add( w.z ) ).toVar();
|
|
102
105
|
|
|
103
106
|
// .toVar() pins the single RNG draw (else it re-executes per comparison → state drift).
|
|
104
|
-
const u =
|
|
107
|
+
const u = getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 10 ) ), rngState, resolution, frame ).toVar();
|
|
105
108
|
const cSigmaT = float( 0.0 ).toVar();
|
|
106
109
|
If( u.lessThan( pmf.x ), () => {
|
|
107
110
|
|
|
@@ -117,7 +120,7 @@ export const sampleChromaticCollision = Fn( ( [ sigmaT, sigmaS, beta, surfaceDis
|
|
|
117
120
|
|
|
118
121
|
} );
|
|
119
122
|
|
|
120
|
-
const xi =
|
|
123
|
+
const xi = getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 9 ) ), rngState, resolution, frame ).toVar();
|
|
121
124
|
const t = log( max( float( 1.0 ).sub( xi ), 1e-6 ) ).negate().div( max( cSigmaT, 1e-6 ) ).toVar();
|
|
122
125
|
|
|
123
126
|
const didScatter = t.lessThan( surfaceDist ).toVar();
|
|
@@ -167,7 +170,9 @@ export const subsurfaceCoefficients = Fn( ( [ subsurfaceColor, subsurfaceRadius,
|
|
|
167
170
|
// Dielectric interface driven by material.ior: reflect (Fresnel/TIR) or refract across
|
|
168
171
|
// the boundary. No color tint — the scattering color lives in sigma_s.
|
|
169
172
|
export const handleSubsurfaceEntry = Fn( ( [
|
|
170
|
-
rayDir, normal, material, entering, rngState,
|
|
173
|
+
rayDir, normal, material, entering, rngState,
|
|
174
|
+
pixelCoord, resolution, frame, dimBase,
|
|
175
|
+
currentMediumIOR, previousMediumIOR,
|
|
171
176
|
] ) => {
|
|
172
177
|
|
|
173
178
|
const result = SubsurfaceEntryResult( {
|
|
@@ -188,13 +193,13 @@ export const handleSubsurfaceEntry = Fn( ( [
|
|
|
188
193
|
const Fr = select( tir, float( 1.0 ), fresnelSchlickFloat( cosThetaI, F0 ) ).toVar();
|
|
189
194
|
const reflectProb = clamp( Fr, 0.02, 0.98 ).toVar();
|
|
190
195
|
|
|
191
|
-
const doReflect = tir.or(
|
|
196
|
+
const doReflect = tir.or( getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 11 ) ), rngState, resolution, frame ).lessThan( reflectProb ) ).toVar();
|
|
192
197
|
result.didReflect.assign( doReflect );
|
|
193
198
|
|
|
194
199
|
If( doReflect, () => {
|
|
195
200
|
|
|
196
201
|
// GGX-sampled reflection: a perfect mirror here makes SSS surfaces read as polished ceramic.
|
|
197
|
-
const xiR =
|
|
202
|
+
const xiR = getRandomSample2D( pixelCoord, int( 0 ), dimBase.add( int( 5 ) ), rngState, resolution, frame );
|
|
198
203
|
const H = ImportanceSampleGGX( { N, roughness: material.roughness, Xi: xiR } );
|
|
199
204
|
const reflDir = reflect( rayDir, H ).toVar();
|
|
200
205
|
If( dot( reflDir, N ).lessThanEqual( 0.0 ), () => {
|
package/src/index.js
CHANGED
|
@@ -54,6 +54,11 @@ export { VRAMTracker, bufferBytes, textureBytes } from './Processor/VRAMTracker.
|
|
|
54
54
|
export { RenderPipeline } from './Pipeline/RenderPipeline.js';
|
|
55
55
|
export { RenderStage, StageExecutionMode } from './Pipeline/RenderStage.js';
|
|
56
56
|
export { PipelineContext } from './Pipeline/PipelineContext.js';
|
|
57
|
+
export {
|
|
58
|
+
setBindingAudit,
|
|
59
|
+
getBindingAuditFindings,
|
|
60
|
+
clearBindingAuditFindings,
|
|
61
|
+
} from './Pipeline/BindingAudit.js';
|
|
57
62
|
|
|
58
63
|
// Animation (for advanced consumers)
|
|
59
64
|
export { AnimationManager } from './managers/AnimationManager.js';
|
|
@@ -274,7 +274,6 @@ export class EnvironmentManager {
|
|
|
274
274
|
this._updateCDFTexture();
|
|
275
275
|
this.uniforms.set( 'envTotalSum', 0.0 );
|
|
276
276
|
this.uniforms.set( 'envCompensationDelta', 0.0 );
|
|
277
|
-
this.uniforms.set( 'useEnvMapIS', 0 );
|
|
278
277
|
return;
|
|
279
278
|
|
|
280
279
|
}
|
|
@@ -289,7 +288,6 @@ export class EnvironmentManager {
|
|
|
289
288
|
this._updateCDFTexture();
|
|
290
289
|
this.uniforms.set( 'envTotalSum', 0.0 );
|
|
291
290
|
this.uniforms.set( 'envCompensationDelta', 0.0 );
|
|
292
|
-
this.uniforms.set( 'useEnvMapIS', 0 );
|
|
293
291
|
return;
|
|
294
292
|
|
|
295
293
|
}
|
|
@@ -309,7 +307,6 @@ export class EnvironmentManager {
|
|
|
309
307
|
this._updateCDFTexture();
|
|
310
308
|
this.uniforms.set( 'envTotalSum', this.equirectHdrInfo.totalSum );
|
|
311
309
|
this.uniforms.set( 'envCompensationDelta', this.equirectHdrInfo.compensationDelta );
|
|
312
|
-
this.uniforms.set( 'useEnvMapIS', 1 );
|
|
313
310
|
|
|
314
311
|
const { width, height } = this.equirectHdrInfo;
|
|
315
312
|
if ( width && height ) {
|
|
@@ -326,7 +323,6 @@ export class EnvironmentManager {
|
|
|
326
323
|
} catch ( error ) {
|
|
327
324
|
|
|
328
325
|
log.error( 'CDF build failed:', error );
|
|
329
|
-
this.uniforms.set( 'useEnvMapIS', 0 );
|
|
330
326
|
this.uniforms.set( 'envTotalSum', 0.0 );
|
|
331
327
|
this.uniforms.set( 'envCompensationDelta', 0.0 );
|
|
332
328
|
|
|
@@ -387,7 +383,6 @@ export class EnvironmentManager {
|
|
|
387
383
|
this._updateCDFTexture();
|
|
388
384
|
this.uniforms.set( 'envTotalSum', 0.0 );
|
|
389
385
|
this.uniforms.set( 'envCompensationDelta', 0.0 );
|
|
390
|
-
this.uniforms.set( 'useEnvMapIS', 0 );
|
|
391
386
|
|
|
392
387
|
}
|
|
393
388
|
|
|
@@ -160,6 +160,10 @@ export class UniformManager {
|
|
|
160
160
|
|
|
161
161
|
// Frame and sampling
|
|
162
162
|
u( 'frame', 0, 'uint' );
|
|
163
|
+
// Sampler seed axis, separate from the accumulation index: `frame` is zeroed by every
|
|
164
|
+
// reset, and a camera move resets each frame of a drag, so the RNG redrew the identical
|
|
165
|
+
// sequence and temporal denoising had nothing to average.
|
|
166
|
+
u( 'seedFrame', 0, 'uint' );
|
|
163
167
|
u( 'maxBounces', DEFAULT_STATE.bounces, 'int' );
|
|
164
168
|
u( 'maxSamples', DEFAULT_STATE.maxSamples, 'int' );
|
|
165
169
|
u( 'transmissiveBounces', DEFAULT_STATE.transmissiveBounces, 'int' );
|
|
@@ -198,7 +202,6 @@ export class UniformManager {
|
|
|
198
202
|
ub( 'transparentBackground', DEFAULT_STATE.transparentBackground );
|
|
199
203
|
ub( 'enableEnvironment', DEFAULT_STATE.enableEnvironment );
|
|
200
204
|
u( 'environmentMatrix', new Matrix4(), 'mat4' );
|
|
201
|
-
ub( 'useEnvMapIS', DEFAULT_STATE.useImportanceSampledEnvironment );
|
|
202
205
|
u( 'envTotalSum', 0.0, 'float' );
|
|
203
206
|
u( 'envCompensationDelta', 0.0, 'float' );
|
|
204
207
|
u( 'envResolution', new Vector2( 1, 1 ), 'vec2' );
|