rayzee 6.4.0 → 7.0.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.
Files changed (58) hide show
  1. package/README.md +24 -5
  2. package/dist/rayzee.es.js +4953 -4225
  3. package/dist/rayzee.es.js.map +1 -1
  4. package/dist/rayzee.umd.js +157 -236
  5. package/dist/rayzee.umd.js.map +1 -1
  6. package/package.json +1 -1
  7. package/src/EngineDefaults.js +29 -13
  8. package/src/PathTracerApp.js +119 -26
  9. package/src/Pipeline/PipelineContext.js +1 -2
  10. package/src/Pipeline/RenderPipeline.js +1 -1
  11. package/src/Pipeline/RenderStage.js +1 -1
  12. package/src/Processor/CameraOptimizer.js +0 -5
  13. package/src/Processor/GeometryExtractor.js +22 -1
  14. package/src/Processor/KernelManager.js +277 -0
  15. package/src/Processor/PackedRayBuffer.js +265 -0
  16. package/src/Processor/QueueManager.js +173 -0
  17. package/src/Processor/SceneProcessor.js +1 -0
  18. package/src/Processor/ShaderBuilder.js +11 -316
  19. package/src/Processor/StorageTexturePool.js +29 -15
  20. package/src/Processor/TextureCreator.js +6 -0
  21. package/src/Processor/VRAMTracker.js +169 -0
  22. package/src/Processor/utils.js +11 -110
  23. package/src/RenderSettings.js +1 -3
  24. package/src/Stages/ASVGF.js +76 -20
  25. package/src/Stages/BilateralFilter.js +34 -10
  26. package/src/Stages/EdgeFilter.js +2 -3
  27. package/src/Stages/MotionVector.js +16 -9
  28. package/src/Stages/NormalDepth.js +17 -5
  29. package/src/Stages/PathTracer.js +671 -1456
  30. package/src/Stages/PathTracerStage.js +1451 -0
  31. package/src/Stages/SSRC.js +32 -15
  32. package/src/Stages/Variance.js +35 -12
  33. package/src/TSL/BVHTraversal.js +7 -1
  34. package/src/TSL/Common.js +12 -2
  35. package/src/TSL/CompactKernel.js +110 -0
  36. package/src/TSL/DebugKernel.js +98 -0
  37. package/src/TSL/Environment.js +13 -11
  38. package/src/TSL/ExtendKernel.js +75 -0
  39. package/src/TSL/FinalWriteKernel.js +121 -0
  40. package/src/TSL/GenerateKernel.js +109 -0
  41. package/src/TSL/LightsSampling.js +2 -2
  42. package/src/TSL/MaterialTransmission.js +32 -2
  43. package/src/TSL/PathTracerCore.js +43 -912
  44. package/src/TSL/ShadeKernel.js +873 -0
  45. package/src/TSL/Struct.js +5 -0
  46. package/src/TSL/Subsurface.js +232 -0
  47. package/src/TSL/patches.js +81 -4
  48. package/src/index.js +3 -0
  49. package/src/managers/CameraManager.js +1 -1
  50. package/src/managers/DenoisingManager.js +40 -75
  51. package/src/managers/EnvironmentManager.js +30 -39
  52. package/src/managers/MaterialDataManager.js +60 -1
  53. package/src/managers/OverlayManager.js +7 -22
  54. package/src/managers/UniformManager.js +1 -3
  55. package/src/managers/helpers/TileHelper.js +2 -2
  56. package/src/Stages/AdaptiveSampling.js +0 -483
  57. package/src/TSL/PathTracer.js +0 -384
  58. package/src/managers/TileManager.js +0 -298
@@ -0,0 +1,121 @@
1
+ /**
2
+ * FinalWriteKernel.js — wavefront final output: temporal accumulation + MRT StorageTexture writes (16×16, 2D).
3
+ */
4
+
5
+ import {
6
+ Fn, wgslFn, float, vec2, vec4, int, uint, uvec2,
7
+ If, mix, select, texture, textureStore,
8
+ localId, workgroupId,
9
+ } from 'three/tsl';
10
+
11
+ import {
12
+ readRayRadiance, readGBuffer, gbDecodeNormalDepth, gbDecodeAlbedo,
13
+ } from '../Processor/PackedRayBuffer.js';
14
+
15
+ const WG_SIZE = 16;
16
+
17
+ // Debug mode 11: NaN/Inf detector — red where the accumulated color is NaN/Inf, black elsewhere.
18
+ const nanInfToRed = /*@__PURE__*/ wgslFn( `
19
+ fn nanInfToRed( c: vec3f ) -> vec3f {
20
+ let isNan = c.x != c.x || c.y != c.y || c.z != c.z;
21
+ let isInf = abs( c.x ) > 1e30f || abs( c.y ) > 1e30f || abs( c.z ) > 1e30f;
22
+ if ( isNan || isInf ) { return vec3f( 1.0f, 0.0f, 0.0f ); }
23
+ return vec3f( 0.0f );
24
+ }
25
+ ` );
26
+
27
+ export function buildFinalWriteKernel( params ) {
28
+
29
+ const {
30
+ rayBufferRO, gBufferRO,
31
+ writeColorTex, writeNDTex, writeAlbedoTex,
32
+ resolution, frame,
33
+ enableAccumulation, hasPreviousAccumulated, accumulationAlpha, cameraIsMoving,
34
+ transparentBackground,
35
+ prevAccumTexture, prevNormalDepthTexture, prevAlbedoTexture,
36
+ renderWidth, renderHeight,
37
+ // Multi-sample: average S sample-slots per pixel (slot = pixel + k*w*h, w*h from the resolution uniform).
38
+ samplesPerPass = 1,
39
+ visMode,
40
+ } = params;
41
+
42
+ const S = samplesPerPass | 0;
43
+
44
+ const computeFn = Fn( () => {
45
+
46
+ const gx = int( workgroupId.x ).mul( WG_SIZE ).add( int( localId.x ) );
47
+ const gy = int( workgroupId.y ).mul( WG_SIZE ).add( int( localId.y ) );
48
+
49
+ If( gx.lessThan( renderWidth ).and( gy.lessThan( renderHeight ) ), () => {
50
+
51
+ const pixelIndex = gy.mul( int( resolution.x ) ).add( gx );
52
+ const rayID = uint( pixelIndex );
53
+
54
+ // Average the S sub-samples; MRT (normal/depth/albedo) from sub-sample 0.
55
+ const sampleColor = ( () => {
56
+
57
+ if ( S <= 1 ) return readRayRadiance( rayBufferRO, rayID );
58
+ const acc = readRayRadiance( rayBufferRO, rayID ).toVar();
59
+ const mrps = uint( resolution.x ).mul( uint( resolution.y ) ).toVar(); // w*h from the resolution uniform, not baked
60
+ for ( let k = 1; k < S; k ++ ) {
61
+
62
+ acc.addAssign( readRayRadiance( rayBufferRO, rayID.add( uint( k ).mul( mrps ) ) ) );
63
+
64
+ }
65
+
66
+ acc.assign( acc.div( float( S ) ) );
67
+ return acc;
68
+
69
+ } )();
70
+ // MRT comes from the per-pixel G-buffer (rayID == pixelIndex here, i.e. sub-sample 0). Half-packed: decode.
71
+ const gbuf = readGBuffer( gBufferRO, rayID );
72
+ const normalDepth = gbDecodeNormalDepth( gbuf );
73
+ const albedoID = vec4( gbDecodeAlbedo( gbuf ), 0.0 );
74
+
75
+ const finalColor = sampleColor.xyz.toVar();
76
+ const finalNormalDepth = normalDepth.toVar();
77
+ const finalAlbedo = albedoID.xyz.toVar();
78
+ const outputAlpha = select( transparentBackground, sampleColor.w, float( 1.0 ) ).toVar();
79
+
80
+ const pixelCoord = vec2( float( gx ).add( 0.5 ), float( gy ).add( 0.5 ) );
81
+ const prevUV = pixelCoord.div( resolution );
82
+
83
+ // visMode 11 (NaN/Inf) bypasses accumulation (megakernel parity main_TSL_PathTracer.js:355) so the
84
+ // detector runs on each frame's fresh color — else mix() propagates a transient NaN and it stays red forever.
85
+ If( enableAccumulation.and( cameraIsMoving.not() ).and( frame.greaterThan( uint( 0 ) ) ).and( hasPreviousAccumulated ).and( visMode.notEqual( int( 11 ) ) ), () => {
86
+
87
+ const prevAccumSample = texture( prevAccumTexture, prevUV, 0 ).toVar();
88
+
89
+ finalColor.assign( mix( prevAccumSample.xyz, sampleColor.xyz, accumulationAlpha ) );
90
+ finalNormalDepth.assign( mix( texture( prevNormalDepthTexture, prevUV, 0 ), finalNormalDepth, accumulationAlpha ) );
91
+ finalAlbedo.assign( mix( texture( prevAlbedoTexture, prevUV, 0 ).xyz, finalAlbedo, accumulationAlpha ) );
92
+
93
+ If( transparentBackground, () => {
94
+
95
+ outputAlpha.assign( mix( prevAccumSample.w, sampleColor.w, accumulationAlpha ) );
96
+
97
+ } );
98
+
99
+ } );
100
+
101
+ // Debug mode 11: flag NaN/Inf on the accumulated color (red on NaN/Inf, black elsewhere).
102
+ If( visMode.equal( int( 11 ) ), () => {
103
+
104
+ finalColor.assign( nanInfToRed( finalColor ) );
105
+
106
+ } );
107
+
108
+ const uintCoord = uvec2( uint( gx ), uint( gy ) );
109
+ textureStore( writeColorTex, uintCoord, vec4( finalColor, outputAlpha ) ).toWriteOnly();
110
+ textureStore( writeNDTex, uintCoord, finalNormalDepth ).toWriteOnly();
111
+ textureStore( writeAlbedoTex, uintCoord, vec4( finalAlbedo, 1.0 ) ).toWriteOnly();
112
+
113
+ } );
114
+
115
+ } );
116
+
117
+ return computeFn;
118
+
119
+ }
120
+
121
+ export { WG_SIZE as FINALWRITE_WG_SIZE };
@@ -0,0 +1,109 @@
1
+ /**
2
+ * GenerateKernel.js — wavefront primary ray generation (16×16, 2D screen-space dispatch).
3
+ */
4
+
5
+ import {
6
+ Fn, float, vec2, vec3, vec4, int, uint,
7
+ If, select,
8
+ localId, workgroupId,
9
+ } from 'three/tsl';
10
+
11
+ import {
12
+ getDecorrelatedSeed,
13
+ pcgHash,
14
+ getStratifiedSample,
15
+ } from './Random.js';
16
+
17
+ import { generateRayFromCamera } from './BVHTraversal.js';
18
+ import { Ray } from './Struct.js';
19
+ import { RAY_FLAG } from '../Processor/QueueManager.js';
20
+ import {
21
+ writeRayOriginMeta, writeRayDirFlags, writeRayThroughputPdf,
22
+ writeRayRadiance, writeGBuffer,
23
+ writeMediumStack,
24
+ } from '../Processor/PackedRayBuffer.js';
25
+
26
+ const WG_SIZE = 16;
27
+
28
+ export function buildGenerateKernel( params ) {
29
+
30
+ const {
31
+ rayBufferRW, rngBufferRW, gBufferRW,
32
+ resolution, frame,
33
+ cameraWorldMatrix, cameraProjectionMatrixInverse,
34
+ enableDOF, focalLength, aperture, focusDistance, sceneScale, apertureScale, anamorphicRatio,
35
+ renderWidth, renderHeight,
36
+ // Multi-sample: S primary rays/pixel/frame; S>1 dispatch covers h*S rows, ray lands in slot subSample*(w*h) + pixelIndex.
37
+ samplesPerPass = 1,
38
+ transmissiveBounces, // per-ray refraction budget (megakernel parity: PathTracerCore.js:606)
39
+ transparentBackground, // alpha inits to 1 here (megakernel parity: PathTracerCore.js:554) — env-escape-without-opaque zeroes it in Shade
40
+ } = params;
41
+
42
+ const S = samplesPerPass | 0;
43
+
44
+ const computeFn = Fn( () => {
45
+
46
+ const gx = int( workgroupId.x ).mul( WG_SIZE ).add( int( localId.x ) );
47
+ const gyRaw = int( workgroupId.y ).mul( WG_SIZE ).add( int( localId.y ) );
48
+
49
+ const subSample = S > 1 ? gyRaw.div( renderHeight ).toVar() : int( 0 );
50
+ const gy = S > 1 ? gyRaw.sub( subSample.mul( renderHeight ) ).toVar() : gyRaw;
51
+ const yBound = S > 1 ? renderHeight.mul( int( S ) ) : renderHeight;
52
+
53
+ If( gx.lessThan( renderWidth ).and( gyRaw.lessThan( yBound ) ), () => {
54
+
55
+ const pixelCoord = vec2( float( gx ).add( 0.5 ), float( gy ).add( 0.5 ) );
56
+ const pixelIndex = gy.mul( int( resolution.x ) ).add( gx );
57
+ // maxRaysPerSample = w*h, derived from the resolution uniform (NOT baked) so resize never changes the WGSL.
58
+ const rayID = S > 1
59
+ ? uint( pixelIndex ).add( uint( subSample ).mul( uint( resolution.x ).mul( uint( resolution.y ) ) ) )
60
+ : uint( pixelIndex );
61
+
62
+ const screenPosition = pixelCoord.div( resolution ).mul( 2.0 ).sub( 1.0 ).toVar();
63
+ screenPosition.y.assign( screenPosition.y.negate() );
64
+
65
+ const baseSeed = getDecorrelatedSeed( { pixelCoord, rayIndex: subSample, frame } ).toVar();
66
+ const seed = pcgHash( { state: baseSeed } ).toVar();
67
+
68
+ const stratifiedJitter = getStratifiedSample( pixelCoord, subSample, int( S ), seed, resolution, frame ).toVar();
69
+
70
+ const jitterScale = vec2( 2.0 ).div( resolution );
71
+ const jitter = stratifiedJitter.sub( 0.5 ).mul( jitterScale );
72
+ const jitteredScreenPosition = screenPosition.add( jitter );
73
+
74
+ const ray = Ray.wrap( generateRayFromCamera(
75
+ jitteredScreenPosition, seed,
76
+ cameraWorldMatrix, cameraProjectionMatrixInverse,
77
+ enableDOF, focalLength, aperture, focusDistance, sceneScale, apertureScale, anamorphicRatio,
78
+ ) );
79
+
80
+ writeRayOriginMeta( rayBufferRW, rayID, ray.origin, int( 0 ), int( 0 ) );
81
+ writeRayDirFlags( rayBufferRW, rayID, ray.direction, uint( RAY_FLAG.ACTIVE ) );
82
+ // pdf inits to 0 = prevBouncePdf (megakernel parity PathTracerCore.js:556). The bounce>0 env/emissive
83
+ // MIS gate skips until an opaque scatter writes a real combinedPdf; free bounces preserve it.
84
+ writeRayThroughputPdf( rayBufferRW, rayID, vec4( 1.0, 1.0, 1.0, 0.0 ).xyz, float( 0.0 ) );
85
+ // Alpha inits to 1 in transparent-bg mode (megakernel parity: PathTracerCore.js:554). Shade zeroes
86
+ // it only on env-escape-without-opaque; a ray that dies inside geometry (e.g. SSS walk termination)
87
+ // keeps alpha 1 → solid. Non-transparent mode is inert (FinalWrite forces alpha 1).
88
+ writeRayRadiance( rayBufferRW, rayID, vec4( vec3( 0.0 ), select( transparentBackground, float( 1.0 ), float( 0.0 ) ) ) );
89
+
90
+ If( subSample.equal( int( 0 ) ), () => {
91
+
92
+ // default: normal +Z, depth 1 (far), black albedo (background/miss)
93
+ writeGBuffer( gBufferRW, uint( pixelIndex ), vec3( 0.0, 0.0, 1.0 ), float( 1.0 ), vec3( 0.0 ) );
94
+
95
+ } );
96
+
97
+ writeMediumStack( rayBufferRW, rayID, uint( 0 ), uint( transmissiveBounces ), float( 1.0 ), float( 1.0 ), float( 1.0 ) );
98
+
99
+ rngBufferRW.element( rayID ).assign( seed );
100
+
101
+ } );
102
+
103
+ } );
104
+
105
+ return computeFn;
106
+
107
+ }
108
+
109
+ export { WG_SIZE as GENERATE_WG_SIZE };
@@ -704,7 +704,7 @@ export const calculateDirectLightingUnified = Fn( ( [
704
704
  materialBuffer,
705
705
  // Environment resources
706
706
  envTexture, environmentIntensity, envMatrix,
707
- envCDFBuffer,
707
+ envCDFTexture,
708
708
  envTotalSum, envCompensationDelta, envResolution,
709
709
  enableEnvironmentLight,
710
710
  ] ) => {
@@ -969,7 +969,7 @@ export const calculateDirectLightingUnified = Fn( ( [
969
969
 
970
970
  // Sample direction + PDF + color from importance-sampled environment
971
971
  const envSampleResult = sampleEquirectProbability(
972
- envTexture, envCDFBuffer,
972
+ envTexture, envCDFTexture,
973
973
  envMatrix, environmentIntensity, envTotalSum, envCompensationDelta, envResolution, envRandom, envColor
974
974
  ).toVar();
975
975
 
@@ -27,6 +27,7 @@ import { iorToFresnel0, fresnelSchlickFloat } from './Fresnel.js';
27
27
  import { DistributionGGX } from './MaterialProperties.js';
28
28
  import { ImportanceSampleGGX } from './MaterialSampling.js';
29
29
  import { RandomValue, pcgHash } from './Random.js';
30
+ import { handleSubsurfaceEntry, SubsurfaceEntryResult } from './Subsurface.js';
30
31
 
31
32
  // ================================================================================
32
33
  // STRUCTS (local to transmission)
@@ -43,6 +44,7 @@ export const MaterialInteractionResult = struct( {
43
44
  continueRay: 'bool', // Whether the ray should continue without further BRDF evaluation
44
45
  isTransmissive: 'bool', // Flag to indicate this was a transmissive interaction
45
46
  isAlphaSkip: 'bool', // Flag to indicate this was an alpha skip
47
+ isSubsurface: 'bool', // Flag to indicate this entered/exited a subsurface medium
46
48
  didReflect: 'bool', // Whether TIR/reflection occurred (for medium stack update)
47
49
  entering: 'bool', // Whether ray is entering or exiting medium
48
50
  direction: 'vec3', // New ray direction if continuing
@@ -493,6 +495,7 @@ export const handleMaterialTransparency = Fn( ( [
493
495
  continueRay: false,
494
496
  isTransmissive: false,
495
497
  isAlphaSkip: false,
498
+ isSubsurface: false,
496
499
  didReflect: false,
497
500
  entering: false,
498
501
  direction: ray.direction,
@@ -501,8 +504,9 @@ export const handleMaterialTransparency = Fn( ( [
501
504
  pathWavelength: pathWavelength,
502
505
  } ).toVar();
503
506
 
504
- // Fast path for fully opaque materials (most common case)
505
- If( material.alphaMode.equal( int( 0 ) ).and( material.transmission.lessThanEqual( 0.0 ) ), () => {
507
+ // Fast path for fully opaque, non-scattering materials (most common case).
508
+ // Subsurface materials are opaque (transmission==0) but must NOT take this path.
509
+ If( material.alphaMode.equal( int( 0 ) ).and( material.transmission.lessThanEqual( 0.0 ) ).and( material.subsurface.lessThanEqual( 0.0 ) ), () => {
506
510
 
507
511
  // no interaction needed
508
512
 
@@ -584,6 +588,32 @@ export const handleMaterialTransparency = Fn( ( [
584
588
 
585
589
  } );
586
590
 
591
+ // Subsurface (independent of transmission; works at transmission==0). Entry is a lottery
592
+ // (prob = weight) so 1-weight falls through to the opaque BRDF; exit is deterministic.
593
+ If( handled.not().and( material.subsurface.greaterThan( 0.0 ) ), () => {
594
+
595
+ const entering = dot( ray.direction, normal ).lessThan( 0.0 );
596
+ const doEnter = entering.not().or( RandomValue( rngState ).lessThan( material.subsurface ) );
597
+
598
+ If( doEnter, () => {
599
+
600
+ const ssResult = SubsurfaceEntryResult.wrap( handleSubsurfaceEntry(
601
+ ray.direction, normal, material, entering, rngState,
602
+ currentMediumIOR, previousMediumIOR,
603
+ ) ).toVar();
604
+
605
+ result.direction.assign( ssResult.direction );
606
+ result.throughput.assign( ssResult.throughput );
607
+ result.continueRay.assign( true );
608
+ result.isSubsurface.assign( true );
609
+ result.didReflect.assign( ssResult.didReflect );
610
+ result.entering.assign( entering );
611
+ result.alpha.assign( 1.0 );
612
+
613
+ } );
614
+
615
+ } );
616
+
587
617
  } );
588
618
 
589
619
  return result;