rayzee 6.0.0 → 6.1.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/README.md +5 -0
- package/dist/assets/TexturesWorker-DBqGmVdR.js.map +1 -1
- package/dist/rayzee.es.js +2396 -2072
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +49 -53
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/EngineDefaults.js +3 -0
- package/src/PathTracerApp.js +18 -8
- package/src/Pipeline/RenderStage.js +3 -0
- package/src/Processor/IESParser.js +340 -0
- package/src/Processor/LightSerializer.js +32 -4
- package/src/Processor/SceneProcessor.js +0 -1
- package/src/Processor/ShaderBuilder.js +40 -1
- package/src/Processor/Workers/TexturesWorker.js +1 -1
- package/src/RenderSettings.js +3 -0
- package/src/Stages/NormalDepth.js +3 -19
- package/src/Stages/PathTracer.js +15 -9
- package/src/TSL/BVHTraversal.js +5 -18
- package/src/TSL/Clearcoat.js +1 -5
- package/src/TSL/Common.js +2 -2
- package/src/TSL/Debugger.js +0 -14
- package/src/TSL/EmissiveSampling.js +20 -22
- package/src/TSL/Environment.js +60 -14
- package/src/TSL/Fresnel.js +13 -4
- package/src/TSL/LightsCore.js +238 -5
- package/src/TSL/LightsDirect.js +16 -5
- package/src/TSL/LightsIndirect.js +6 -38
- package/src/TSL/LightsSampling.js +119 -185
- package/src/TSL/MaterialEvaluation.js +25 -14
- package/src/TSL/MaterialProperties.js +14 -34
- package/src/TSL/MaterialTransmission.js +100 -222
- package/src/TSL/PathTracer.js +5 -4
- package/src/TSL/PathTracerCore.js +152 -140
- package/src/TSL/Struct.js +7 -1
- package/src/TSL/TextureSampling.js +2 -2
- package/src/index.js +2 -0
- package/src/managers/AnimationManager.js +3 -6
- package/src/managers/DenoisingManager.js +1 -1
- package/src/managers/GoboManager.js +277 -0
- package/src/managers/IESManager.js +268 -0
- package/src/managers/LightManager.js +33 -1
- package/src/managers/TransformManager.js +3 -3
- package/src/managers/UniformManager.js +5 -5
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { Fn, float, vec3,
|
|
1
|
+
import { Fn, float, vec3, int, If, dot, max, min, sqrt, cos, exp, mix, clamp, smoothstep } from 'three/tsl';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
BRDFWeights,
|
|
5
|
-
MaterialClassification,
|
|
6
5
|
MaterialCache,
|
|
7
6
|
ImportanceSamplingInfo,
|
|
7
|
+
DFGResult,
|
|
8
8
|
|
|
9
9
|
} from './Struct.js';
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
-
PI, TWO_PI, EPSILON, MIN_ROUGHNESS,
|
|
13
|
-
XYZ_TO_REC709, square,
|
|
12
|
+
PI, TWO_PI, EPSILON, MIN_ROUGHNESS,
|
|
13
|
+
XYZ_TO_REC709, square,
|
|
14
14
|
} from './Common.js';
|
|
15
15
|
|
|
16
16
|
import {
|
|
@@ -68,10 +68,14 @@ export const GeometrySmith = Fn( ( [ NoV, NoL, roughness ] ) => {
|
|
|
68
68
|
// multiplicative factor for the specular BRDF that compensates for this loss.
|
|
69
69
|
// Based on: Kulla & Conty 2017 + Karis 2014 analytical DFG approximation.
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
// Single Karis DFG evaluation that returns both outputs the BRDF needs:
|
|
72
|
+
// compensation — multiscatter energy compensation factor for the specular lobe
|
|
73
|
+
// E_total — total specular directional albedo (single-scatter × compensation)
|
|
74
|
+
// Both share the same dfgScale/dfgBias/Ew polynomial, so computing them together
|
|
75
|
+
// halves the polynomial work versus calling two separate functions.
|
|
76
|
+
export const evaluateDFG = Fn( ( [ F0, NoV, roughness ] ) => {
|
|
72
77
|
|
|
73
78
|
// Analytical DFG approximation (Karis 2014)
|
|
74
|
-
// Computes scale and bias where E(F0) = F0 * scale + bias
|
|
75
79
|
const r0 = float( 1.0 ).sub( roughness );
|
|
76
80
|
const r1 = roughness.mul( - 0.0275 ).add( 0.0425 );
|
|
77
81
|
const r2 = roughness.mul( - 0.572 ).add( 1.04 );
|
|
@@ -87,35 +91,13 @@ export const multiscatterCompensation = Fn( ( [ F0, NoV, roughness ] ) => {
|
|
|
87
91
|
const Ew = max( dfgScale.add( dfgBias ), 0.1 );
|
|
88
92
|
|
|
89
93
|
// Energy compensation: 1 + F0 * (1/Ew - 1)
|
|
90
|
-
|
|
91
|
-
// At F0=0.04 (dielectrics): negligible correction
|
|
92
|
-
return vec3( 1.0 ).add( F0.mul( float( 1.0 ).div( Ew ).sub( 1.0 ) ) );
|
|
93
|
-
|
|
94
|
-
} );
|
|
95
|
-
|
|
96
|
-
// Compute the total specular directional albedo including multiscatter compensation.
|
|
97
|
-
// Returns per-channel fraction of energy captured by specular reflection,
|
|
98
|
-
// used for energy-conserving diffuse weight: kD = (1 - E_total) * (1 - metalness).
|
|
99
|
-
export const specularDirectionalAlbedo = Fn( ( [ F0, NoV, roughness ] ) => {
|
|
100
|
-
|
|
101
|
-
// Analytical DFG approximation (same as multiscatterCompensation)
|
|
102
|
-
const r0 = float( 1.0 ).sub( roughness );
|
|
103
|
-
const r1 = roughness.mul( - 0.0275 ).add( 0.0425 );
|
|
104
|
-
const r2 = roughness.mul( - 0.572 ).add( 1.04 );
|
|
105
|
-
const r3 = roughness.mul( 0.022 ).sub( 0.04 );
|
|
106
|
-
const a004 = min( r0.mul( r0 ), exp( float( - 6.4308 ).mul( NoV ) ) ).mul( r0 ).add( r1 );
|
|
107
|
-
const dfgScale = float( - 1.04 ).mul( a004 ).add( r2 );
|
|
108
|
-
const dfgBias = float( 1.04 ).mul( a004 ).add( r3 );
|
|
94
|
+
const compensation = vec3( 1.0 ).add( F0.mul( float( 1.0 ).div( Ew ).sub( 1.0 ) ) );
|
|
109
95
|
|
|
110
|
-
// Single-scatter directional albedo per channel
|
|
96
|
+
// Single-scatter directional albedo per channel, then total with compensation
|
|
111
97
|
const E_ss = max( F0.mul( dfgScale ).add( vec3( dfgBias ) ), vec3( 0.0 ) );
|
|
98
|
+
const E_total = clamp( E_ss.mul( compensation ), vec3( 0.0 ), vec3( 1.0 ) );
|
|
112
99
|
|
|
113
|
-
|
|
114
|
-
const Ew = max( dfgScale.add( dfgBias ), 0.1 );
|
|
115
|
-
|
|
116
|
-
// Apply multiscatter compensation to get total specular albedo
|
|
117
|
-
const compensation = vec3( 1.0 ).add( F0.mul( float( 1.0 ).div( Ew ).sub( 1.0 ) ) );
|
|
118
|
-
return clamp( E_ss.mul( compensation ), vec3( 0.0 ), vec3( 1.0 ) );
|
|
100
|
+
return DFGResult( { compensation, E_total } );
|
|
119
101
|
|
|
120
102
|
} );
|
|
121
103
|
|
|
@@ -329,7 +311,6 @@ export const calculateBRDFWeights = Fn( ( [ material, mc, cache ] ) => {
|
|
|
329
311
|
|
|
330
312
|
export const getImportanceSamplingInfo = Fn( ( [
|
|
331
313
|
material, bounceIndex, mc,
|
|
332
|
-
environmentIntensity, useEnvMapIS, enableEnvironmentLight
|
|
333
314
|
] ) => {
|
|
334
315
|
|
|
335
316
|
// Base BRDF weights using temporary cache
|
|
@@ -418,7 +399,6 @@ export const getImportanceSamplingInfo = Fn( ( [
|
|
|
418
399
|
specularImportance,
|
|
419
400
|
transmissionImportance,
|
|
420
401
|
clearcoatImportance,
|
|
421
|
-
envmapImportance: float( 0.0 ),
|
|
422
402
|
} );
|
|
423
403
|
|
|
424
404
|
} );
|
|
@@ -6,35 +6,25 @@ import {
|
|
|
6
6
|
wgslFn,
|
|
7
7
|
vec2,
|
|
8
8
|
vec3,
|
|
9
|
-
vec4,
|
|
10
9
|
float,
|
|
11
10
|
int,
|
|
12
11
|
bool as tslBool,
|
|
13
|
-
uint,
|
|
14
12
|
If,
|
|
15
|
-
Loop,
|
|
16
13
|
select,
|
|
17
14
|
abs,
|
|
18
|
-
acos,
|
|
19
|
-
sin,
|
|
20
|
-
cos,
|
|
21
15
|
dot,
|
|
22
|
-
normalize,
|
|
23
16
|
reflect,
|
|
24
17
|
refract,
|
|
25
18
|
max,
|
|
26
|
-
min,
|
|
27
19
|
mix,
|
|
28
20
|
clamp,
|
|
29
|
-
|
|
30
|
-
fract,
|
|
21
|
+
exp,
|
|
31
22
|
} from 'three/tsl';
|
|
32
23
|
|
|
33
24
|
import { struct } from './patches.js';
|
|
34
|
-
import {
|
|
35
|
-
import { PI, EPSILON, MIN_ROUGHNESS, MIN_CLEARCOAT_ROUGHNESS, computeDotProducts } from './Common.js';
|
|
25
|
+
import { EPSILON, MIN_ROUGHNESS, MIN_PDF } from './Common.js';
|
|
36
26
|
import { iorToFresnel0, fresnelSchlickFloat } from './Fresnel.js';
|
|
37
|
-
import { DistributionGGX
|
|
27
|
+
import { DistributionGGX } from './MaterialProperties.js';
|
|
38
28
|
import { ImportanceSampleGGX } from './MaterialSampling.js';
|
|
39
29
|
import { RandomValue, pcgHash } from './Random.js';
|
|
40
30
|
|
|
@@ -46,6 +36,7 @@ export const TransmissionResult = struct( {
|
|
|
46
36
|
direction: 'vec3', // New ray direction after transmission/reflection
|
|
47
37
|
throughput: 'vec3', // Color throughput including absorption
|
|
48
38
|
didReflect: 'bool', // Whether the ray was reflected instead of transmitted
|
|
39
|
+
pathWavelength: 'float', // 0 if path is not yet spectral, else locked wavelength in nm
|
|
49
40
|
} );
|
|
50
41
|
|
|
51
42
|
export const MaterialInteractionResult = struct( {
|
|
@@ -57,6 +48,7 @@ export const MaterialInteractionResult = struct( {
|
|
|
57
48
|
direction: 'vec3', // New ray direction if continuing
|
|
58
49
|
throughput: 'vec3', // Color modification for the ray
|
|
59
50
|
alpha: 'float', // Alpha modification
|
|
51
|
+
pathWavelength: 'float', // 0 if path is not yet spectral, else locked wavelength in nm
|
|
60
52
|
} );
|
|
61
53
|
|
|
62
54
|
export const Medium = struct( {
|
|
@@ -77,11 +69,10 @@ export const MicrofacetTransmissionResult = struct( {
|
|
|
77
69
|
halfVector: 'vec3', // Sampled half-vector
|
|
78
70
|
didReflect: 'bool', // Whether TIR occurred
|
|
79
71
|
pdf: 'float', // PDF of the sampled direction
|
|
72
|
+
colorWeight: 'vec3', // Spectral tint to apply once; vec3(1) if locked or non-dispersive
|
|
73
|
+
pathWavelength: 'float', // 0 if path is not yet spectral, else locked wavelength in nm
|
|
80
74
|
} );
|
|
81
75
|
|
|
82
|
-
// Maximum number of nested media
|
|
83
|
-
const MAX_MEDIA_STACK = 4;
|
|
84
|
-
|
|
85
76
|
// MediumStack as a struct with fixed-size slots
|
|
86
77
|
export const MediumStack = struct( {
|
|
87
78
|
m0_ior: 'float',
|
|
@@ -107,75 +98,52 @@ export const MediumStack = struct( {
|
|
|
107
98
|
// DISPERSION
|
|
108
99
|
// ================================================================================
|
|
109
100
|
|
|
110
|
-
//
|
|
111
|
-
export const
|
|
112
|
-
|
|
113
|
-
// Map random value to visible spectrum (380-700nm)
|
|
114
|
-
const wl = mix( float( 380.0 ), float( 700.0 ), random ).toVar();
|
|
115
|
-
|
|
116
|
-
// Convert to micrometers for Cauchy equation
|
|
117
|
-
const wlMicron = wl.div( 1000.0 );
|
|
118
|
-
|
|
119
|
-
// Strong IOR calculation for dramatic dispersion
|
|
120
|
-
const A = baseIOR;
|
|
121
|
-
const B = dispersionStrength.mul( 0.03 );
|
|
122
|
-
const sampledIOR = A.add( B.div( wlMicron.mul( wlMicron ) ) ).toVar();
|
|
123
|
-
|
|
124
|
-
// PURE SATURATED spectral colors
|
|
125
|
-
const colorWeight = vec3( 0.0 ).toVar();
|
|
126
|
-
|
|
127
|
-
// Deep Violet: 380-420
|
|
128
|
-
If( wl.greaterThanEqual( 380.0 ).and( wl.lessThan( 420.0 ) ), () => {
|
|
129
|
-
|
|
130
|
-
colorWeight.assign( vec3( 0.9, 0.0, 1.0 ) );
|
|
131
|
-
|
|
132
|
-
} );
|
|
133
|
-
|
|
134
|
-
// Blue: 420-480
|
|
135
|
-
If( wl.greaterThanEqual( 420.0 ).and( wl.lessThan( 480.0 ) ), () => {
|
|
136
|
-
|
|
137
|
-
colorWeight.assign( vec3( 0.0, 0.0, 1.0 ) );
|
|
138
|
-
|
|
139
|
-
} );
|
|
140
|
-
|
|
141
|
-
// Cyan: 480-500
|
|
142
|
-
If( wl.greaterThanEqual( 480.0 ).and( wl.lessThan( 500.0 ) ), () => {
|
|
101
|
+
// Cauchy IOR n(λ) = baseIOR + 0.03·dispersion / λ_µm²
|
|
102
|
+
export const iorFromWavelength = /*@__PURE__*/ Fn( ( [ baseIOR, dispersionStrength, wavelength ] ) => {
|
|
143
103
|
|
|
144
|
-
|
|
104
|
+
const wlMicron = wavelength.div( 1000.0 );
|
|
105
|
+
return baseIOR.add( dispersionStrength.mul( 0.03 ).div( wlMicron.mul( wlMicron ) ) );
|
|
145
106
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// Green: 500-530
|
|
149
|
-
If( wl.greaterThanEqual( 500.0 ).and( wl.lessThan( 530.0 ) ), () => {
|
|
150
|
-
|
|
151
|
-
colorWeight.assign( vec3( 0.0, 1.0, 0.0 ) );
|
|
152
|
-
|
|
153
|
-
} );
|
|
107
|
+
} );
|
|
154
108
|
|
|
155
|
-
|
|
156
|
-
|
|
109
|
+
// Wyman et al. JCGT 2013 piecewise-Gaussian fit to CIE 1931 2° observer
|
|
110
|
+
const cieGauss = /*@__PURE__*/ Fn( ( [ x, mu, sigmaLo, sigmaHi ] ) => {
|
|
157
111
|
|
|
158
|
-
|
|
112
|
+
const sigma = select( x.lessThan( mu ), sigmaLo, sigmaHi );
|
|
113
|
+
const t = x.sub( mu ).mul( sigma );
|
|
114
|
+
return exp( float( - 0.5 ).mul( t ).mul( t ) );
|
|
159
115
|
|
|
160
|
-
|
|
116
|
+
} );
|
|
161
117
|
|
|
162
|
-
|
|
163
|
-
If( wl.greaterThanEqual( 570.0 ).and( wl.lessThan( 620.0 ) ), () => {
|
|
118
|
+
const wavelengthToXYZ = /*@__PURE__*/ Fn( ( [ wl ] ) => {
|
|
164
119
|
|
|
165
|
-
|
|
120
|
+
const X = cieGauss( wl, 442.0, 0.0624, 0.0374 ).mul( 0.362 )
|
|
121
|
+
.add( cieGauss( wl, 599.8, 0.0264, 0.0323 ).mul( 1.056 ) )
|
|
122
|
+
.sub( cieGauss( wl, 501.1, 0.0490, 0.0382 ).mul( 0.065 ) );
|
|
123
|
+
const Y = cieGauss( wl, 568.8, 0.0213, 0.0247 ).mul( 0.821 )
|
|
124
|
+
.add( cieGauss( wl, 530.9, 0.0613, 0.0322 ).mul( 0.286 ) );
|
|
125
|
+
const Z = cieGauss( wl, 437.0, 0.0845, 0.0278 ).mul( 1.217 )
|
|
126
|
+
.add( cieGauss( wl, 459.0, 0.0385, 0.0725 ).mul( 0.681 ) );
|
|
127
|
+
return vec3( X, Y, Z );
|
|
166
128
|
|
|
167
|
-
|
|
129
|
+
} );
|
|
168
130
|
|
|
169
|
-
|
|
170
|
-
|
|
131
|
+
// Sample a wavelength on [380,700]nm and return its IOR + sRGB colorWeight (CIE 1931 →
|
|
132
|
+
// sRGB, gamut-clipped). The (1.819, 2.773, 2.928) factors normalize the clipped per-λ
|
|
133
|
+
// average to vec3(1), so clear glass converges to white as samples accumulate.
|
|
134
|
+
export const sampleWavelengthForDispersion = Fn( ( [ baseIOR, dispersionStrength, random ] ) => {
|
|
171
135
|
|
|
172
|
-
|
|
136
|
+
const wl = mix( float( 380.0 ), float( 700.0 ), random ).toVar();
|
|
137
|
+
const sampledIOR = iorFromWavelength( baseIOR, dispersionStrength, wl ).toVar();
|
|
173
138
|
|
|
174
|
-
|
|
139
|
+
const xyz = wavelengthToXYZ( wl ).toVar();
|
|
140
|
+
const rgb = vec3(
|
|
141
|
+
xyz.x.mul( 3.2406 ).sub( xyz.y.mul( 1.5372 ) ).sub( xyz.z.mul( 0.4986 ) ),
|
|
142
|
+
xyz.x.mul( - 0.9689 ).add( xyz.y.mul( 1.8758 ) ).add( xyz.z.mul( 0.0415 ) ),
|
|
143
|
+
xyz.x.mul( 0.0557 ).sub( xyz.y.mul( 0.2040 ) ).add( xyz.z.mul( 1.0570 ) ),
|
|
144
|
+
).toVar();
|
|
175
145
|
|
|
176
|
-
|
|
177
|
-
colorWeight.assign( pow( colorWeight, vec3( 0.4 ) ) );
|
|
178
|
-
colorWeight.assign( clamp( colorWeight, vec3( 0.0 ), vec3( 2.0 ) ) );
|
|
146
|
+
const colorWeight = max( rgb, vec3( 0.0 ) ).mul( vec3( 1.819, 2.773, 2.928 ) ).toVar();
|
|
179
147
|
|
|
180
148
|
return SpectralSample( {
|
|
181
149
|
wavelength: wl,
|
|
@@ -245,7 +213,7 @@ export const calculateShadowTransmittance = Fn( ( [ rayDir, normal, material, en
|
|
|
245
213
|
// ================================================================================
|
|
246
214
|
|
|
247
215
|
export const sampleMicrofacetTransmission = Fn( ( [
|
|
248
|
-
V, N, ior, roughness, entering, dispersion, xi, rngState
|
|
216
|
+
V, N, ior, roughness, entering, dispersion, xi, rngState, pathWavelength
|
|
249
217
|
] ) => {
|
|
250
218
|
|
|
251
219
|
const result = MicrofacetTransmissionResult( {
|
|
@@ -253,6 +221,8 @@ export const sampleMicrofacetTransmission = Fn( ( [
|
|
|
253
221
|
halfVector: vec3( 0.0 ),
|
|
254
222
|
didReflect: false,
|
|
255
223
|
pdf: float( 0.0 ),
|
|
224
|
+
colorWeight: vec3( 1.0 ),
|
|
225
|
+
pathWavelength: pathWavelength,
|
|
256
226
|
} ).toVar();
|
|
257
227
|
|
|
258
228
|
// For smooth surfaces with dispersion, use perfect refraction with spectral IOR
|
|
@@ -264,9 +234,20 @@ export const sampleMicrofacetTransmission = Fn( ( [
|
|
|
264
234
|
const eta = ior;
|
|
265
235
|
const etaRatio = select( entering, float( 1.0 ).div( eta ), eta ).toVar();
|
|
266
236
|
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
237
|
+
// Reuse the path's locked wavelength if any; else sample a new one and tint once.
|
|
238
|
+
If( pathWavelength.greaterThan( 0.0 ), () => {
|
|
239
|
+
|
|
240
|
+
const lockedIOR = iorFromWavelength( ior, dispersion, pathWavelength );
|
|
241
|
+
etaRatio.assign( select( entering, float( 1.0 ).div( lockedIOR ), lockedIOR ) );
|
|
242
|
+
|
|
243
|
+
} ).Else( () => {
|
|
244
|
+
|
|
245
|
+
const spectralSample = SpectralSample.wrap( sampleWavelengthForDispersion( ior, dispersion, RandomValue( rngState ) ) );
|
|
246
|
+
etaRatio.assign( select( entering, float( 1.0 ).div( spectralSample.ior ), spectralSample.ior ) );
|
|
247
|
+
result.colorWeight.assign( spectralSample.colorWeight );
|
|
248
|
+
result.pathWavelength.assign( spectralSample.wavelength );
|
|
249
|
+
|
|
250
|
+
} );
|
|
270
251
|
|
|
271
252
|
// Perfect refraction using surface normal
|
|
272
253
|
const refractDir = refract( V.negate(), N, etaRatio ).toVar();
|
|
@@ -297,16 +278,33 @@ export const sampleMicrofacetTransmission = Fn( ( [
|
|
|
297
278
|
// Compute IOR ratio
|
|
298
279
|
const etaRatio = select( entering, float( 1.0 ).div( ior ), ior ).toVar();
|
|
299
280
|
|
|
300
|
-
//
|
|
281
|
+
// Reuse the path's locked wavelength if any; else sample a new one and tint once.
|
|
301
282
|
If( dispersion.greaterThan( 0.0 ), () => {
|
|
302
283
|
|
|
303
|
-
|
|
304
|
-
|
|
284
|
+
If( pathWavelength.greaterThan( 0.0 ), () => {
|
|
285
|
+
|
|
286
|
+
const lockedIOR = iorFromWavelength( ior, dispersion, pathWavelength );
|
|
287
|
+
etaRatio.assign( select( entering, float( 1.0 ).div( lockedIOR ), lockedIOR ) );
|
|
288
|
+
|
|
289
|
+
} ).Else( () => {
|
|
290
|
+
|
|
291
|
+
const spectralSample = SpectralSample.wrap( sampleWavelengthForDispersion( ior, dispersion, RandomValue( rngState ) ) );
|
|
292
|
+
etaRatio.assign( select( entering, float( 1.0 ).div( spectralSample.ior ), spectralSample.ior ) );
|
|
293
|
+
result.colorWeight.assign( spectralSample.colorWeight );
|
|
294
|
+
result.pathWavelength.assign( spectralSample.wavelength );
|
|
295
|
+
|
|
296
|
+
} );
|
|
305
297
|
|
|
306
298
|
} );
|
|
307
299
|
|
|
308
|
-
// Compute refracted direction using the sampled half-vector
|
|
309
|
-
|
|
300
|
+
// Compute refracted direction using the sampled half-vector. HoV and NoH
|
|
301
|
+
// are needed by both the TIR and the transmission PDF branches below, so
|
|
302
|
+
// hoist them once here (VoH == HoV — same dot product). DistributionGGX D
|
|
303
|
+
// is also identical between the two branches (calculateGGXPDF builds the
|
|
304
|
+
// same D internally for the TIR branch), so share it too.
|
|
305
|
+
const HoV = clamp( dot( H, V ), 0.001, 1.0 ).toVar();
|
|
306
|
+
const NoH = clamp( dot( N, H ), 0.001, 1.0 ).toVar();
|
|
307
|
+
const D = DistributionGGX( NoH, transmissionRoughness ).toVar();
|
|
310
308
|
const refractDir = refract( V.negate(), H, etaRatio ).toVar();
|
|
311
309
|
|
|
312
310
|
// Check for total internal reflection
|
|
@@ -316,10 +314,8 @@ export const sampleMicrofacetTransmission = Fn( ( [
|
|
|
316
314
|
result.direction.assign( reflect( V.negate(), H ) );
|
|
317
315
|
result.didReflect.assign( true );
|
|
318
316
|
|
|
319
|
-
//
|
|
320
|
-
|
|
321
|
-
const VoH = clamp( dot( V, H ), 0.001, 1.0 );
|
|
322
|
-
result.pdf.assign( calculateGGXPDF( NoH, VoH, transmissionRoughness ) );
|
|
317
|
+
// Reflection PDF: D(H) * NoH / (4 * VoH) — reuses the hoisted D + dots.
|
|
318
|
+
result.pdf.assign( D.mul( NoH ).div( max( float( 4.0 ).mul( HoV ), MIN_PDF ) ) );
|
|
323
319
|
|
|
324
320
|
} ).Else( () => {
|
|
325
321
|
|
|
@@ -327,10 +323,7 @@ export const sampleMicrofacetTransmission = Fn( ( [
|
|
|
327
323
|
result.direction.assign( refractDir );
|
|
328
324
|
result.didReflect.assign( false );
|
|
329
325
|
|
|
330
|
-
// Calculate proper PDF for microfacet transmission
|
|
331
|
-
const NoH = clamp( dot( N, H ), 0.001, 1.0 );
|
|
332
326
|
const HoL = clamp( dot( H, refractDir ), 0.001, 1.0 );
|
|
333
|
-
const D = DistributionGGX( NoH, transmissionRoughness );
|
|
334
327
|
|
|
335
328
|
// Account for change of measure due to refraction (Jacobian)
|
|
336
329
|
const sqrtDenom = HoV.add( etaRatio.mul( HoL ) );
|
|
@@ -353,13 +346,14 @@ export const sampleMicrofacetTransmission = Fn( ( [
|
|
|
353
346
|
|
|
354
347
|
export const handleTransmission = Fn( ( [
|
|
355
348
|
rayDir, normal, material, entering, rngState,
|
|
356
|
-
currentMediumIOR, previousMediumIOR,
|
|
349
|
+
currentMediumIOR, previousMediumIOR, pathWavelength,
|
|
357
350
|
] ) => {
|
|
358
351
|
|
|
359
352
|
const result = TransmissionResult( {
|
|
360
353
|
direction: vec3( 0.0 ),
|
|
361
354
|
throughput: vec3( 1.0 ),
|
|
362
355
|
didReflect: false,
|
|
356
|
+
pathWavelength: pathWavelength,
|
|
363
357
|
} ).toVar();
|
|
364
358
|
|
|
365
359
|
// Setup surface normal based on ray direction
|
|
@@ -416,10 +410,10 @@ export const handleTransmission = Fn( ( [
|
|
|
416
410
|
|
|
417
411
|
If( doReflect, () => {
|
|
418
412
|
|
|
419
|
-
// Reflection
|
|
413
|
+
// Reflection at a transmissive surface — no wavelength locking
|
|
420
414
|
If( material.roughness.greaterThan( 0.05 ), () => {
|
|
421
415
|
|
|
422
|
-
const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission( V, N, material.ior, material.roughness, entering, float( 0.0 ), xi, rngState ) );
|
|
416
|
+
const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission( V, N, material.ior, material.roughness, entering, float( 0.0 ), xi, rngState, float( 0.0 ) ) );
|
|
423
417
|
result.direction.assign( mtResult.direction );
|
|
424
418
|
|
|
425
419
|
} ).Else( () => {
|
|
@@ -436,125 +430,20 @@ export const handleTransmission = Fn( ( [
|
|
|
436
430
|
// Transmission/refraction path
|
|
437
431
|
If( material.roughness.greaterThan( 0.05 ).or( material.dispersion.greaterThan( 0.0 ) ), () => {
|
|
438
432
|
|
|
439
|
-
const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission( V, N, material.ior, material.roughness, entering, material.dispersion, xi, rngState ) );
|
|
433
|
+
const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission( V, N, material.ior, material.roughness, entering, material.dispersion, xi, rngState, pathWavelength ) );
|
|
434
|
+
result.pathWavelength.assign( mtResult.pathWavelength );
|
|
440
435
|
|
|
441
|
-
// If TIR occurred during sampling, respect it
|
|
442
436
|
If( mtResult.didReflect, () => {
|
|
443
437
|
|
|
438
|
+
// TIR during intended transmission: compensate for selection probability
|
|
444
439
|
result.direction.assign( mtResult.direction );
|
|
445
440
|
result.didReflect.assign( true );
|
|
446
|
-
// TIR during intended transmission: compensate for selection probability
|
|
447
441
|
result.throughput.assign( material.color.xyz.div( max( float( 1.0 ).sub( reflectProb ), 0.05 ) ) );
|
|
448
442
|
|
|
449
443
|
} ).Else( () => {
|
|
450
444
|
|
|
451
445
|
result.direction.assign( mtResult.direction );
|
|
452
|
-
|
|
453
|
-
// Handle dispersion coloring
|
|
454
|
-
If( material.dispersion.greaterThan( 0.0 ), () => {
|
|
455
|
-
|
|
456
|
-
// Calculate refracted ray deviation from original direction
|
|
457
|
-
const originalDir = normalize( rayDir );
|
|
458
|
-
const refractedDir = normalize( result.direction );
|
|
459
|
-
|
|
460
|
-
// Calculate angle-dependent dispersion factor
|
|
461
|
-
const edgeFactor = float( 1.0 ).sub( abs( dot( N, originalDir ) ) );
|
|
462
|
-
const deviationAngle = acos( clamp( dot( originalDir, refractedDir ), - 1.0, 1.0 ) );
|
|
463
|
-
|
|
464
|
-
// Create spatial variation using ray direction and normal
|
|
465
|
-
const combinedVec = normalize( originalDir.add( N ) );
|
|
466
|
-
const spatialVariation = sin( combinedVec.x.mul( 15.0 ) ).mul( cos( combinedVec.y.mul( 12.0 ) ) ).mul( sin( combinedVec.z.mul( 18.0 ) ) );
|
|
467
|
-
|
|
468
|
-
// Add additional variation using refracted direction
|
|
469
|
-
const refractVariation = sin( refractedDir.x.mul( 8.0 ).add( refractedDir.y.mul( 6.0 ) ).add( refractedDir.z.mul( 10.0 ) ) );
|
|
470
|
-
|
|
471
|
-
// Combine multiple factors for better color distribution
|
|
472
|
-
const baseColorIndex = deviationAngle.mul( material.dispersion ).mul( 3.0 );
|
|
473
|
-
const spatialBoost = spatialVariation.mul( 0.3 );
|
|
474
|
-
const refractBoost = refractVariation.mul( 0.2 );
|
|
475
|
-
const edgeBoost = edgeFactor.mul( 0.4 );
|
|
476
|
-
|
|
477
|
-
// Create continuous color mapping across the prism
|
|
478
|
-
const colorIndex = fract( baseColorIndex.add( spatialBoost ).add( refractBoost ).add( edgeBoost ) ).toVar();
|
|
479
|
-
|
|
480
|
-
// ROYGBIV spectrum mapping with smooth transitions
|
|
481
|
-
const rainbowColor = vec3( 0.0 ).toVar();
|
|
482
|
-
|
|
483
|
-
// Red zone
|
|
484
|
-
If( colorIndex.lessThan( 0.143 ), () => {
|
|
485
|
-
|
|
486
|
-
const t = colorIndex.div( 0.143 );
|
|
487
|
-
rainbowColor.assign( mix( vec3( 0.8, 0.0, 0.0 ), vec3( 1.0, 0.0, 0.0 ), t ) );
|
|
488
|
-
|
|
489
|
-
} );
|
|
490
|
-
|
|
491
|
-
// Red to Orange
|
|
492
|
-
If( colorIndex.greaterThanEqual( 0.143 ).and( colorIndex.lessThan( 0.286 ) ), () => {
|
|
493
|
-
|
|
494
|
-
const t = colorIndex.sub( 0.143 ).div( 0.143 );
|
|
495
|
-
rainbowColor.assign( mix( vec3( 1.0, 0.0, 0.0 ), vec3( 1.0, 0.6, 0.0 ), t ) );
|
|
496
|
-
|
|
497
|
-
} );
|
|
498
|
-
|
|
499
|
-
// Orange to Yellow
|
|
500
|
-
If( colorIndex.greaterThanEqual( 0.286 ).and( colorIndex.lessThan( 0.429 ) ), () => {
|
|
501
|
-
|
|
502
|
-
const t = colorIndex.sub( 0.286 ).div( 0.143 );
|
|
503
|
-
rainbowColor.assign( mix( vec3( 1.0, 0.6, 0.0 ), vec3( 1.0, 1.0, 0.0 ), t ) );
|
|
504
|
-
|
|
505
|
-
} );
|
|
506
|
-
|
|
507
|
-
// Yellow to Green
|
|
508
|
-
If( colorIndex.greaterThanEqual( 0.429 ).and( colorIndex.lessThan( 0.571 ) ), () => {
|
|
509
|
-
|
|
510
|
-
const t = colorIndex.sub( 0.429 ).div( 0.142 );
|
|
511
|
-
rainbowColor.assign( mix( vec3( 1.0, 1.0, 0.0 ), vec3( 0.0, 1.0, 0.0 ), t ) );
|
|
512
|
-
|
|
513
|
-
} );
|
|
514
|
-
|
|
515
|
-
// Green to Blue
|
|
516
|
-
If( colorIndex.greaterThanEqual( 0.571 ).and( colorIndex.lessThan( 0.714 ) ), () => {
|
|
517
|
-
|
|
518
|
-
const t = colorIndex.sub( 0.571 ).div( 0.143 );
|
|
519
|
-
rainbowColor.assign( mix( vec3( 0.0, 1.0, 0.0 ), vec3( 0.0, 0.4, 1.0 ), t ) );
|
|
520
|
-
|
|
521
|
-
} );
|
|
522
|
-
|
|
523
|
-
// Blue to Indigo
|
|
524
|
-
If( colorIndex.greaterThanEqual( 0.714 ).and( colorIndex.lessThan( 0.857 ) ), () => {
|
|
525
|
-
|
|
526
|
-
const t = colorIndex.sub( 0.714 ).div( 0.143 );
|
|
527
|
-
rainbowColor.assign( mix( vec3( 0.0, 0.4, 1.0 ), vec3( 0.3, 0.0, 0.8 ), t ) );
|
|
528
|
-
|
|
529
|
-
} );
|
|
530
|
-
|
|
531
|
-
// Indigo to Violet
|
|
532
|
-
If( colorIndex.greaterThanEqual( 0.857 ), () => {
|
|
533
|
-
|
|
534
|
-
const t = colorIndex.sub( 0.857 ).div( 0.143 );
|
|
535
|
-
rainbowColor.assign( mix( vec3( 0.3, 0.0, 0.8 ), vec3( 0.6, 0.0, 1.0 ), t ) );
|
|
536
|
-
|
|
537
|
-
} );
|
|
538
|
-
|
|
539
|
-
// Calculate dispersion strength with proper variation
|
|
540
|
-
const normalizedDispersion = clamp( material.dispersion.div( 5.0 ), 0.0, 1.0 );
|
|
541
|
-
const angleBoost = float( 1.0 ).add( edgeFactor.mul( 1.5 ) );
|
|
542
|
-
|
|
543
|
-
// Make dispersion visibility more gradual
|
|
544
|
-
const baseVisibility = normalizedDispersion.mul( angleBoost );
|
|
545
|
-
const combinedVariation = spatialVariation.add( refractVariation );
|
|
546
|
-
const spatialMod = float( 0.5 ).add( float( 0.5 ).mul( sin( combinedVariation.mul( 3.14159 ) ) ) );
|
|
547
|
-
const dispersionVisibility = clamp( baseVisibility.mul( spatialMod ), 0.0, 0.8 );
|
|
548
|
-
|
|
549
|
-
// Mix rainbow color with clear base for realistic prism effect
|
|
550
|
-
result.throughput.assign( mix( vec3( 1.0 ), rainbowColor, dispersionVisibility ) );
|
|
551
|
-
|
|
552
|
-
} ).Else( () => {
|
|
553
|
-
|
|
554
|
-
// No dispersion - pure white transmission
|
|
555
|
-
result.throughput.assign( vec3( 1.0 ) );
|
|
556
|
-
|
|
557
|
-
} );
|
|
446
|
+
result.throughput.assign( mtResult.colorWeight );
|
|
558
447
|
|
|
559
448
|
} );
|
|
560
449
|
|
|
@@ -576,12 +465,9 @@ export const handleTransmission = Fn( ( [
|
|
|
576
465
|
// due to solid angle compression/expansion (cancels for round-trip enter+exit paths)
|
|
577
466
|
result.throughput.mulAssign( n1.mul( n1 ).div( max( n2.mul( n2 ), EPSILON ) ) );
|
|
578
467
|
|
|
579
|
-
//
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
result.throughput.mulAssign( calculateBeerLawAbsorption( { attenuationColor: material.attenuationColor, attenuationDistance: material.attenuationDistance, thickness: material.thickness } ) );
|
|
583
|
-
|
|
584
|
-
} );
|
|
468
|
+
// KHR_materials_volume absorption is applied per-bounce in PathTracerCore based
|
|
469
|
+
// on the actual ray path length inside the medium (driven by the medium stack).
|
|
470
|
+
// No entry-point thickness approximation here — that would double-count.
|
|
585
471
|
|
|
586
472
|
// Fresnel transmission factor with PDF compensation
|
|
587
473
|
result.throughput.mulAssign( float( 1.0 ).sub( Fr ).div( max( float( 1.0 ).sub( reflectProb ), 0.05 ) ) );
|
|
@@ -599,11 +485,10 @@ export const handleTransmission = Fn( ( [
|
|
|
599
485
|
// ================================================================================
|
|
600
486
|
|
|
601
487
|
export const handleMaterialTransparency = Fn( ( [
|
|
602
|
-
ray,
|
|
603
|
-
// RenderState fields passed individually (since inout not supported)
|
|
488
|
+
ray, normal, material, rngState,
|
|
604
489
|
transmissiveTraversals,
|
|
605
|
-
// Precomputed medium IOR values from stack
|
|
606
490
|
currentMediumIOR, previousMediumIOR,
|
|
491
|
+
pathWavelength,
|
|
607
492
|
] ) => {
|
|
608
493
|
|
|
609
494
|
const result = MaterialInteractionResult( {
|
|
@@ -615,21 +500,16 @@ export const handleMaterialTransparency = Fn( ( [
|
|
|
615
500
|
direction: ray.direction,
|
|
616
501
|
throughput: vec3( 1.0 ),
|
|
617
502
|
alpha: float( 1.0 ),
|
|
503
|
+
pathWavelength: pathWavelength,
|
|
618
504
|
} ).toVar();
|
|
619
505
|
|
|
620
|
-
//
|
|
621
|
-
// Step 1: Fast path for completely opaque materials
|
|
622
|
-
// -----------------------------------------------------------------
|
|
623
|
-
// Quick early exit for fully opaque materials (most common case)
|
|
506
|
+
// Fast path for fully opaque materials (most common case)
|
|
624
507
|
If( material.alphaMode.equal( int( 0 ) ).and( material.transmission.lessThanEqual( 0.0 ) ), () => {
|
|
625
508
|
|
|
626
|
-
//
|
|
509
|
+
// no interaction needed
|
|
627
510
|
|
|
628
511
|
} ).Else( () => {
|
|
629
512
|
|
|
630
|
-
// -----------------------------------------------------------------
|
|
631
|
-
// Step 2: Handle alpha modes according to glTF spec
|
|
632
|
-
// -----------------------------------------------------------------
|
|
633
513
|
const alphaRand = RandomValue( rngState );
|
|
634
514
|
const transmissionRand = RandomValue( rngState );
|
|
635
515
|
const transmissionSeed = pcgHash( { state: rngState } );
|
|
@@ -681,9 +561,6 @@ export const handleMaterialTransparency = Fn( ( [
|
|
|
681
561
|
|
|
682
562
|
} );
|
|
683
563
|
|
|
684
|
-
// -----------------------------------------------------------------
|
|
685
|
-
// Step 3: Handle transmission if present
|
|
686
|
-
// -----------------------------------------------------------------
|
|
687
564
|
If( handled.not().and( material.transmission.greaterThan( 0.0 ) ).and( transmissiveTraversals.greaterThan( int( 0 ) ) ), () => {
|
|
688
565
|
|
|
689
566
|
// Only apply transmission with probability equal to the transmission value
|
|
@@ -693,7 +570,7 @@ export const handleMaterialTransparency = Fn( ( [
|
|
|
693
570
|
|
|
694
571
|
const transResult = TransmissionResult.wrap( handleTransmission(
|
|
695
572
|
ray.direction, normal, material, entering, transmissionSeed,
|
|
696
|
-
currentMediumIOR, previousMediumIOR,
|
|
573
|
+
currentMediumIOR, previousMediumIOR, pathWavelength,
|
|
697
574
|
) );
|
|
698
575
|
|
|
699
576
|
result.direction.assign( transResult.direction );
|
|
@@ -703,6 +580,7 @@ export const handleMaterialTransparency = Fn( ( [
|
|
|
703
580
|
result.didReflect.assign( transResult.didReflect );
|
|
704
581
|
result.entering.assign( entering );
|
|
705
582
|
result.alpha.assign( float( 1.0 ).sub( material.transmission ) );
|
|
583
|
+
result.pathWavelength.assign( transResult.pathWavelength );
|
|
706
584
|
|
|
707
585
|
} );
|
|
708
586
|
|
package/src/TSL/PathTracer.js
CHANGED
|
@@ -140,10 +140,11 @@ export const pathTracerMain = ( params ) => {
|
|
|
140
140
|
envCDFBuffer,
|
|
141
141
|
envTotalSum, envCompensationDelta, envResolution,
|
|
142
142
|
enableEnvironmentLight, useEnvMapIS,
|
|
143
|
+
groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight,
|
|
143
144
|
maxBounceCount, transmissiveBounces,
|
|
144
145
|
showBackground, transparentBackground, backgroundIntensity,
|
|
145
146
|
fireflyThreshold, globalIlluminationIntensity,
|
|
146
|
-
|
|
147
|
+
enableEmissiveTriangleSampling,
|
|
147
148
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
|
|
148
149
|
lightBVHBuffer, lightBVHNodeCount,
|
|
149
150
|
debugVisScale,
|
|
@@ -170,7 +171,6 @@ export const pathTracerMain = ( params ) => {
|
|
|
170
171
|
const pixelSamples = int( 0 ).toVar();
|
|
171
172
|
|
|
172
173
|
const baseSeed = getDecorrelatedSeed( { pixelCoord, rayIndex: int( 0 ), frame } ).toVar();
|
|
173
|
-
const pixelIndex = int( pixelCoord.y ).mul( int( resolution.x ) ).add( int( pixelCoord.x ) ).toVar();
|
|
174
174
|
|
|
175
175
|
// MRT data
|
|
176
176
|
const worldNormal = vec3( 0.0, 0.0, 1.0 ).toVar();
|
|
@@ -272,7 +272,7 @@ export const pathTracerMain = ( params ) => {
|
|
|
272
272
|
|
|
273
273
|
// Normal path tracing
|
|
274
274
|
const traceResult = TraceResult.wrap( Trace(
|
|
275
|
-
ray, seed, rayIndex,
|
|
275
|
+
ray, seed, rayIndex,
|
|
276
276
|
bvhBuffer,
|
|
277
277
|
triangleBuffer,
|
|
278
278
|
materialBuffer,
|
|
@@ -287,10 +287,11 @@ export const pathTracerMain = ( params ) => {
|
|
|
287
287
|
envCDFBuffer,
|
|
288
288
|
envTotalSum, envCompensationDelta, envResolution,
|
|
289
289
|
enableEnvironmentLight, useEnvMapIS,
|
|
290
|
+
groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight,
|
|
290
291
|
maxBounceCount, transmissiveBounces,
|
|
291
292
|
backgroundIntensity, showBackground, transparentBackground,
|
|
292
293
|
fireflyThreshold, globalIlluminationIntensity,
|
|
293
|
-
|
|
294
|
+
enableEmissiveTriangleSampling,
|
|
294
295
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
|
|
295
296
|
lightBVHBuffer, lightBVHNodeCount,
|
|
296
297
|
pixelCoord, resolution, frame,
|