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
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
import {
|
|
12
12
|
Fn,
|
|
13
13
|
wgslFn,
|
|
14
|
+
bool as tslBool,
|
|
14
15
|
float,
|
|
15
|
-
vec2,
|
|
16
16
|
vec3,
|
|
17
17
|
int,
|
|
18
18
|
max,
|
|
@@ -28,7 +28,6 @@ import {
|
|
|
28
28
|
} from 'three/tsl';
|
|
29
29
|
|
|
30
30
|
import {
|
|
31
|
-
PI_INV,
|
|
32
31
|
MAX_ROUGHNESS,
|
|
33
32
|
MIN_CLEARCOAT_ROUGHNESS,
|
|
34
33
|
MIN_PDF,
|
|
@@ -36,19 +35,19 @@ import {
|
|
|
36
35
|
anisoTangentFrame,
|
|
37
36
|
calculateFireflyThreshold,
|
|
38
37
|
applySoftSuppressionRGB,
|
|
38
|
+
computeDotProductsAniso,
|
|
39
39
|
} from './Common.js';
|
|
40
|
-
import { AnisoFrame, DirectionSample, MaterialCache } from './Struct.js';
|
|
41
|
-
import {
|
|
40
|
+
import { AnisoFrame, DirectionSample, DotProducts, MaterialCache } from './Struct.js';
|
|
41
|
+
import { getRandomSample1D } from './Random.js';
|
|
42
42
|
import { sampleMicrofacetTransmission, MicrofacetTransmissionResult } from './MaterialTransmission.js';
|
|
43
43
|
import {
|
|
44
|
-
|
|
45
|
-
calculateVNDFPDF,
|
|
46
|
-
calculateVNDFPDFAniso,
|
|
44
|
+
calculateBSDFSamplingPDF,
|
|
47
45
|
computeAnisoAlphas,
|
|
48
46
|
calculateBRDFWeights,
|
|
47
|
+
sheenSamplingRoughness,
|
|
49
48
|
} from './MaterialProperties.js';
|
|
50
|
-
import {
|
|
51
|
-
|
|
49
|
+
import { evaluateMaterialResponseFromDots } from './MaterialEvaluation.js';
|
|
50
|
+
|
|
52
51
|
import {
|
|
53
52
|
ImportanceSampleCosine,
|
|
54
53
|
ImportanceSampleGGX,
|
|
@@ -61,7 +60,8 @@ import {
|
|
|
61
60
|
// =============================================================================
|
|
62
61
|
|
|
63
62
|
export const generateSampledDirection = Fn( ( [
|
|
64
|
-
V, N, material, xi, rngState,
|
|
63
|
+
V, N, material, xi, lobeXi, rngState,
|
|
64
|
+
pixelCoord, resolution, frame, dimBase,
|
|
65
65
|
// Caller-resolved material classification (avoids redundant classifyMaterial —
|
|
66
66
|
// TSL Fn can't write back to caller variables, so the caller is responsible
|
|
67
67
|
// for keeping psCachedClassification current and passes it in here).
|
|
@@ -73,6 +73,8 @@ export const generateSampledDirection = Fn( ( [
|
|
|
73
73
|
const resultDirection = vec3( 0.0 ).toVar();
|
|
74
74
|
const resultValue = vec3( 0.0 ).toVar();
|
|
75
75
|
const resultPdf = float( 0.0 ).toVar();
|
|
76
|
+
const resultIsTransmission = tslBool( false ).toVar();
|
|
77
|
+
const resultColorWeight = vec3( 1.0 ).toVar();
|
|
76
78
|
|
|
77
79
|
// Compute BRDF weights
|
|
78
80
|
const weights = cachedBrdfWeights.toVar();
|
|
@@ -87,13 +89,6 @@ export const generateSampledDirection = Fn( ( [
|
|
|
87
89
|
|
|
88
90
|
// Create minimal temporary cache
|
|
89
91
|
const tempCache = MaterialCache( {
|
|
90
|
-
F0: dielectricF0( material.ior ),
|
|
91
|
-
NoV: float( 1.0 ),
|
|
92
|
-
diffuseColor: vec3( 0.0 ),
|
|
93
|
-
isPurelyDiffuse: false,
|
|
94
|
-
alpha: float( 0.0 ),
|
|
95
|
-
k: float( 0.0 ),
|
|
96
|
-
alpha2: float( 0.0 ),
|
|
97
92
|
invRoughness: float( 1.0 ).sub( material.roughness ),
|
|
98
93
|
metalFactor: float( 0.5 ).add( float( 0.5 ).mul( material.metalness ) ),
|
|
99
94
|
iorFactor: min( float( 2.0 ).div( material.ior ), 1.0 ),
|
|
@@ -105,8 +100,10 @@ export const generateSampledDirection = Fn( ( [
|
|
|
105
100
|
|
|
106
101
|
} );
|
|
107
102
|
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
// Own dimension, NOT xi.x — the lobe samplers below consume the full pair, so reusing xi.x
|
|
104
|
+
// would leave it conditioned on the branch taken and confine VNDF's r=sqrt(Xi.x) to
|
|
105
|
+
// [sqrt(lo),1), making the specular peak unreachable whenever a diffuse lobe competes.
|
|
106
|
+
const rand = lobeXi.toVar();
|
|
110
107
|
const H = vec3( 0.0 ).toVar();
|
|
111
108
|
|
|
112
109
|
// Cumulative probability approach for sampling selection
|
|
@@ -115,17 +112,11 @@ export const generateSampledDirection = Fn( ( [
|
|
|
115
112
|
const cumulativeSheen = cumulativeSpecular.add( weights.sheen ).toVar();
|
|
116
113
|
const cumulativeClearcoat = cumulativeSheen.add( weights.clearcoat );
|
|
117
114
|
|
|
118
|
-
// Hoisted out of the lobe chain: used by both Specular and Clearcoat branches
|
|
119
|
-
const NoV = clamp( dot( N, V ), 0.001, 1.0 ).toVar();
|
|
120
|
-
|
|
121
115
|
// Chained If/ElseIf so emitted WGSL becomes a single mutually-exclusive branch
|
|
122
116
|
// (replaces five separate If blocks gated on a `sampled` flag — divergence hotspot)
|
|
123
117
|
If( rand.lessThan( cumulativeDiffuse ), () => {
|
|
124
118
|
|
|
125
|
-
resultDirection.assign( ImportanceSampleCosine( { N, xi
|
|
126
|
-
const NoL = clamp( dot( N, resultDirection ), 0.0, 1.0 );
|
|
127
|
-
resultPdf.assign( NoL.mul( PI_INV ) );
|
|
128
|
-
resultValue.assign( evaluateMaterialResponse( V, resultDirection, N, material ) );
|
|
119
|
+
resultDirection.assign( ImportanceSampleCosine( { N, xi } ) );
|
|
129
120
|
|
|
130
121
|
} ).ElseIf( rand.lessThan( cumulativeSpecular ), () => {
|
|
131
122
|
|
|
@@ -141,10 +132,7 @@ export const generateSampledDirection = Fn( ( [
|
|
|
141
132
|
const localH = sampleGGXVNDFAniso( { V: localV, alphaX: a.x, alphaY: a.y, Xi: xi } );
|
|
142
133
|
H.assign( Ta.mul( localH.x ).add( Ba.mul( localH.y ) ).add( N.mul( localH.z ) ) );
|
|
143
134
|
|
|
144
|
-
const NoH = clamp( dot( N, H ), 0.001, 1.0 );
|
|
145
135
|
resultDirection.assign( reflect( V.negate(), H ) );
|
|
146
|
-
resultPdf.assign( calculateVNDFPDFAniso( a.x, a.y, NoH, dot( Ta, H ), dot( Ba, H ), NoV, dot( Ta, V ), dot( Ba, V ) ) );
|
|
147
|
-
resultValue.assign( evaluateMaterialResponse( V, resultDirection, N, material ) );
|
|
148
136
|
|
|
149
137
|
} ).Else( () => {
|
|
150
138
|
|
|
@@ -155,69 +143,65 @@ export const generateSampledDirection = Fn( ( [
|
|
|
155
143
|
const localH = sampleGGXVNDF( { V: localV, roughness: material.roughness, Xi: xi } );
|
|
156
144
|
H.assign( TBN.mul( localH ) );
|
|
157
145
|
|
|
158
|
-
const NoH = clamp( dot( N, H ), 0.001, 1.0 );
|
|
159
|
-
|
|
160
146
|
resultDirection.assign( reflect( V.negate(), H ) );
|
|
161
|
-
resultPdf.assign( calculateVNDFPDF( NoH, NoV, material.roughness ) );
|
|
162
|
-
resultValue.assign( evaluateMaterialResponse( V, resultDirection, N, material ) );
|
|
163
147
|
|
|
164
148
|
} );
|
|
165
149
|
|
|
166
150
|
} ).ElseIf( rand.lessThan( cumulativeSheen ), () => {
|
|
167
151
|
|
|
168
|
-
H.assign( ImportanceSampleGGX( { N, roughness: material.sheenRoughness, Xi: xi } ) );
|
|
169
|
-
const NoH = clamp( dot( N, H ), 0.001, 1.0 );
|
|
170
|
-
const VoH = clamp( dot( V, H ), 0.001, 1.0 );
|
|
152
|
+
H.assign( ImportanceSampleGGX( { N, roughness: sheenSamplingRoughness( material.sheenRoughness ), Xi: xi } ) );
|
|
171
153
|
resultDirection.assign( reflect( V.negate(), H ) );
|
|
172
|
-
const NoL = dot( N, resultDirection ).toVar();
|
|
173
|
-
|
|
174
|
-
// Reject directions below the surface - fall back to diffuse
|
|
175
|
-
If( NoL.lessThanEqual( 0.0 ), () => {
|
|
176
|
-
|
|
177
|
-
resultDirection.assign( ImportanceSampleCosine( { N, xi } ) );
|
|
178
|
-
NoL.assign( clamp( dot( N, resultDirection ), 0.0, 1.0 ) );
|
|
179
|
-
resultPdf.assign( NoL.mul( PI_INV ) );
|
|
180
|
-
resultValue.assign( evaluateMaterialResponse( V, resultDirection, N, material ) );
|
|
181
|
-
|
|
182
|
-
} ).Else( () => {
|
|
183
154
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
} );
|
|
155
|
+
// Below-surface sheen draws are dropped, not redirected to a cosine direction: a fallback
|
|
156
|
+
// would add a P(reject)·cos term to the true density that calculateBSDFSamplingPDF cannot
|
|
157
|
+
// model, and dividing by the smaller modelled density inflated the lobe 23 %.
|
|
189
158
|
|
|
190
159
|
} ).ElseIf( rand.lessThan( cumulativeClearcoat ), () => {
|
|
191
160
|
|
|
161
|
+
// VNDF, not ImportanceSampleGGX: the mixture density reports this lobe with
|
|
162
|
+
// calculateVNDFPDF, and sampling the half-vector GGX distribution instead would make the
|
|
163
|
+
// reported density one this lobe never drew from.
|
|
192
164
|
const clearcoatRoughness = clamp( material.clearcoatRoughness, MIN_CLEARCOAT_ROUGHNESS, MAX_ROUGHNESS );
|
|
193
|
-
|
|
194
|
-
|
|
165
|
+
const ccTBN = constructTBN( { N } );
|
|
166
|
+
H.assign( ccTBN.mul( sampleGGXVNDF( { V: ccTBN.transpose().mul( V ), roughness: clearcoatRoughness, Xi: xi } ) ) );
|
|
195
167
|
resultDirection.assign( reflect( V.negate(), H ) );
|
|
196
|
-
resultPdf.assign( calculateVNDFPDF( NoH, NoV, clearcoatRoughness ) );
|
|
197
|
-
resultPdf.assign( max( resultPdf, MIN_PDF ) );
|
|
198
|
-
resultValue.assign( evaluateMaterialResponse( V, resultDirection, N, material ) );
|
|
199
168
|
|
|
200
169
|
} ).Else( () => {
|
|
201
170
|
|
|
202
|
-
// Transmission sampling (fallback)
|
|
203
171
|
const entering = dot( V, N ).greaterThan( 0.0 );
|
|
204
|
-
// pathWavelength=0 —
|
|
172
|
+
// pathWavelength=0 — the spectral lock happens downstream; colorWeight is carried out on
|
|
173
|
+
// the DirectionSample so the consumer can build the transmission throughput.
|
|
205
174
|
const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission(
|
|
206
175
|
V, N, material.ior, material.roughness, entering, material.dispersion, xi, rngState, float( 0.0 ),
|
|
176
|
+
pixelCoord, resolution, frame, dimBase,
|
|
207
177
|
) );
|
|
208
178
|
resultDirection.assign( mtResult.direction );
|
|
209
|
-
|
|
210
|
-
|
|
179
|
+
// Selection probability included: this lobe is the only one that reaches its hemisphere,
|
|
180
|
+
// so its density is w_transmission · pdf rather than pdf alone.
|
|
181
|
+
resultPdf.assign( max( weights.transmission.mul( mtResult.pdf ), MIN_PDF ) );
|
|
182
|
+
resultColorWeight.assign( mtResult.colorWeight );
|
|
183
|
+
resultIsTransmission.assign( true );
|
|
184
|
+
|
|
185
|
+
} );
|
|
186
|
+
|
|
187
|
+
// One mixture density for every reflection lobe: any of them could have produced this
|
|
188
|
+
// direction, so the chosen lobe's own pdf is not the density we sampled from.
|
|
189
|
+
If( resultIsTransmission.not(), () => {
|
|
190
|
+
|
|
191
|
+
const dotsOut = DotProducts.wrap( computeDotProductsAniso( N, V, resultDirection, material ) );
|
|
192
|
+
resultValue.assign( evaluateMaterialResponseFromDots( material, dotsOut ) );
|
|
193
|
+
resultPdf.assign( calculateBSDFSamplingPDF( material, weights, dotsOut ) );
|
|
211
194
|
|
|
212
195
|
} );
|
|
213
196
|
|
|
214
|
-
// Ensure PDF is valid
|
|
215
197
|
resultPdf.assign( max( resultPdf, MIN_PDF ) );
|
|
216
198
|
|
|
217
199
|
return DirectionSample( {
|
|
218
200
|
direction: resultDirection,
|
|
219
201
|
value: resultValue,
|
|
220
202
|
pdf: resultPdf,
|
|
203
|
+
isTransmission: resultIsTransmission,
|
|
204
|
+
colorWeight: resultColorWeight,
|
|
221
205
|
} );
|
|
222
206
|
|
|
223
207
|
} );
|
|
@@ -258,6 +242,7 @@ export const computeNDCDepth = /*@__PURE__*/ wgslFn( `
|
|
|
258
242
|
// Takes the already-computed MaterialClassification `mc` directly (the wavefront classifies once per shade).
|
|
259
243
|
export const handleRussianRoulette = Fn( ( [
|
|
260
244
|
depth, throughput, mc, rayDirection, rngState,
|
|
245
|
+
pixelCoord, resolution, frame, dimBase,
|
|
261
246
|
enableEnvironmentLight, useEnvMapIS,
|
|
262
247
|
] ) => {
|
|
263
248
|
|
|
@@ -271,7 +256,7 @@ export const handleRussianRoulette = Fn( ( [
|
|
|
271
256
|
If( throughputStrength.lessThan( 0.0008 ).and( depth.greaterThan( int( 4 ) ) ), () => {
|
|
272
257
|
|
|
273
258
|
const lowThroughputProb = max( throughputStrength.mul( 125.0 ), 0.01 );
|
|
274
|
-
const rrSample =
|
|
259
|
+
const rrSample = getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 3 ) ), rngState, resolution, frame );
|
|
275
260
|
result.assign( select( rrSample.lessThan( lowThroughputProb ), lowThroughputProb, float( 0.0 ) ) );
|
|
276
261
|
|
|
277
262
|
} ).Else( () => {
|
|
@@ -373,7 +358,7 @@ export const handleRussianRoulette = Fn( ( [
|
|
|
373
358
|
const minProb = select( mc.isEmissive, float( 0.04 ), float( 0.02 ) );
|
|
374
359
|
rrProb.assign( max( rrProb, minProb ) );
|
|
375
360
|
|
|
376
|
-
const rrSample =
|
|
361
|
+
const rrSample = getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 3 ) ), rngState, resolution, frame );
|
|
377
362
|
result.assign( select( rrSample.lessThan( rrProb ), rrProb, float( 0.0 ) ) );
|
|
378
363
|
|
|
379
364
|
} );
|