rayzee 8.5.0 → 8.6.1

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 (64) hide show
  1. package/README.md +60 -56
  2. package/dist/{DLSSNeural-BNSJ8V3z.js → NeuralRetouch-CYOJ21Wn.js} +33 -33
  3. package/dist/{DLSSSuperRes-BoEW89Cl.js → NeuralSuperRes-Dc6jRYke.js} +25 -25
  4. package/dist/ToneMapGPU-BVE852iN.js +533 -0
  5. package/dist/rayzee.es.js +27235 -9074
  6. package/dist/rayzee.umd.js +255 -187
  7. package/package.json +1 -1
  8. package/src/AssetConfig.js +21 -7
  9. package/src/Color/BakedViews.js +134 -0
  10. package/src/Color/BuiltinViews.js +261 -0
  11. package/src/Color/ColorManagement.js +1130 -0
  12. package/src/Color/ColorSpaces.js +432 -0
  13. package/src/Color/Displays.js +19 -0
  14. package/src/Color/InputColorSpaces.js +243 -0
  15. package/src/Color/LutBake.js +371 -0
  16. package/src/Color/OcioRuntime.js +338 -0
  17. package/src/Color/OcioViews.js +419 -0
  18. package/src/Color/ViewTransforms.js +323 -0
  19. package/src/Color/WorkingMatrix.js +86 -0
  20. package/src/EngineDefaults.js +55 -3
  21. package/src/EngineIssues.js +3 -0
  22. package/src/Passes/AIUpscaler.js +16 -7
  23. package/src/Passes/OIDNDenoiser.js +13 -5
  24. package/src/PathTracerApp.js +227 -7
  25. package/src/Processor/AssetLoader.js +1 -2
  26. package/src/Processor/EmissiveTriangleBuilder.js +34 -2
  27. package/src/Processor/GeometryExtractor.js +151 -138
  28. package/src/Processor/LightSerializer.js +8 -3
  29. package/src/Processor/MaterialPacking.js +168 -0
  30. package/src/Processor/PackedRayBuffer.js +6 -2
  31. package/src/Processor/ProceduralSky.js +4 -0
  32. package/src/Processor/SceneProcessor.js +25 -1
  33. package/src/Processor/SimpleSky.js +8 -0
  34. package/src/Processor/TextureCreator.js +137 -91
  35. package/src/Processor/TextureReadback.js +92 -0
  36. package/src/Processor/ToneMapCPU.js +80 -165
  37. package/src/Processor/ToneMapGPU.js +122 -151
  38. package/src/RenderSettings.js +1 -0
  39. package/src/Stages/Compositor.js +11 -2
  40. package/src/Stages/PathTracer.js +2 -0
  41. package/src/Stages/PathTracerStage.js +1 -1
  42. package/src/TSL/Common.js +15 -0
  43. package/src/TSL/Debugger.js +2 -3
  44. package/src/TSL/EmissiveSampling.js +6 -8
  45. package/src/TSL/ExtendKernel.js +9 -0
  46. package/src/TSL/Fresnel.js +23 -1
  47. package/src/TSL/HitFacet.js +102 -0
  48. package/src/TSL/LightsDirect.js +12 -33
  49. package/src/TSL/LightsSampling.js +15 -7
  50. package/src/TSL/MaterialEvaluation.js +26 -19
  51. package/src/TSL/MaterialProperties.js +74 -37
  52. package/src/TSL/MaterialTransmission.js +2 -3
  53. package/src/TSL/ShadeKernel.js +34 -21
  54. package/src/TSL/ShadowTerminator.js +125 -0
  55. package/src/TSL/Struct.js +9 -0
  56. package/src/TSL/Subsurface.js +2 -3
  57. package/src/index.js +32 -4
  58. package/src/managers/DenoisingManager.js +60 -44
  59. package/src/managers/EnvironmentManager.js +20 -0
  60. package/src/managers/MaterialDataManager.js +118 -208
  61. package/src/managers/UniformManager.js +1 -0
  62. package/src/{dlss/DLSSNeural.js → neural/NeuralRetouch.js} +42 -41
  63. package/src/{dlss/DLSSSuperRes.js → neural/NeuralSuperRes.js} +27 -26
  64. package/dist/ToneMapGPU-DzqSyzTx.js +0 -341
@@ -7,7 +7,7 @@
7
7
  import {
8
8
  Fn, float, vec2, vec3, vec4, int, uint,
9
9
  bool as tslBool,
10
- If, Loop, normalize, max, min, exp, log, clamp, dot, length, select, smoothstep, mix,
10
+ If, Loop, normalize, max, min, exp, log, clamp, dot, length, select, smoothstep,
11
11
  instanceIndex,
12
12
  sampler,
13
13
  atomicAdd, atomicLoad, atomicStore, uintBitsToFloat,
@@ -15,20 +15,21 @@ import {
15
15
  } from 'three/tsl';
16
16
 
17
17
  import { sampleEnvironment, sampleEquirectProbability, sampleEquirect, groundProjectedEnvDir } from './Environment.js';
18
- import { getMaterial, powerHeuristic, balanceHeuristic, classifyMaterial, REC709_LUMINANCE_COEFFICIENTS, PI_INV, EPSILON, diffuseGroundMaterial } from './Common.js';
18
+ import { getMaterial, powerHeuristic, balanceHeuristic, classifyMaterial, REC709_LUMINANCE_COEFFICIENTS, PI_INV, EPSILON, diffuseGroundMaterial, offsetRayOrigin, SHADOW_END } from './Common.js';
19
19
  import { cosineWeightedSample } from './MaterialSampling.js';
20
20
  import { sampleAllMaterialTextures, processAnisotropyMap, applyExtensionMaps, getTransformedUV, triangleUVTangent } from './TextureSampling.js';
21
21
  import { evaluateMaterialResponse } from './MaterialEvaluation.js';
22
22
  import { calculateDirectLightingUnified, calculateMaterialPDF } from './LightsSampling.js';
23
- import { traceShadowRay, calculateRayOffset } from './LightsDirect.js';
23
+ import { traceShadowRay } from './LightsDirect.js';
24
+ import { shadowTerminatorOrigin } from './ShadowTerminator.js';
25
+ import { unpackHitFacet } from './HitFacet.js';
24
26
  import { traverseBVHShadow } from './BVHTraversal.js';
25
27
  import { handleMaterialTransparency, MaterialInteractionResult } from './MaterialTransmission.js';
26
28
  import { sampleChromaticCollision, sampleHenyeyGreenstein, subsurfaceCoefficients, CollisionSample, MediumCoeffs } from './Subsurface.js';
27
29
  import { calculateIndirectLighting } from './LightsIndirect.js';
28
30
  import { IndirectLightingResult, sampleCone } from './LightsCore.js';
29
31
  import { regularizePathContribution, generateSampledDirection, computeNDCDepth, handleRussianRoulette } from './PathTracerCore.js';
30
- import { evaluateDFG } from './MaterialProperties.js';
31
- import { dielectricF0 } from './Fresnel.js';
32
+ import { evaluateSpecularDFG, baseFresnelParams } from './MaterialProperties.js';
32
33
  import { NRD_HIT_DIST_A, NRD_HIT_DIST_B } from '../EngineDefaults.js';
33
34
  import { sampleClearcoat, ClearcoatResult } from './Clearcoat.js';
34
35
  import { refineDisplacedIntersection, DisplacementResult } from './Displacement.js';
@@ -46,6 +47,7 @@ import {
46
47
  MaterialCache,
47
48
  DirectLightingDual,
48
49
  DFGResult,
50
+ BaseFresnel,
49
51
  } from './Struct.js';
50
52
  import { getRandomSample, getRandomSample1D, getRandomSample2D, SAMPLER_DIMS_PER_BOUNCE, SAMPLER_DIM_AUX_BASE } from './Random.js';
51
53
  import { RAY_FLAG, COUNTER } from '../Processor/QueueManager.js';
@@ -56,7 +58,7 @@ import {
56
58
  readTransparentCount,
57
59
  readMisRayT,
58
60
  readHitDistance, readHitBarycentrics, readHitNormal,
59
- readHitMaterialIndex, readHitTriangleIndex, readHitInstanceLeaf,
61
+ readHitMaterialIndex, readHitTriangleIndex, readHitInstanceLeaf, readHitFacet,
60
62
  writeRayOriginMeta, writeRayDirFlags, writeRayThroughputPdf, writeRayRadiance,
61
63
  writeGBuffer, writeGBufferHitDist, readGBuffer, gbDecodeNormalDepth,
62
64
  readRayRadiance,
@@ -96,6 +98,7 @@ export function buildShadeKernel( params ) {
96
98
  globalIlluminationIntensity,
97
99
  cameraProjectionMatrix, cameraViewMatrix,
98
100
  fireflyThreshold, frame, resolution,
101
+ shadowTerminatorOffset, // Cycles' Shadow Terminator → Geometry Offset; 0 disables
99
102
  // Accumulation index, NOT the free-running seed counter `frame` rides. The firefly clamp
100
103
  // relaxes as sqrt(frame+1) so its bias decays as a render converges; keying that off the
101
104
  // seed axis let it grow for the whole session and disabled suppression outright.
@@ -283,6 +286,7 @@ export function buildShadeKernel( params ) {
283
286
  envTotalSum, envCompensationDelta, envResolution,
284
287
  enableEnvironmentLight,
285
288
  tslBool( true ), // wantUnoccluded
289
+ vec3( 0.0 ), planeN, float( 0.0 ),
286
290
  ) ).toVar();
287
291
 
288
292
  const lumShad = max( dot( dual.shadowed, REC709_LUMINANCE_COEFFICIENTS ), float( 0.0 ) );
@@ -930,10 +934,9 @@ export function buildShadeKernel( params ) {
930
934
 
931
935
  throughput.mulAssign( interaction.throughput );
932
936
 
933
- // reflection stays on same side, transmission pushes through
934
- const reflectOffsetDir = select( interaction.entering, N, N.negate() );
935
- const offsetDir = select( interaction.didReflect, reflectOffsetDir, direction );
936
- const newOrigin = hitPoint.add( offsetDir.mul( 0.001 ) );
937
+ // Off the side of the facet the new ray leaves on: reflection stays, transmission and alpha skip cross.
938
+ const Ng = unpackHitFacet( readHitFacet( hitBufferRO, rayID ) ).faceN;
939
+ const newOrigin = offsetRayOrigin( hitPoint, select( dot( Ng, interaction.direction ).lessThan( 0.0 ), Ng.negate(), Ng ) );
937
940
 
938
941
  // SSS = free bounce (depth unchanged); transmission advances camera-bounce depth.
939
942
  // Transmissive / alpha-skip / SSS-boundary are all FREE bounces — they do NOT advance camera depth (megakernel parity, gap #4). cameraDepth advances only on opaque scatter (below).
@@ -1062,6 +1065,14 @@ export function buildShadeKernel( params ) {
1062
1065
  // face-forwarded geometric normal: horizon guard for NEE and the bounce continuation
1063
1066
  const Ngeo = normalize( hitNormal );
1064
1067
  const NgeoFF = select( dot( Ngeo, V ).lessThan( 0.0 ), Ngeo.negate(), Ngeo ).toVar();
1068
+ // The facet on the viewer's side: every ray spawned from here is offset off it (HitFacet.js).
1069
+ const facet = unpackHitFacet( readHitFacet( hitBufferRO, rayID ) );
1070
+ const facetN = facet.faceN.toVar();
1071
+ const terminatorLift = Ngeo.mul( facet.liftScale );
1072
+ const lightShadowOrigin = L => shadowTerminatorOrigin( {
1073
+ hitPoint, offsetNormal: facetN, lift: terminatorLift, faceN: facetN, smoothN: NgeoFF, L,
1074
+ cutoff: shadowTerminatorOffset,
1075
+ } );
1065
1076
 
1066
1077
  // Two-sided shading: opaque path only (transmissive/SSS already continued). Decide the flip on the
1067
1078
  // GEOMETRIC normal — an inward-normal / double-sided mesh (GLB/PBRT) faces away as a whole — so a
@@ -1094,11 +1105,11 @@ export function buildShadeKernel( params ) {
1094
1105
  // smooth mirror / metal: DEFER — tint by the specular directional albedo (metal colour),
1095
1106
  // matching the opaque throughput (≈ F at the near-delta mirror lobe). Achromatic energy excluded.
1096
1107
  const NoV = max( dot( N, V ), float( 1e-3 ) );
1097
- const F0 = clamp(
1098
- mix( dielectricF0( material.ior ).mul( material.specularColor ), albedo, material.metalness ).mul( material.specularIntensity ),
1099
- vec3( 0.0 ), vec3( 1.0 ),
1100
- );
1101
- featCarry.mulAssign( DFGResult.wrap( evaluateDFG( F0, NoV, max( rawRough, float( 0.02 ) ) ) ).E_total );
1108
+ const bf = BaseFresnel.wrap( baseFresnelParams( material, albedo ) );
1109
+ featCarry.mulAssign( DFGResult.wrap( evaluateSpecularDFG(
1110
+ bf.f0, bf.f90, bf.eta, bf.F0m, material.metalness, vec3( 0.0 ), float( 0.0 ), bf.F0,
1111
+ NoV, max( rawRough, float( 0.02 ) ),
1112
+ ) ).E_total );
1102
1113
 
1103
1114
  } );
1104
1115
 
@@ -1176,6 +1187,7 @@ export function buildShadeKernel( params ) {
1176
1187
  envTotalSum, envCompensationDelta, envResolution,
1177
1188
  enableEnvironmentLight,
1178
1189
  tslBool( false ), // wantUnoccluded: false on real surfaces — dead-codes the unoccluded sum
1190
+ terminatorLift, facetN, shadowTerminatorOffset,
1179
1191
  ) ).shadowed.toVar();
1180
1192
 
1181
1193
  const giScale = select( bounceIndex.greaterThan( 0 ), globalIlluminationIntensity, float( 1.0 ) );
@@ -1230,11 +1242,12 @@ export function buildShadeKernel( params ) {
1230
1242
 
1231
1243
  If( NoL.greaterThan( 0.0 ).and( dot( emissiveSample.direction, NgeoFF ).greaterThan( 0.0 ) ), () => {
1232
1244
 
1233
- const rayOffset = calculateRayOffset( hitPoint, NgeoFF, material );
1234
- const rayOrigin = hitPoint.add( rayOffset );
1235
- const shadowDist = emissiveSample.distance.sub( 0.001 );
1245
+ // Aimed at the sampled point and stopped a relative hair short, as for area lights.
1246
+ const rayOrigin = lightShadowOrigin( emissiveSample.direction ).toVar();
1247
+ const toSample = emissiveSample.position.sub( rayOrigin ).toVar();
1248
+ const shadowDist = length( toSample ).toVar();
1236
1249
  const visibility = traceShadowRayWrapped(
1237
- rayOrigin, emissiveSample.direction, shadowDist,
1250
+ rayOrigin, toSample.div( shadowDist ), shadowDist.mul( SHADOW_END ),
1238
1251
  );
1239
1252
 
1240
1253
  If( visibility.greaterThan( 0.0 ), () => {
@@ -1278,7 +1291,7 @@ export function buildShadeKernel( params ) {
1278
1291
  lightBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
1279
1292
  triangleBuffer, bvhBuffer,
1280
1293
  traceShadowRayWrapped,
1281
- calculateRayOffset,
1294
+ Fn( ( [ p, , L ] ) => lightShadowOrigin( L ).sub( p ) ),
1282
1295
  );
1283
1296
 
1284
1297
  currentRadiance.assign( vec4(
@@ -1352,7 +1365,7 @@ export function buildShadeKernel( params ) {
1352
1365
 
1353
1366
  } );
1354
1367
 
1355
- const newOrigin = hitPoint.add( N.mul( 0.001 ) );
1368
+ const newOrigin = offsetRayOrigin( hitPoint, select( dot( facetN, bounceDir ).lessThan( 0.0 ), facetN.negate(), facetN ) );
1356
1369
 
1357
1370
  // Opaque scatter: the only bounce that advances camera depth.
1358
1371
  writeRayOriginMeta( rayBufferRW, rayID, newOrigin, cameraDepth.add( 1 ), sssSteps, transparentCount );
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Cycles' shadow terminator geometry offset (kernel/light/sample.h, Blender 5.1).
3
+ *
4
+ * Near the terminator of a smooth-shaded low-poly surface, a light's shadow ray leaving the flat
5
+ * facet is blocked by that facet or its neighbours although the smooth normal says the point is lit,
6
+ * which draws the mesh's outline into the shading. Starting the ray on the smooth surface the vertex
7
+ * normals describe — lifted along the normal by a parabolic estimate, bounded by each vertex's
8
+ * tangent plane — removes it. Only light and environment shadow rays use it, as in Cycles.
9
+ *
10
+ * Extend computes the lift with the facet normal (HitFacet.js), where the triangle's corners were just
11
+ * read, and packs both into the hit record's spare lane.
12
+ */
13
+
14
+ import { vec3, float, max, min, dot, length, clamp, select, If } from 'three/tsl';
15
+ import { instanceRows, instanceDirToWorld, offsetRayOrigin } from './Common.js';
16
+
17
+ /**
18
+ * The lift at full strength, as a signed length along the hit's smooth normal, from the triangle's
19
+ * corners (see HitFacet.js). Cycles lifts along the object-to-world image of that normal; the two
20
+ * agree unless the placement scales non-uniformly. Zero on a flat-shaded triangle.
21
+ *
22
+ * @param {Object} p - corners and vertex normals as stored (object space when `instanced`), and
23
+ * `faceN`, the world facet normal on the viewer's side
24
+ */
25
+ export function shadowTerminatorLift( { V0, V1, V2, N0, N1, N2, hitPoint, instanced, bvhBuffer, instanceLeaf, faceN } ) {
26
+
27
+ const scale = float( 0.0 ).toVar();
28
+
29
+ const P = hitPoint.toVar();
30
+ If( instanced, () => {
31
+
32
+ const rows = instanceRows( bvhBuffer, instanceLeaf );
33
+ P.assign( vec3(
34
+ rows[ 0 ].xyz.dot( hitPoint ).add( rows[ 0 ].w ),
35
+ rows[ 1 ].xyz.dot( hitPoint ).add( rows[ 1 ].w ),
36
+ rows[ 2 ].xyz.dot( hitPoint ).add( rows[ 2 ].w ),
37
+ ) );
38
+
39
+ } );
40
+
41
+ // The hit record keeps texture UVs, not barycentrics.
42
+ const e1 = V1.sub( V0 ).toVar();
43
+ const e2 = V2.sub( V0 ).toVar();
44
+ const ep = P.sub( V0 ).toVar();
45
+ const d11 = dot( e1, e1 );
46
+ const d12 = dot( e1, e2 );
47
+ const d22 = dot( e2, e2 );
48
+ const den = d11.mul( d22 ).sub( d12.mul( d12 ) ).toVar();
49
+
50
+ If( den.greaterThan( d11.mul( d22 ).mul( 1e-12 ) ), () => {
51
+
52
+ const dp1 = dot( ep, e1 );
53
+ const dp2 = dot( ep, e2 );
54
+ const v = clamp( d22.mul( dp1 ).sub( d12.mul( dp2 ) ).div( den ), 0.0, 1.0 ).toVar();
55
+ const w = clamp( d11.mul( dp2 ).sub( d12.mul( dp1 ) ).div( den ), 0.0, float( 1.0 ).sub( v ) ).toVar();
56
+ const u = float( 1.0 ).sub( v ).sub( w ).toVar();
57
+
58
+ const Pl = V0.mul( u ).add( V1.mul( v ) ).add( V2.mul( w ) ).toVar();
59
+ const nLocal = N0.mul( u ).add( N1.mul( v ) ).add( N2.mul( w ) ).toVar();
60
+ const n = nLocal.toVar();
61
+ If( instanced, () => {
62
+
63
+ const rows = instanceRows( bvhBuffer, instanceLeaf );
64
+ n.assign( instanceDirToWorld( { r0: rows[ 0 ].xyz, r1: rows[ 1 ].xyz, r2: rows[ 2 ].xyz, v: nLocal } ) );
65
+
66
+ } );
67
+
68
+ // Parabolic approximation.
69
+ const a = dot( N2.sub( N0 ), V0.sub( V2 ) );
70
+ const b = dot( N2.sub( N1 ), V1.sub( V2 ) );
71
+ const c = dot( N1.sub( N0 ), V1.sub( V0 ) );
72
+ const h = a.mul( u ).mul( u.sub( 1.0 ) ).add( a.add( b ).add( c ).mul( u ).mul( v ) ).add( b.mul( v ).mul( v.sub( 1.0 ) ) ).toVar();
73
+
74
+ // Bounded by the local linear envelope of the vertices' tangent planes.
75
+ If( dot( n, faceN ).greaterThan( 0.0 ), () => {
76
+
77
+ const h0 = max( dot( V0.sub( Pl ), N0 ).add( max( max( dot( V1.sub( V0 ), N0 ), dot( V2.sub( V0 ), N0 ) ), 0.0 ) ), 0.0 );
78
+ const h1 = max( dot( V1.sub( Pl ), N1 ).add( max( max( dot( V0.sub( V1 ), N1 ), dot( V2.sub( V1 ), N1 ) ), 0.0 ) ), 0.0 );
79
+ const h2 = max( dot( V2.sub( Pl ), N2 ).add( max( max( dot( V0.sub( V2 ), N2 ), dot( V1.sub( V2 ), N2 ) ), 0.0 ) ), 0.0 );
80
+ h.assign( max( min( min( h0, h1 ), h2 ), h.mul( 0.5 ) ) );
81
+
82
+ } ).Else( () => {
83
+
84
+ const h0 = max( dot( Pl.sub( V0 ), N0 ).add( max( max( dot( V0.sub( V1 ), N0 ), dot( V0.sub( V2 ), N0 ) ), 0.0 ) ), 0.0 );
85
+ const h1 = max( dot( Pl.sub( V1 ), N1 ).add( max( max( dot( V1.sub( V0 ), N1 ), dot( V1.sub( V2 ), N1 ) ), 0.0 ) ), 0.0 );
86
+ const h2 = max( dot( Pl.sub( V2 ), N2 ).add( max( max( dot( V2.sub( V0 ), N2 ), dot( V2.sub( V1 ), N2 ) ), 0.0 ) ), 0.0 );
87
+ h.assign( min( min( min( h0, h1 ), h2 ).negate(), h.mul( 0.5 ) ) );
88
+
89
+ } );
90
+
91
+ scale.assign( length( n ).mul( h ) );
92
+
93
+ } );
94
+
95
+ return scale;
96
+
97
+ }
98
+
99
+ /**
100
+ * A shadow ray's origin towards direction `L`: lifted by the share of the lift Cycles gives a
101
+ * direction this close to the terminator, then offset as every spawned ray is.
102
+ *
103
+ * @param {Object} p
104
+ * @param {Node} p.lift - the hit's unit smooth normal times the lift's scale
105
+ * @param {Node} p.smoothN - the interpolated normal on the viewer's side (Cycles' sd->N)
106
+ * @param {Node} p.cutoff - the geometry offset setting; 0 disables
107
+ */
108
+ export function shadowTerminatorOrigin( { hitPoint, offsetNormal, lift, faceN, smoothN, L, cutoff } ) {
109
+
110
+ // Cycles lifts towards the back for a transmitted direction; NEE here only lights the front.
111
+ const NL = dot( smoothN, L );
112
+ const NgL = dot( faceN, L );
113
+ const c = max( cutoff, 1e-6 );
114
+ const amount = select(
115
+ cutoff.greaterThan( 0.0 ).and( NL.greaterThan( 0.0 ) ),
116
+ select(
117
+ NL.lessThan( cutoff ),
118
+ clamp( float( 2.0 ).sub( NgL.add( NL ).div( c ) ), 0.0, 1.0 ),
119
+ clamp( float( 1.0 ).sub( NgL.div( c ) ), 0.0, 1.0 ),
120
+ ),
121
+ float( 0.0 ),
122
+ );
123
+ return offsetRayOrigin( hitPoint.add( lift.mul( amount ) ), offsetNormal );
124
+
125
+ }
package/src/TSL/Struct.js CHANGED
@@ -198,6 +198,15 @@ export const DFGResult = struct( {
198
198
  E_total: 'vec3', // clamp(E_ss * compensation, 0, 1)
199
199
  } );
200
200
 
201
+ // Base-layer Fresnel inputs (see baseFresnelParams).
202
+ export const BaseFresnel = struct( {
203
+ f0: 'vec3', // dielectric reflectance head-on: min(F0(ior) * specularColor, 1) * specularIntensity
204
+ f90: 'float', // dielectric reflectance at grazing: specularIntensity
205
+ eta: 'float', // max(ior, 1)
206
+ F0m: 'vec3', // metal F0: color * specularIntensity
207
+ F0: 'vec3', // the two blended by metalness — the multiscatter tint and the iridescence substrate
208
+ } );
209
+
201
210
  export const MaterialSamples = struct( {
202
211
  albedo: 'vec4',
203
212
  emissive: 'vec3',
@@ -30,7 +30,7 @@ import {
30
30
  import { struct } from './patches.js';
31
31
  import { TWO_PI, EPSILON, constructTBN } from './Common.js';
32
32
  import { getRandomSample1D, getRandomSample2D } from './Random.js';
33
- import { iorToFresnel0, fresnelSchlickFloat } from './Fresnel.js';
33
+ import { fresnelDielectric } from './Fresnel.js';
34
34
  import { ImportanceSampleGGX } from './MaterialSampling.js';
35
35
 
36
36
  // ================================================================================
@@ -189,8 +189,7 @@ export const handleSubsurfaceEntry = Fn( ( [
189
189
  const sinThetaT2 = n1.mul( n1 ).div( max( n2.mul( n2 ), EPSILON ) ).mul( float( 1.0 ).sub( cosThetaI.mul( cosThetaI ) ) );
190
190
  const tir = sinThetaT2.greaterThan( 1.0 ).toVar();
191
191
 
192
- const F0 = iorToFresnel0( n2, n1 );
193
- const Fr = select( tir, float( 1.0 ), fresnelSchlickFloat( cosThetaI, F0 ) ).toVar();
192
+ const Fr = fresnelDielectric( cosThetaI, n2.div( max( n1, EPSILON ) ) ).toVar();
194
193
  const reflectProb = clamp( Fr, 0.02, 0.98 ).toVar();
195
194
 
196
195
  const doReflect = tir.or( getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 11 ) ), rngState, resolution, frame ).lessThan( reflectProb ) ).toVar();
package/src/index.js CHANGED
@@ -27,7 +27,7 @@ export { SETTING_SOURCE } from './RenderSettings.js';
27
27
  export { configureAssets, getAssetConfig } from './AssetConfig.js';
28
28
 
29
29
  // glTF alphaMode derivation — the one definition, shared with hosts that edit materials
30
- export { deriveAlphaMode } from './Processor/GeometryExtractor.js';
30
+ export { deriveAlphaMode, MATERIAL_VALUE_SOURCE } from './Processor/GeometryExtractor.js';
31
31
 
32
32
  // Scene-level authoring metadata embedded in model files (glTF `extras`)
33
33
  export { extractSceneMetadata, parseSceneMetadata } from './Processor/SceneMetadata.js';
@@ -53,6 +53,7 @@ export {
53
53
  BVH_LEAF_MARKERS,
54
54
  TEXTURE_CONSTANTS,
55
55
  DEFAULT_TEXTURE_MATRIX,
56
+ MATERIAL_DEFAULTS,
56
57
  MEMORY_CONSTANTS,
57
58
  PRODUCTION_RENDER_CONFIG,
58
59
  INTERACTIVE_RENDER_CONFIG,
@@ -98,8 +99,35 @@ export { VideoRenderManager } from './managers/VideoRenderManager.js';
98
99
  // Interaction
99
100
  export { InteractionManager } from './managers/InteractionManager.js';
100
101
 
101
- // DLSS neural passes. The app drives these through `denoisingManager` — what is exported here is
102
+ // Neural post passes. The app drives these through `denoisingManager` — what is exported here is
102
103
  // the offline entry point (`renderUpscaled`, used by the regression bench) plus the two contracts a
103
104
  // host needs to build UI against.
104
- export { renderUpscaled, SR_SCALE, SR_MAX_INPUT } from './dlss/DLSSSuperRes.js';
105
- export { DLSS_NR_DEFAULTS, DLSS_NR_RANGES, DLSS_NR_MAX_PIXELS } from './dlss/DLSSNeural.js';
105
+ export { renderUpscaled, SR_SCALE, SR_MAX_INPUT } from './neural/NeuralSuperRes.js';
106
+ export { RETOUCH_DEFAULTS, RETOUCH_RANGES, RETOUCH_MAX_PIXELS } from './neural/NeuralRetouch.js';
107
+
108
+ // ── Colour management ───────────────────────────────────────────────────────────────────────────
109
+ // `app.color` is the instance a host normally uses; these are for building UI against it, and for
110
+ // code that needs the view-transform registry without an app.
111
+ export {
112
+ ColorManagement, getActiveColorManagement, setActiveColorManagement,
113
+ isColorManaged, DEFAULT_WORKING_SPACE, findNativeLinearSpace, canvasColorSpaceFor,
114
+ } from './Color/ColorManagement.js';
115
+ export { displayCanvasFit } from './Color/Displays.js';
116
+ export {
117
+ VIEW_TRANSFORMS, listViewTransforms, getViewTransform, addViewTransform, removeViewTransform,
118
+ onRegistryChange, getRegistryVersion, buildToneMapWGSL,
119
+ OCIO_VIEW_BASE, MAX_TABLE_TRANSFORMS,
120
+ } from './Color/ViewTransforms.js';
121
+ export {
122
+ buildOcioView, addOcioView, addAllOcioViews,
123
+ DEFAULT_LUT_SIZE, DEFAULT_MIN_EV, DEFAULT_MAX_EV,
124
+ } from './Color/OcioViews.js';
125
+ export {
126
+ resolveInputSpace, textureInputSpace, setInputOverride, clearInputOverrides,
127
+ listInputOverrides, listFileRules, isDataTexture,
128
+ } from './Color/InputColorSpaces.js';
129
+ export {
130
+ convertColor, convertPixelsF32, convertEncodedRGBA8, applyMatrixRGBA8, extractMatrix,
131
+ hasColorSpace, isDataSpace, srgbToLinear, linearToSrgb,
132
+ } from './Color/ColorSpaces.js';
133
+ export { measureBakeError, bakeLut, makeCpuSampler } from './Color/LutBake.js';
@@ -4,9 +4,9 @@ import { AIUpscaler } from '../Passes/AIUpscaler.js';
4
4
  import { EngineEvents } from '../EngineEvents.js';
5
5
  import { createLogger } from '../utils/Logger.js';
6
6
 
7
- // The neural passes live in `../dlss/` but report through the manager that drives them, so they
8
- // share one namespace: `rayzee.log.only( 'dlss' )` shows the whole chain.
9
- const dlssLog = createLogger( 'dlss' );
7
+ // The neural passes live in `../neural/` but report through the manager that drives them, so they
8
+ // share one namespace: `rayzee.log.only( 'neural' )` shows the whole chain.
9
+ const neuralLog = createLogger( 'neural' );
10
10
  import { ENGINE_DEFAULTS as DEFAULT_STATE, ASVGF_QUALITY_PRESETS, NRD_DEFAULTS, NRD_QUALITY_PRESETS, NRD_PRESET_KEYS } from '../EngineDefaults.js';
11
11
 
12
12
  // A refresh slower than this is a slideshow, not a live view, so the cadence swaps to a cheaper
@@ -112,16 +112,16 @@ export class DenoisingManager extends EventDispatcher {
112
112
 
113
113
  this.denoiser = null;
114
114
  this.upscaler = null;
115
- // Which model the AI upscaler runs. 'esrgan' is the ONNX chain; 'dlss' is the neural
115
+ // Which model the AI upscaler runs. 'esrgan' is the ONNX chain; 'neural' is the neural
116
116
  // super-resolution pass, which is fixed at 2x and needs a denoised source.
117
117
  this.upscalerBackend = 'esrgan';
118
- this._dlssUpscaler = null;
118
+ this._neuralUpscaler = null;
119
119
  // The neural-rendering (detail) pass is independent of the upscaler: it changes appearance,
120
120
  // not resolution, and runs last because its result cannot be read back.
121
121
  this.neuralRendering = false;
122
122
  this.neuralRenderingSettings = {};
123
- this._dlssNeural = null;
124
- this._dlssModule = null;
123
+ this._retouch = null;
124
+ this._superResModule = null;
125
125
  this._neuralPostBusy = false;
126
126
 
127
127
  // The tier the finished image uses. The loaded tier is not always this one: while the
@@ -162,6 +162,7 @@ export class DenoisingManager extends EventDispatcher {
162
162
  // Bound event forwarding handlers (stored for removal on re-setup / dispose)
163
163
  this._denoiserStartHandler = null;
164
164
  this._denoiserEndHandler = null;
165
+ this._denoiserTileHandler = null;
165
166
  this._upscalerResChangedHandler = null;
166
167
  this._upscalerStartHandler = null;
167
168
  this._upscalerProgressHandler = null;
@@ -298,8 +299,21 @@ export class DenoisingManager extends EventDispatcher {
298
299
 
299
300
  };
300
301
 
302
+ // The loop has stopped by the time a final denoise runs, so each tile has to redraw the
303
+ // canvas itself. Not through the display-refresh callback: that restarts the loop, which a
304
+ // video export drives by hand.
305
+ this._denoiserTileHandler = e => {
306
+
307
+ if ( e.continuous ) return;
308
+ this._publishOutput();
309
+ const ctx = this.pipeline?.context;
310
+ if ( this._stages.compositor && ctx ) this._stages.compositor.render( ctx );
311
+
312
+ };
313
+
301
314
  this.denoiser.addEventListener( 'start', this._denoiserStartHandler );
302
315
  this.denoiser.addEventListener( 'end', this._denoiserEndHandler );
316
+ this.denoiser.addEventListener( 'tileProgress', this._denoiserTileHandler );
303
317
 
304
318
  }
305
319
 
@@ -804,20 +818,20 @@ export class DenoisingManager extends EventDispatcher {
804
818
  }
805
819
 
806
820
  /**
807
- * Selects the AI upscaler's model. 'dlss' is fixed at 2x and reads the denoised picture, so it
821
+ * Selects the AI upscaler's model. 'neural' is fixed at 2x and reads the denoised picture, so it
808
822
  * is a no-op without a denoiser — feeding it raw Monte-Carlo noise measurably loses to bilinear.
809
823
  *
810
- * @param {'esrgan'|'dlss'} backend
824
+ * @param {'esrgan'|'neural'} backend
811
825
  */
812
826
  setUpscalerBackend( backend ) {
813
827
 
814
- const next = backend === 'dlss' ? 'dlss' : 'esrgan';
828
+ const next = backend === 'neural' ? 'neural' : 'esrgan';
815
829
  if ( next === this.upscalerBackend ) return;
816
830
 
817
831
  this.upscalerBackend = next;
818
832
  // Whatever is on screen came from the model being switched away from.
819
833
  this._restoreRenderDisplay();
820
- if ( next !== 'dlss' ) this._releaseDLSSUpscaler();
834
+ if ( next !== 'neural' ) this._releaseNeuralUpscaler();
821
835
 
822
836
  }
823
837
 
@@ -847,15 +861,15 @@ export class DenoisingManager extends EventDispatcher {
847
861
 
848
862
  }
849
863
 
850
- _releaseDLSSUpscaler() {
864
+ _releaseNeuralUpscaler() {
851
865
 
852
- this._dlssUpscaler?.dispose();
853
- this._dlssUpscaler = null;
866
+ this._neuralUpscaler?.dispose();
867
+ this._neuralUpscaler = null;
854
868
 
855
869
  // The frame packer lives on the module, not on the upscaler, and holds two buffers the size
856
870
  // of the render. Nothing else reaches it, so this is its only release point. Guarded on the
857
871
  // module having been loaded, so a teardown never pulls in 1 MB of runtime to call a no-op.
858
- this._dlssModule?.releaseDenoisedReader();
872
+ this._superResModule?.releaseDenoisedReader();
859
873
 
860
874
  }
861
875
 
@@ -866,17 +880,17 @@ export class DenoisingManager extends EventDispatcher {
866
880
  if ( settings ) this.neuralRenderingSettings = { ...this.neuralRenderingSettings, ...settings };
867
881
  if ( ! this.neuralRendering ) {
868
882
 
869
- this._releaseDLSSNeural();
883
+ this._releaseNeuralRetouch();
870
884
  this._restoreRenderDisplay();
871
885
 
872
886
  }
873
887
 
874
888
  }
875
889
 
876
- _releaseDLSSNeural() {
890
+ _releaseNeuralRetouch() {
877
891
 
878
- this._dlssNeural?.dispose();
879
- this._dlssNeural = null;
892
+ this._retouch?.dispose();
893
+ this._retouch = null;
880
894
 
881
895
  }
882
896
 
@@ -894,7 +908,7 @@ export class DenoisingManager extends EventDispatcher {
894
908
  */
895
909
  async _runNeuralPost( isStillComplete ) {
896
910
 
897
- const wantSR = this.upscaler?.enabled && this.upscalerBackend === 'dlss';
911
+ const wantSR = this.upscaler?.enabled && this.upscalerBackend === 'neural';
898
912
  const wantNR = this.neuralRendering;
899
913
  if ( ! wantSR && ! wantNR ) return;
900
914
 
@@ -904,14 +918,14 @@ export class DenoisingManager extends EventDispatcher {
904
918
 
905
919
  if ( ! this.upscalerCanvas ) {
906
920
 
907
- dlssLog.warn( 'neural pass skipped: the engine has no overlay canvas to present on' );
921
+ neuralLog.warn( 'neural pass skipped: the engine has no overlay canvas to present on' );
908
922
  return;
909
923
 
910
924
  }
911
925
 
912
926
  if ( ! this.pipeline?.context?.getTexture( 'oidn:output' ) ) {
913
927
 
914
- dlssLog.warn( 'neural pass skipped: no denoised picture — it needs OIDN' );
928
+ neuralLog.warn( 'neural pass skipped: no denoised picture — it needs OIDN' );
915
929
  return;
916
930
 
917
931
  }
@@ -921,26 +935,26 @@ export class DenoisingManager extends EventDispatcher {
921
935
 
922
936
  try {
923
937
 
924
- const sr = await import( '../dlss/DLSSSuperRes.js' );
925
- this._dlssModule = sr;
938
+ const sr = await import( '../neural/NeuralSuperRes.js' );
939
+ this._superResModule = sr;
926
940
 
927
941
  // The detail pass has a size ceiling — above it a run takes minutes and then loses every GPU
928
942
  // device in the page. Running it FIRST is what keeps it under: at render size it sees a
929
943
  // quarter of the pixels it would after a 2x upscale, and the render reserve already caps
930
944
  // that at 2048. The check stays as a floor under a raised reserve.
931
- const nrModule = wantNR ? await import( '../dlss/DLSSNeural.js' ) : null;
945
+ const nrModule = wantNR ? await import( '../neural/NeuralRetouch.js' ) : null;
932
946
  const srcSize = this.denoiser?._outTexSize;
933
947
  const nrPixels = ( srcSize?.width ?? 0 ) * ( srcSize?.height ?? 0 );
934
- const runNR = wantNR && nrPixels <= nrModule.DLSS_NR_MAX_PIXELS;
948
+ const runNR = wantNR && nrPixels <= nrModule.RETOUCH_MAX_PIXELS;
935
949
 
936
950
  if ( wantNR && ! runNR ) {
937
951
 
938
- dlssLog.warn(
952
+ neuralLog.warn(
939
953
  `Neural rendering skipped: the render is ${( nrPixels / 1e6 ).toFixed( 1 )} MP, above the ` +
940
- `${( nrModule.DLSS_NR_MAX_PIXELS / 1e6 ).toFixed( 1 )} MP the pass survives.` +
954
+ `${( nrModule.RETOUCH_MAX_PIXELS / 1e6 ).toFixed( 1 )} MP the pass survives.` +
941
955
  ( wantSR ? ' The upscale still ran.' : '' )
942
956
  );
943
- this._releaseDLSSNeural();
957
+ this._releaseNeuralRetouch();
944
958
 
945
959
  }
946
960
 
@@ -961,16 +975,16 @@ export class DenoisingManager extends EventDispatcher {
961
975
 
962
976
  if ( wantSR ) {
963
977
 
964
- if ( this._dlssUpscaler
965
- && ( this._dlssUpscaler.inputWidth !== source.width || this._dlssUpscaler.inputHeight !== source.height ) ) {
978
+ if ( this._neuralUpscaler
979
+ && ( this._neuralUpscaler.inputWidth !== source.width || this._neuralUpscaler.inputHeight !== source.height ) ) {
966
980
 
967
- this._releaseDLSSUpscaler();
981
+ this._releaseNeuralUpscaler();
968
982
 
969
983
  }
970
984
 
971
- if ( ! this._dlssUpscaler ) {
985
+ if ( ! this._neuralUpscaler ) {
972
986
 
973
- this._dlssUpscaler = await sr.DLSSSuperRes.create( { width: source.width, height: source.height } );
987
+ this._neuralUpscaler = await sr.NeuralSuperRes.create( { width: source.width, height: source.height } );
974
988
 
975
989
  }
976
990
 
@@ -979,23 +993,23 @@ export class DenoisingManager extends EventDispatcher {
979
993
  let image = source;
980
994
 
981
995
  // Detail first, upscale second. The detail pass hands back scene-referred light (see
982
- // `app/public/dlss/PATCHES.md`), so the upscaler still gets the linear HDR it expects —
996
+ // the runtime's `rayzee-patch` edits), so the upscaler still gets the linear HDR it expects —
983
997
  // which is what makes this order possible at all.
984
998
  if ( runNR ) {
985
999
 
986
1000
  const result = await nrModule.enhanceFrame( {
987
1001
  source: image,
988
1002
  settings: this.neuralRenderingSettings,
989
- instance: this._dlssNeural,
1003
+ instance: this._retouch,
990
1004
  exposure: tone.exposure,
991
1005
  tone: wantSR ? null : tone,
992
1006
  } );
993
- this._dlssNeural = result.instance;
1007
+ this._retouch = result.instance;
994
1008
  image = result;
995
1009
 
996
1010
  }
997
1011
 
998
- if ( wantSR ) image = await this._dlssUpscaler.upscaleToRGBA8( image, tone );
1012
+ if ( wantSR ) image = await this._neuralUpscaler.upscaleToRGBA8( image, tone );
999
1013
 
1000
1014
  if ( ! isStillComplete() ) {
1001
1015
 
@@ -1024,7 +1038,7 @@ export class DenoisingManager extends EventDispatcher {
1024
1038
 
1025
1039
  } catch ( error ) {
1026
1040
 
1027
- dlssLog.warn( 'neural pass failed', error );
1041
+ neuralLog.warn( 'neural pass failed', error );
1028
1042
 
1029
1043
  } finally {
1030
1044
 
@@ -1060,7 +1074,7 @@ export class DenoisingManager extends EventDispatcher {
1060
1074
 
1061
1075
  // One owner of the overlay canvas, so the neural chain and the ONNX upscaler are
1062
1076
  // exclusive. The neural chain also runs on its own when only its detail pass is on.
1063
- if ( this.neuralRendering || ( this.upscaler?.enabled && this.upscalerBackend === 'dlss' ) ) {
1077
+ if ( this.neuralRendering || ( this.upscaler?.enabled && this.upscalerBackend === 'neural' ) ) {
1064
1078
 
1065
1079
  this._runNeuralPost( isStillComplete );
1066
1080
  return;
@@ -1184,8 +1198,8 @@ export class DenoisingManager extends EventDispatcher {
1184
1198
  // Remove pending completion-chain listener
1185
1199
  this._cleanupCompletionListener();
1186
1200
  // Owns a second GPUDevice plus 3 MB of weights, so it must not outlive the manager.
1187
- this._releaseDLSSUpscaler();
1188
- this._releaseDLSSNeural();
1201
+ this._releaseNeuralUpscaler();
1202
+ this._releaseNeuralRetouch();
1189
1203
 
1190
1204
  // Before the denoiser destroys the picture the Compositor would otherwise still sample.
1191
1205
  this._unpublishOutput();
@@ -1194,6 +1208,7 @@ export class DenoisingManager extends EventDispatcher {
1194
1208
 
1195
1209
  if ( this._denoiserStartHandler ) this.denoiser.removeEventListener( 'start', this._denoiserStartHandler );
1196
1210
  if ( this._denoiserEndHandler ) this.denoiser.removeEventListener( 'end', this._denoiserEndHandler );
1211
+ if ( this._denoiserTileHandler ) this.denoiser.removeEventListener( 'tileProgress', this._denoiserTileHandler );
1197
1212
  this.denoiser.dispose();
1198
1213
  this.denoiser = null;
1199
1214
 
@@ -1207,15 +1222,16 @@ export class DenoisingManager extends EventDispatcher {
1207
1222
  if ( this._upscalerEndHandler ) this.upscaler.removeEventListener( 'end', this._upscalerEndHandler );
1208
1223
  this.upscaler.dispose();
1209
1224
  this.upscaler = null;
1210
- // Which model the AI upscaler runs. 'esrgan' is the ONNX chain; 'dlss' is the neural
1225
+ // Which model the AI upscaler runs. 'esrgan' is the ONNX chain; 'neural' is the neural
1211
1226
  // super-resolution pass, which is fixed at 2x and needs a denoised source.
1212
1227
  this.upscalerBackend = 'esrgan';
1213
- this._dlssUpscaler = null;
1228
+ this._neuralUpscaler = null;
1214
1229
 
1215
1230
  }
1216
1231
 
1217
1232
  this._denoiserStartHandler = null;
1218
1233
  this._denoiserEndHandler = null;
1234
+ this._denoiserTileHandler = null;
1219
1235
  this._upscalerResChangedHandler = null;
1220
1236
  this._upscalerStartHandler = null;
1221
1237
  this._upscalerProgressHandler = null;