rayzee 7.20.2 → 7.20.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/rayzee.es.js +3977 -3125
- package/dist/rayzee.umd.js +87 -115
- package/package.json +1 -1
- package/src/EngineDefaults.js +1 -1
- package/src/TSL/Clearcoat.js +29 -40
- package/src/TSL/EmissiveSampling.js +8 -7
- package/src/TSL/LightBVHSampling.js +16 -12
- package/src/TSL/LightsCore.js +0 -2
- package/src/TSL/LightsIndirect.js +36 -355
- package/src/TSL/LightsSampling.js +28 -72
- package/src/TSL/MaterialEvaluation.js +44 -71
- package/src/TSL/MaterialProperties.js +209 -176
- package/src/TSL/MaterialTransmission.js +17 -32
- package/src/TSL/PathTracerCore.js +49 -64
- package/src/TSL/Random.js +202 -132
- package/src/TSL/ShadeKernel.js +50 -39
- package/src/TSL/Struct.js +6 -28
- package/src/TSL/Subsurface.js +13 -8
package/package.json
CHANGED
package/src/EngineDefaults.js
CHANGED
|
@@ -97,7 +97,7 @@ export const ENGINE_DEFAULTS = {
|
|
|
97
97
|
pixelFreezeThreshold: 0.02, // per-pixel noise below which a pixel becomes a freeze candidate
|
|
98
98
|
pixelFreezeStability: 8, // consecutive candidate frames before a pixel freezes
|
|
99
99
|
|
|
100
|
-
samplingTechnique:
|
|
100
|
+
samplingTechnique: 2,
|
|
101
101
|
enableEmissiveTriangleSampling: false,
|
|
102
102
|
emissiveBoost: 1.0,
|
|
103
103
|
|
package/src/TSL/Clearcoat.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Fn,
|
|
3
3
|
vec3,
|
|
4
|
-
|
|
4
|
+
int,
|
|
5
5
|
dot,
|
|
6
6
|
normalize,
|
|
7
7
|
reflect,
|
|
@@ -11,12 +11,14 @@ import {
|
|
|
11
11
|
|
|
12
12
|
import { struct } from './patches.js';
|
|
13
13
|
|
|
14
|
-
import { AnisoFrame, DotProducts
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
14
|
+
import { AnisoFrame, DotProducts,
|
|
15
|
+
BRDFWeights,
|
|
16
|
+
} from './Struct.js';
|
|
17
|
+
import { MIN_CLEARCOAT_ROUGHNESS, computeDotProductsAniso, anisoTangentFrame, constructTBN } from './Common.js';
|
|
18
|
+
import { computeAnisoAlphas, calculateBRDFWeightsFromMaterial, calculateBSDFSamplingPDF } from './MaterialProperties.js';
|
|
19
|
+
import { ImportanceSampleCosine, sampleGGXVNDF, sampleGGXVNDFAniso } from './MaterialSampling.js';
|
|
20
|
+
import { evaluateMaterialResponseFromDots } from './MaterialEvaluation.js';
|
|
21
|
+
import { getRandomSample1D } from './Random.js';
|
|
20
22
|
|
|
21
23
|
export const ClearcoatResult = struct( {
|
|
22
24
|
brdf: 'vec3',
|
|
@@ -29,6 +31,7 @@ export const ClearcoatResult = struct( {
|
|
|
29
31
|
// L (light direction) is returned via the out pattern as a separate return
|
|
30
32
|
export const sampleClearcoat = Fn( ( [
|
|
31
33
|
ray, hitInfo, material, randomSample, rngState,
|
|
34
|
+
pixelCoord, resolution, frame, dimBase,
|
|
32
35
|
] ) => {
|
|
33
36
|
|
|
34
37
|
const N = hitInfo.normal;
|
|
@@ -38,27 +41,26 @@ export const sampleClearcoat = Fn( ( [
|
|
|
38
41
|
const clearcoatRoughness = max( material.clearcoatRoughness, MIN_CLEARCOAT_ROUGHNESS );
|
|
39
42
|
const baseRoughness = max( material.roughness, MIN_CLEARCOAT_ROUGHNESS );
|
|
40
43
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
specularWeight.
|
|
49
|
-
clearcoatWeight.divAssign( total );
|
|
50
|
-
diffuseWeight.divAssign( total );
|
|
44
|
+
// The shared selection probabilities, NOT a fourth private scheme. This function used to
|
|
45
|
+
// derive its own specular/clearcoat/diffuse split, so a clearcoat material was sampled with
|
|
46
|
+
// one set of probabilities while every MIS site evaluated the density of another — the white
|
|
47
|
+
// furnace read the resulting mismatch as an energy error that flipped sign once the other
|
|
48
|
+
// sites were unified.
|
|
49
|
+
const weights = BRDFWeights.wrap( calculateBRDFWeightsFromMaterial( material ) ).toVar();
|
|
50
|
+
const clearcoatWeight = weights.clearcoat.toVar();
|
|
51
|
+
const specularWeight = weights.specular.toVar();
|
|
51
52
|
|
|
52
53
|
// Choose which layer to sample
|
|
53
|
-
const rand =
|
|
54
|
+
const rand = getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 13 ) ), rngState, resolution, frame );
|
|
54
55
|
|
|
55
56
|
const L = vec3( 0.0 ).toVar();
|
|
56
57
|
const H = vec3( 0.0 ).toVar();
|
|
57
58
|
|
|
58
59
|
If( rand.lessThan( clearcoatWeight ), () => {
|
|
59
60
|
|
|
60
|
-
// Sample clearcoat layer
|
|
61
|
-
|
|
61
|
+
// Sample clearcoat layer (VNDF — see the base-specular note below)
|
|
62
|
+
const ccTBN = constructTBN( { N } );
|
|
63
|
+
H.assign( ccTBN.mul( sampleGGXVNDF( { V: ccTBN.transpose().mul( V ), roughness: clearcoatRoughness, Xi: randomSample } ) ) );
|
|
62
64
|
L.assign( reflect( V.negate(), H ) );
|
|
63
65
|
|
|
64
66
|
} ).ElseIf( rand.lessThan( clearcoatWeight.add( specularWeight ) ), () => {
|
|
@@ -74,7 +76,9 @@ export const sampleClearcoat = Fn( ( [
|
|
|
74
76
|
|
|
75
77
|
} ).Else( () => {
|
|
76
78
|
|
|
77
|
-
|
|
79
|
+
// VNDF to match the density the MIS sites evaluate for this lobe.
|
|
80
|
+
const tbn = constructTBN( { N } );
|
|
81
|
+
H.assign( tbn.mul( sampleGGXVNDF( { V: tbn.transpose().mul( V ), roughness: baseRoughness, Xi: randomSample } ) ) );
|
|
78
82
|
|
|
79
83
|
} );
|
|
80
84
|
L.assign( reflect( V.negate(), H ) );
|
|
@@ -90,27 +94,12 @@ export const sampleClearcoat = Fn( ( [
|
|
|
90
94
|
// Calculate dot products (aniso-aware: also projects onto the anisotropy frame)
|
|
91
95
|
const dots = DotProducts.wrap( computeDotProductsAniso( N, V, L, material ) );
|
|
92
96
|
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
const clearcoatPDF = DistributionGGX( dots.NoH, clearcoatRoughness ).mul( dots.NoH ).div( float( 4.0 ).mul( dots.VoH ) ).mul( clearcoatWeight );
|
|
96
|
-
const specularPDF = float( 0.0 ).toVar();
|
|
97
|
-
If( material.anisotropy.greaterThan( 0.0 ), () => {
|
|
98
|
-
|
|
99
|
-
const a = computeAnisoAlphas( material.roughness, material.anisotropy );
|
|
100
|
-
specularPDF.assign( calculateVNDFPDFAniso( a.x, a.y, dots.NoH, dots.ToH, dots.BoH, dots.NoV, dots.ToV, dots.BoV ).mul( specularWeight ) );
|
|
101
|
-
|
|
102
|
-
} ).Else( () => {
|
|
103
|
-
|
|
104
|
-
specularPDF.assign( DistributionGGX( dots.NoH, baseRoughness ).mul( dots.NoH ).div( float( 4.0 ).mul( dots.VoH ) ).mul( specularWeight ) );
|
|
105
|
-
|
|
106
|
-
} );
|
|
107
|
-
const diffusePDF = dots.NoL.div( PI ).mul( diffuseWeight );
|
|
108
|
-
|
|
109
|
-
// Combined PDF using MIS
|
|
110
|
-
const pdf = max( clearcoatPDF.add( specularPDF ).add( diffusePDF ), 0.001 );
|
|
97
|
+
// One density for every MIS site — see calculateBSDFSamplingPDF.
|
|
98
|
+
const pdf = max( calculateBSDFSamplingPDF( material, weights, dots ), 0.001 );
|
|
111
99
|
|
|
112
100
|
// Evaluate complete BRDF
|
|
113
|
-
|
|
101
|
+
// The same BRDF every other site evaluates — see the clearcoat note in MaterialEvaluation.
|
|
102
|
+
const brdf = evaluateMaterialResponseFromDots( material, dots );
|
|
114
103
|
|
|
115
104
|
// Return brdf, L direction, and pdf packed together
|
|
116
105
|
// Caller needs L and pdf - return as struct-like output
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
Fn,
|
|
6
|
-
vec2,
|
|
7
6
|
vec3,
|
|
8
7
|
float,
|
|
9
8
|
int,
|
|
@@ -27,7 +26,7 @@ import {
|
|
|
27
26
|
|
|
28
27
|
import { struct } from './patches.js';
|
|
29
28
|
import { MIN_PDF, getDatafromStorageBuffer, powerHeuristic, MATERIAL_SLOTS, MATERIAL_SLOT, computeDotProductsAniso } from './Common.js';
|
|
30
|
-
import {
|
|
29
|
+
import { getRandomSample1D, getRandomSample2D } from './Random.js';
|
|
31
30
|
import { calculateMaterialPDFFromDots } from './LightsSampling.js';
|
|
32
31
|
import { evaluateMaterialResponseFromDots } from './MaterialEvaluation.js';
|
|
33
32
|
import { DotProducts } from './Struct.js';
|
|
@@ -390,6 +389,7 @@ const binarySearchCDF = Fn( ( [ emissiveTriangleBuffer, emissiveOffset, emissive
|
|
|
390
389
|
export const sampleEmissiveTriangle = Fn( ( [
|
|
391
390
|
hitPoint, surfaceNormal,
|
|
392
391
|
rngState,
|
|
392
|
+
pixelCoord, resolution, frame, dimBase,
|
|
393
393
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
394
394
|
triangleBuffer,
|
|
395
395
|
] ) => {
|
|
@@ -410,7 +410,7 @@ export const sampleEmissiveTriangle = Fn( ( [
|
|
|
410
410
|
If( emissiveTriangleCount.greaterThan( int( 0 ) ), () => {
|
|
411
411
|
|
|
412
412
|
// CDF importance-weighted triangle selection (brighter triangles sampled more)
|
|
413
|
-
const randEmissive =
|
|
413
|
+
const randEmissive = getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 1 ) ), rngState, resolution, frame );
|
|
414
414
|
const emissiveIndex = binarySearchCDF( emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, randEmissive ).toVar();
|
|
415
415
|
|
|
416
416
|
// Fetch emissive triangle data from packed light buffer (2 vec4s per entry)
|
|
@@ -427,10 +427,7 @@ export const sampleEmissiveTriangle = Fn( ( [
|
|
|
427
427
|
// Fetch triangle geometry
|
|
428
428
|
const triData = TriangleData.wrap( fetchTriangleData( triangleIndex, triangleBuffer ) );
|
|
429
429
|
|
|
430
|
-
|
|
431
|
-
const xi_r1 = RandomValue( rngState ).toVar();
|
|
432
|
-
const xi_r2 = RandomValue( rngState ).toVar();
|
|
433
|
-
const xi = vec2( xi_r1, xi_r2 );
|
|
430
|
+
const xi = getRandomSample2D( pixelCoord, int( 0 ), dimBase.add( int( 3 ) ), rngState, resolution, frame ).toVar();
|
|
434
431
|
|
|
435
432
|
const geoNormal = normalize( cross( triData.v1.sub( triData.v0 ), triData.v2.sub( triData.v0 ) ) );
|
|
436
433
|
|
|
@@ -532,6 +529,7 @@ export const sampleEmissiveTriangle = Fn( ( [
|
|
|
532
529
|
export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
533
530
|
hitPoint, normal, viewDir, material,
|
|
534
531
|
bounceIndex, rngState,
|
|
532
|
+
pixelCoord, resolution, frame, dimBase,
|
|
535
533
|
emissiveBoost,
|
|
536
534
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
537
535
|
triangleBuffer,
|
|
@@ -555,6 +553,7 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
|
555
553
|
// Sample emissive triangle (CDF importance-weighted)
|
|
556
554
|
const emissiveSample = EmissiveSample.wrap( sampleEmissiveTriangle(
|
|
557
555
|
hitPoint, normal, rngState,
|
|
556
|
+
pixelCoord, resolution, frame, dimBase,
|
|
558
557
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
559
558
|
triangleBuffer,
|
|
560
559
|
) );
|
|
@@ -616,6 +615,7 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
|
616
615
|
export const calculateEmissiveTriangleContribution = Fn( ( [
|
|
617
616
|
hitPoint, normal, viewDir, material,
|
|
618
617
|
bounceIndex, rngState,
|
|
618
|
+
pixelCoord, resolution, frame, dimBase,
|
|
619
619
|
emissiveBoost,
|
|
620
620
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
621
621
|
triangleBuffer,
|
|
@@ -626,6 +626,7 @@ export const calculateEmissiveTriangleContribution = Fn( ( [
|
|
|
626
626
|
const result = EmissiveContributionResult.wrap( calculateEmissiveTriangleContributionDebug(
|
|
627
627
|
hitPoint, normal, viewDir, material,
|
|
628
628
|
bounceIndex, rngState,
|
|
629
|
+
pixelCoord, resolution, frame, dimBase,
|
|
629
630
|
emissiveBoost,
|
|
630
631
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
631
632
|
triangleBuffer,
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
Fn,
|
|
6
|
-
vec2,
|
|
7
6
|
vec3,
|
|
8
7
|
float,
|
|
9
8
|
int,
|
|
@@ -21,7 +20,7 @@ import {
|
|
|
21
20
|
length,
|
|
22
21
|
} from 'three/tsl';
|
|
23
22
|
import { MIN_PDF } from './Common.js';
|
|
24
|
-
import {
|
|
23
|
+
import { getRandomSample1D, getRandomSample2D } from './Random.js';
|
|
25
24
|
import {
|
|
26
25
|
EmissiveSample,
|
|
27
26
|
sampleTriangle,
|
|
@@ -107,6 +106,7 @@ export const lbvhNodeImportance = Fn( ( [ nMin, power, nMax, coneAxis, cosThetaO
|
|
|
107
106
|
export const sampleLightBVHTriangle = Fn( ( [
|
|
108
107
|
hitPoint, surfaceNormal,
|
|
109
108
|
rngState,
|
|
109
|
+
pixelCoord, resolution, frame, dimBase,
|
|
110
110
|
lbvhBuffer,
|
|
111
111
|
emissiveTriangleBuffer,
|
|
112
112
|
emissiveVec4Offset,
|
|
@@ -130,6 +130,13 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
130
130
|
const nodeIndex = int( 0 ).toVar();
|
|
131
131
|
const foundLeaf = tslBool( false ).toVar();
|
|
132
132
|
|
|
133
|
+
// Hierarchical sample warping: ONE variate recycled down the descent and into the leaf scan,
|
|
134
|
+
// rescaled to [0,1) after each choice. A per-level draw has no fixed dimension to key on
|
|
135
|
+
// (depth varies per pixel), so the descent — which dominates emissive-NEE variance — would
|
|
136
|
+
// otherwise stay white-noise however well the point-on-triangle pair is sampled. Unbiased:
|
|
137
|
+
// the rescaled value is uniform conditional on the branch taken; selectionPdf is unchanged.
|
|
138
|
+
const u = getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 1 ) ), rngState, resolution, frame ).toVar();
|
|
139
|
+
|
|
133
140
|
// Tree descent: at most MAX_LBVH_DEPTH iterations
|
|
134
141
|
Loop( MAX_LBVH_DEPTH, () => {
|
|
135
142
|
|
|
@@ -179,19 +186,19 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
179
186
|
// Probability of choosing left child
|
|
180
187
|
const pLeft = lImportance.div( totalImportance );
|
|
181
188
|
|
|
182
|
-
|
|
183
|
-
const rand = RandomValue( rngState );
|
|
184
|
-
|
|
185
|
-
If( rand.lessThan( pLeft ), () => {
|
|
189
|
+
If( u.lessThan( pLeft ), () => {
|
|
186
190
|
|
|
187
191
|
// Choose left child
|
|
192
|
+
u.assign( clamp( u.div( max( pLeft, float( 1e-6 ) ) ), 0.0, 0.999999 ) );
|
|
188
193
|
selectionPdf.mulAssign( pLeft );
|
|
189
194
|
nodeIndex.assign( leftChildIdx );
|
|
190
195
|
|
|
191
196
|
} ).Else( () => {
|
|
192
197
|
|
|
193
198
|
// Choose right child
|
|
194
|
-
|
|
199
|
+
const pRight = float( 1.0 ).sub( pLeft ).toVar();
|
|
200
|
+
u.assign( clamp( u.sub( pLeft ).div( max( pRight, float( 1e-6 ) ) ), 0.0, 0.999999 ) );
|
|
201
|
+
selectionPdf.mulAssign( pRight );
|
|
195
202
|
nodeIndex.assign( rightChildIdx );
|
|
196
203
|
|
|
197
204
|
} );
|
|
@@ -213,7 +220,7 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
213
220
|
|
|
214
221
|
// Sample one triangle proportional to power within the leaf
|
|
215
222
|
// Linear scan: pick random threshold against cumulative power sum
|
|
216
|
-
const randLeaf =
|
|
223
|
+
const randLeaf = u.mul( leafTotalPower );
|
|
217
224
|
const cumPower = float( 0.0 ).toVar();
|
|
218
225
|
|
|
219
226
|
// Default to last entry as fallback
|
|
@@ -253,10 +260,7 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
253
260
|
// Fetch triangle geometry
|
|
254
261
|
const triData = TriangleData.wrap( fetchTriangleData( triangleIndex, triangleBuffer ) );
|
|
255
262
|
|
|
256
|
-
|
|
257
|
-
const xi_r1 = RandomValue( rngState ).toVar();
|
|
258
|
-
const xi_r2 = RandomValue( rngState ).toVar();
|
|
259
|
-
const xi = vec2( xi_r1, xi_r2 );
|
|
263
|
+
const xi = getRandomSample2D( pixelCoord, int( 0 ), dimBase.add( int( 3 ) ), rngState, resolution, frame ).toVar();
|
|
260
264
|
|
|
261
265
|
const geoNormal = normalize( cross( triData.v1.sub( triData.v0 ), triData.v2.sub( triData.v0 ) ) );
|
|
262
266
|
|
package/src/TSL/LightsCore.js
CHANGED
|
@@ -88,8 +88,6 @@ export const LightSample = struct( {
|
|
|
88
88
|
export const IndirectLightingResult = struct( {
|
|
89
89
|
direction: 'vec3', // Sampled direction for next bounce
|
|
90
90
|
throughput: 'vec3', // Light throughput along this path
|
|
91
|
-
misWeight: 'float', // MIS weight for this sample
|
|
92
|
-
pdf: 'float', // PDF of the selected strategy
|
|
93
91
|
combinedPdf: 'float', // Weighted sum of all strategy PDFs (for NEE↔implicit MIS pairing)
|
|
94
92
|
} );
|
|
95
93
|
|