rayzee 6.0.0 → 6.0.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.
- package/dist/rayzee.es.js +769 -786
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +22 -22
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/TSL/BVHTraversal.js +1 -12
- package/src/TSL/Clearcoat.js +1 -5
- package/src/TSL/Common.js +1 -1
- package/src/TSL/Debugger.js +0 -12
- package/src/TSL/LightsIndirect.js +2 -1
- package/src/TSL/MaterialTransmission.js +82 -185
- package/src/TSL/PathTracerCore.js +8 -1
package/package.json
CHANGED
package/src/TSL/BVHTraversal.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// BVH Traversal - Ported from bvhtraverse.fs
|
|
2
|
-
// Stack-based BVH traversal for ray-triangle intersection
|
|
3
|
-
|
|
4
1
|
import {
|
|
5
2
|
Fn,
|
|
6
3
|
wgslFn,
|
|
@@ -26,17 +23,9 @@ import {
|
|
|
26
23
|
} from 'three/tsl';
|
|
27
24
|
|
|
28
25
|
import { Ray, HitInfo } from './Struct.js';
|
|
29
|
-
import { getDatafromStorageBuffer
|
|
26
|
+
import { getDatafromStorageBuffer } from './Common.js';
|
|
30
27
|
import { RandomPointInCircle } from './Random.js';
|
|
31
28
|
|
|
32
|
-
// ================================================================================
|
|
33
|
-
// STRUCTS
|
|
34
|
-
// ================================================================================
|
|
35
|
-
|
|
36
|
-
// ================================================================================
|
|
37
|
-
// CONSTANTS
|
|
38
|
-
// ================================================================================
|
|
39
|
-
|
|
40
29
|
const MAX_STACK_DEPTH = 32;
|
|
41
30
|
const MAX_BVH_ITERATIONS = 512;
|
|
42
31
|
const BVH_STRIDE = 4;
|
package/src/TSL/Clearcoat.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
// Clearcoat BRDF - Ported from clearcoat.fs
|
|
2
|
-
// Note: evaluateLayeredBRDF lives in MaterialEvaluation.js
|
|
3
|
-
|
|
4
1
|
import {
|
|
5
2
|
Fn,
|
|
6
3
|
vec3,
|
|
7
4
|
float,
|
|
8
|
-
dot,
|
|
9
5
|
normalize,
|
|
10
6
|
reflect,
|
|
11
7
|
max,
|
|
@@ -14,7 +10,7 @@ import {
|
|
|
14
10
|
|
|
15
11
|
import { struct } from './patches.js';
|
|
16
12
|
|
|
17
|
-
import {
|
|
13
|
+
import { DotProducts } from './Struct.js';
|
|
18
14
|
import { PI, MIN_CLEARCOAT_ROUGHNESS, computeDotProducts } from './Common.js';
|
|
19
15
|
import { DistributionGGX } from './MaterialProperties.js';
|
|
20
16
|
import { ImportanceSampleGGX, ImportanceSampleCosine } from './MaterialSampling.js';
|
package/src/TSL/Common.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fn, wgslFn, float, vec2, vec3, vec4, int, mat3, If, max,
|
|
1
|
+
import { Fn, wgslFn, float, vec2, vec3, vec4, int, mat3, If, max, dot, clamp, bool as tslBool } from 'three/tsl';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
DotProducts,
|
package/src/TSL/Debugger.js
CHANGED
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Debugger.js - Debug Visualization Modes
|
|
3
|
-
*
|
|
4
|
-
* Exact port of debugger.fs
|
|
5
|
-
* Pure TSL: Fn(), If(), .toVar(), .assign() — NO wgslFn()
|
|
6
|
-
*
|
|
7
|
-
* Contains:
|
|
8
|
-
* - visualizeDepth — depth to grayscale gradient
|
|
9
|
-
* - visualizeNormal — normal to RGB mapping
|
|
10
|
-
* - TraceDebugMode — main debug mode dispatch (switch on visMode)
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
1
|
import {
|
|
14
2
|
Fn,
|
|
15
3
|
wgslFn,
|
|
@@ -346,8 +346,9 @@ export const calculateIndirectLighting = Fn( ( [
|
|
|
346
346
|
|
|
347
347
|
// Strategy 3: Transmission
|
|
348
348
|
const entering = dot( V, N ).greaterThan( 0.0 ).toVar();
|
|
349
|
+
// pathWavelength=0 — MIS evaluation reads only direction/PDF, no spectral tint
|
|
349
350
|
const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission(
|
|
350
|
-
V, N, material.ior, material.roughness, entering, material.dispersion, sampleRand, rngState
|
|
351
|
+
V, N, material.ior, material.roughness, entering, material.dispersion, sampleRand, rngState, float( 0.0 )
|
|
351
352
|
).toVar() );
|
|
352
353
|
sampleDir.assign( mtResult.direction );
|
|
353
354
|
samplePdf.assign( mtResult.pdf );
|
|
@@ -15,19 +15,14 @@ import {
|
|
|
15
15
|
Loop,
|
|
16
16
|
select,
|
|
17
17
|
abs,
|
|
18
|
-
acos,
|
|
19
|
-
sin,
|
|
20
|
-
cos,
|
|
21
18
|
dot,
|
|
22
|
-
normalize,
|
|
23
19
|
reflect,
|
|
24
20
|
refract,
|
|
25
21
|
max,
|
|
26
22
|
min,
|
|
27
23
|
mix,
|
|
28
24
|
clamp,
|
|
29
|
-
|
|
30
|
-
fract,
|
|
25
|
+
exp,
|
|
31
26
|
} from 'three/tsl';
|
|
32
27
|
|
|
33
28
|
import { struct } from './patches.js';
|
|
@@ -46,6 +41,7 @@ export const TransmissionResult = struct( {
|
|
|
46
41
|
direction: 'vec3', // New ray direction after transmission/reflection
|
|
47
42
|
throughput: 'vec3', // Color throughput including absorption
|
|
48
43
|
didReflect: 'bool', // Whether the ray was reflected instead of transmitted
|
|
44
|
+
pathWavelength: 'float', // 0 if path is not yet spectral, else locked wavelength in nm
|
|
49
45
|
} );
|
|
50
46
|
|
|
51
47
|
export const MaterialInteractionResult = struct( {
|
|
@@ -57,6 +53,7 @@ export const MaterialInteractionResult = struct( {
|
|
|
57
53
|
direction: 'vec3', // New ray direction if continuing
|
|
58
54
|
throughput: 'vec3', // Color modification for the ray
|
|
59
55
|
alpha: 'float', // Alpha modification
|
|
56
|
+
pathWavelength: 'float', // 0 if path is not yet spectral, else locked wavelength in nm
|
|
60
57
|
} );
|
|
61
58
|
|
|
62
59
|
export const Medium = struct( {
|
|
@@ -77,6 +74,8 @@ export const MicrofacetTransmissionResult = struct( {
|
|
|
77
74
|
halfVector: 'vec3', // Sampled half-vector
|
|
78
75
|
didReflect: 'bool', // Whether TIR occurred
|
|
79
76
|
pdf: 'float', // PDF of the sampled direction
|
|
77
|
+
colorWeight: 'vec3', // Spectral tint to apply once; vec3(1) if locked or non-dispersive
|
|
78
|
+
pathWavelength: 'float', // 0 if path is not yet spectral, else locked wavelength in nm
|
|
80
79
|
} );
|
|
81
80
|
|
|
82
81
|
// Maximum number of nested media
|
|
@@ -107,75 +106,52 @@ export const MediumStack = struct( {
|
|
|
107
106
|
// DISPERSION
|
|
108
107
|
// ================================================================================
|
|
109
108
|
|
|
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 ) ), () => {
|
|
109
|
+
// Cauchy IOR n(λ) = baseIOR + 0.03·dispersion / λ_µm²
|
|
110
|
+
export const iorFromWavelength = /*@__PURE__*/ Fn( ( [ baseIOR, dispersionStrength, wavelength ] ) => {
|
|
136
111
|
|
|
137
|
-
|
|
112
|
+
const wlMicron = wavelength.div( 1000.0 );
|
|
113
|
+
return baseIOR.add( dispersionStrength.mul( 0.03 ).div( wlMicron.mul( wlMicron ) ) );
|
|
138
114
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// Cyan: 480-500
|
|
142
|
-
If( wl.greaterThanEqual( 480.0 ).and( wl.lessThan( 500.0 ) ), () => {
|
|
143
|
-
|
|
144
|
-
colorWeight.assign( vec3( 0.0, 1.0, 1.0 ) );
|
|
145
|
-
|
|
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
|
-
} );
|
|
115
|
+
} );
|
|
154
116
|
|
|
155
|
-
|
|
156
|
-
|
|
117
|
+
// Wyman et al. JCGT 2013 piecewise-Gaussian fit to CIE 1931 2° observer
|
|
118
|
+
const cieGauss = /*@__PURE__*/ Fn( ( [ x, mu, sigmaLo, sigmaHi ] ) => {
|
|
157
119
|
|
|
158
|
-
|
|
120
|
+
const sigma = select( x.lessThan( mu ), sigmaLo, sigmaHi );
|
|
121
|
+
const t = x.sub( mu ).mul( sigma );
|
|
122
|
+
return exp( float( - 0.5 ).mul( t ).mul( t ) );
|
|
159
123
|
|
|
160
|
-
|
|
124
|
+
} );
|
|
161
125
|
|
|
162
|
-
|
|
163
|
-
If( wl.greaterThanEqual( 570.0 ).and( wl.lessThan( 620.0 ) ), () => {
|
|
126
|
+
const wavelengthToXYZ = /*@__PURE__*/ Fn( ( [ wl ] ) => {
|
|
164
127
|
|
|
165
|
-
|
|
128
|
+
const X = cieGauss( wl, 442.0, 0.0624, 0.0374 ).mul( 0.362 )
|
|
129
|
+
.add( cieGauss( wl, 599.8, 0.0264, 0.0323 ).mul( 1.056 ) )
|
|
130
|
+
.sub( cieGauss( wl, 501.1, 0.0490, 0.0382 ).mul( 0.065 ) );
|
|
131
|
+
const Y = cieGauss( wl, 568.8, 0.0213, 0.0247 ).mul( 0.821 )
|
|
132
|
+
.add( cieGauss( wl, 530.9, 0.0613, 0.0322 ).mul( 0.286 ) );
|
|
133
|
+
const Z = cieGauss( wl, 437.0, 0.0845, 0.0278 ).mul( 1.217 )
|
|
134
|
+
.add( cieGauss( wl, 459.0, 0.0385, 0.0725 ).mul( 0.681 ) );
|
|
135
|
+
return vec3( X, Y, Z );
|
|
166
136
|
|
|
167
|
-
|
|
137
|
+
} );
|
|
168
138
|
|
|
169
|
-
|
|
170
|
-
|
|
139
|
+
// Sample a wavelength on [380,700]nm and return its IOR + sRGB colorWeight (CIE 1931 →
|
|
140
|
+
// sRGB, gamut-clipped). The (1.819, 2.773, 2.928) factors normalize the clipped per-λ
|
|
141
|
+
// average to vec3(1), so clear glass converges to white as samples accumulate.
|
|
142
|
+
export const sampleWavelengthForDispersion = Fn( ( [ baseIOR, dispersionStrength, random ] ) => {
|
|
171
143
|
|
|
172
|
-
|
|
144
|
+
const wl = mix( float( 380.0 ), float( 700.0 ), random ).toVar();
|
|
145
|
+
const sampledIOR = iorFromWavelength( baseIOR, dispersionStrength, wl ).toVar();
|
|
173
146
|
|
|
174
|
-
|
|
147
|
+
const xyz = wavelengthToXYZ( wl ).toVar();
|
|
148
|
+
const rgb = vec3(
|
|
149
|
+
xyz.x.mul( 3.2406 ).sub( xyz.y.mul( 1.5372 ) ).sub( xyz.z.mul( 0.4986 ) ),
|
|
150
|
+
xyz.x.mul( - 0.9689 ).add( xyz.y.mul( 1.8758 ) ).add( xyz.z.mul( 0.0415 ) ),
|
|
151
|
+
xyz.x.mul( 0.0557 ).sub( xyz.y.mul( 0.2040 ) ).add( xyz.z.mul( 1.0570 ) ),
|
|
152
|
+
).toVar();
|
|
175
153
|
|
|
176
|
-
|
|
177
|
-
colorWeight.assign( pow( colorWeight, vec3( 0.4 ) ) );
|
|
178
|
-
colorWeight.assign( clamp( colorWeight, vec3( 0.0 ), vec3( 2.0 ) ) );
|
|
154
|
+
const colorWeight = max( rgb, vec3( 0.0 ) ).mul( vec3( 1.819, 2.773, 2.928 ) ).toVar();
|
|
179
155
|
|
|
180
156
|
return SpectralSample( {
|
|
181
157
|
wavelength: wl,
|
|
@@ -245,7 +221,7 @@ export const calculateShadowTransmittance = Fn( ( [ rayDir, normal, material, en
|
|
|
245
221
|
// ================================================================================
|
|
246
222
|
|
|
247
223
|
export const sampleMicrofacetTransmission = Fn( ( [
|
|
248
|
-
V, N, ior, roughness, entering, dispersion, xi, rngState
|
|
224
|
+
V, N, ior, roughness, entering, dispersion, xi, rngState, pathWavelength
|
|
249
225
|
] ) => {
|
|
250
226
|
|
|
251
227
|
const result = MicrofacetTransmissionResult( {
|
|
@@ -253,6 +229,8 @@ export const sampleMicrofacetTransmission = Fn( ( [
|
|
|
253
229
|
halfVector: vec3( 0.0 ),
|
|
254
230
|
didReflect: false,
|
|
255
231
|
pdf: float( 0.0 ),
|
|
232
|
+
colorWeight: vec3( 1.0 ),
|
|
233
|
+
pathWavelength: pathWavelength,
|
|
256
234
|
} ).toVar();
|
|
257
235
|
|
|
258
236
|
// For smooth surfaces with dispersion, use perfect refraction with spectral IOR
|
|
@@ -264,9 +242,20 @@ export const sampleMicrofacetTransmission = Fn( ( [
|
|
|
264
242
|
const eta = ior;
|
|
265
243
|
const etaRatio = select( entering, float( 1.0 ).div( eta ), eta ).toVar();
|
|
266
244
|
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
245
|
+
// Reuse the path's locked wavelength if any; else sample a new one and tint once.
|
|
246
|
+
If( pathWavelength.greaterThan( 0.0 ), () => {
|
|
247
|
+
|
|
248
|
+
const lockedIOR = iorFromWavelength( ior, dispersion, pathWavelength );
|
|
249
|
+
etaRatio.assign( select( entering, float( 1.0 ).div( lockedIOR ), lockedIOR ) );
|
|
250
|
+
|
|
251
|
+
} ).Else( () => {
|
|
252
|
+
|
|
253
|
+
const spectralSample = SpectralSample.wrap( sampleWavelengthForDispersion( ior, dispersion, RandomValue( rngState ) ) );
|
|
254
|
+
etaRatio.assign( select( entering, float( 1.0 ).div( spectralSample.ior ), spectralSample.ior ) );
|
|
255
|
+
result.colorWeight.assign( spectralSample.colorWeight );
|
|
256
|
+
result.pathWavelength.assign( spectralSample.wavelength );
|
|
257
|
+
|
|
258
|
+
} );
|
|
270
259
|
|
|
271
260
|
// Perfect refraction using surface normal
|
|
272
261
|
const refractDir = refract( V.negate(), N, etaRatio ).toVar();
|
|
@@ -297,11 +286,22 @@ export const sampleMicrofacetTransmission = Fn( ( [
|
|
|
297
286
|
// Compute IOR ratio
|
|
298
287
|
const etaRatio = select( entering, float( 1.0 ).div( ior ), ior ).toVar();
|
|
299
288
|
|
|
300
|
-
//
|
|
289
|
+
// Reuse the path's locked wavelength if any; else sample a new one and tint once.
|
|
301
290
|
If( dispersion.greaterThan( 0.0 ), () => {
|
|
302
291
|
|
|
303
|
-
|
|
304
|
-
|
|
292
|
+
If( pathWavelength.greaterThan( 0.0 ), () => {
|
|
293
|
+
|
|
294
|
+
const lockedIOR = iorFromWavelength( ior, dispersion, pathWavelength );
|
|
295
|
+
etaRatio.assign( select( entering, float( 1.0 ).div( lockedIOR ), lockedIOR ) );
|
|
296
|
+
|
|
297
|
+
} ).Else( () => {
|
|
298
|
+
|
|
299
|
+
const spectralSample = SpectralSample.wrap( sampleWavelengthForDispersion( ior, dispersion, RandomValue( rngState ) ) );
|
|
300
|
+
etaRatio.assign( select( entering, float( 1.0 ).div( spectralSample.ior ), spectralSample.ior ) );
|
|
301
|
+
result.colorWeight.assign( spectralSample.colorWeight );
|
|
302
|
+
result.pathWavelength.assign( spectralSample.wavelength );
|
|
303
|
+
|
|
304
|
+
} );
|
|
305
305
|
|
|
306
306
|
} );
|
|
307
307
|
|
|
@@ -353,13 +353,14 @@ export const sampleMicrofacetTransmission = Fn( ( [
|
|
|
353
353
|
|
|
354
354
|
export const handleTransmission = Fn( ( [
|
|
355
355
|
rayDir, normal, material, entering, rngState,
|
|
356
|
-
currentMediumIOR, previousMediumIOR,
|
|
356
|
+
currentMediumIOR, previousMediumIOR, pathWavelength,
|
|
357
357
|
] ) => {
|
|
358
358
|
|
|
359
359
|
const result = TransmissionResult( {
|
|
360
360
|
direction: vec3( 0.0 ),
|
|
361
361
|
throughput: vec3( 1.0 ),
|
|
362
362
|
didReflect: false,
|
|
363
|
+
pathWavelength: pathWavelength,
|
|
363
364
|
} ).toVar();
|
|
364
365
|
|
|
365
366
|
// Setup surface normal based on ray direction
|
|
@@ -416,10 +417,10 @@ export const handleTransmission = Fn( ( [
|
|
|
416
417
|
|
|
417
418
|
If( doReflect, () => {
|
|
418
419
|
|
|
419
|
-
// Reflection
|
|
420
|
+
// Reflection at a transmissive surface — no wavelength locking
|
|
420
421
|
If( material.roughness.greaterThan( 0.05 ), () => {
|
|
421
422
|
|
|
422
|
-
const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission( V, N, material.ior, material.roughness, entering, float( 0.0 ), xi, rngState ) );
|
|
423
|
+
const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission( V, N, material.ior, material.roughness, entering, float( 0.0 ), xi, rngState, float( 0.0 ) ) );
|
|
423
424
|
result.direction.assign( mtResult.direction );
|
|
424
425
|
|
|
425
426
|
} ).Else( () => {
|
|
@@ -436,125 +437,20 @@ export const handleTransmission = Fn( ( [
|
|
|
436
437
|
// Transmission/refraction path
|
|
437
438
|
If( material.roughness.greaterThan( 0.05 ).or( material.dispersion.greaterThan( 0.0 ) ), () => {
|
|
438
439
|
|
|
439
|
-
const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission( V, N, material.ior, material.roughness, entering, material.dispersion, xi, rngState ) );
|
|
440
|
+
const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission( V, N, material.ior, material.roughness, entering, material.dispersion, xi, rngState, pathWavelength ) );
|
|
441
|
+
result.pathWavelength.assign( mtResult.pathWavelength );
|
|
440
442
|
|
|
441
|
-
// If TIR occurred during sampling, respect it
|
|
442
443
|
If( mtResult.didReflect, () => {
|
|
443
444
|
|
|
445
|
+
// TIR during intended transmission: compensate for selection probability
|
|
444
446
|
result.direction.assign( mtResult.direction );
|
|
445
447
|
result.didReflect.assign( true );
|
|
446
|
-
// TIR during intended transmission: compensate for selection probability
|
|
447
448
|
result.throughput.assign( material.color.xyz.div( max( float( 1.0 ).sub( reflectProb ), 0.05 ) ) );
|
|
448
449
|
|
|
449
450
|
} ).Else( () => {
|
|
450
451
|
|
|
451
452
|
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
|
-
} );
|
|
453
|
+
result.throughput.assign( mtResult.colorWeight );
|
|
558
454
|
|
|
559
455
|
} );
|
|
560
456
|
|
|
@@ -600,10 +496,9 @@ export const handleTransmission = Fn( ( [
|
|
|
600
496
|
|
|
601
497
|
export const handleMaterialTransparency = Fn( ( [
|
|
602
498
|
ray, hitPoint, normal, material, rngState,
|
|
603
|
-
// RenderState fields passed individually (since inout not supported)
|
|
604
499
|
transmissiveTraversals,
|
|
605
|
-
// Precomputed medium IOR values from stack
|
|
606
500
|
currentMediumIOR, previousMediumIOR,
|
|
501
|
+
pathWavelength,
|
|
607
502
|
] ) => {
|
|
608
503
|
|
|
609
504
|
const result = MaterialInteractionResult( {
|
|
@@ -615,6 +510,7 @@ export const handleMaterialTransparency = Fn( ( [
|
|
|
615
510
|
direction: ray.direction,
|
|
616
511
|
throughput: vec3( 1.0 ),
|
|
617
512
|
alpha: float( 1.0 ),
|
|
513
|
+
pathWavelength: pathWavelength,
|
|
618
514
|
} ).toVar();
|
|
619
515
|
|
|
620
516
|
// -----------------------------------------------------------------
|
|
@@ -693,7 +589,7 @@ export const handleMaterialTransparency = Fn( ( [
|
|
|
693
589
|
|
|
694
590
|
const transResult = TransmissionResult.wrap( handleTransmission(
|
|
695
591
|
ray.direction, normal, material, entering, transmissionSeed,
|
|
696
|
-
currentMediumIOR, previousMediumIOR,
|
|
592
|
+
currentMediumIOR, previousMediumIOR, pathWavelength,
|
|
697
593
|
) );
|
|
698
594
|
|
|
699
595
|
result.direction.assign( transResult.direction );
|
|
@@ -703,6 +599,7 @@ export const handleMaterialTransparency = Fn( ( [
|
|
|
703
599
|
result.didReflect.assign( transResult.didReflect );
|
|
704
600
|
result.entering.assign( entering );
|
|
705
601
|
result.alpha.assign( float( 1.0 ).sub( material.transmission ) );
|
|
602
|
+
result.pathWavelength.assign( transResult.pathWavelength );
|
|
706
603
|
|
|
707
604
|
} );
|
|
708
605
|
|
|
@@ -291,8 +291,9 @@ export const generateSampledDirection = Fn( ( [
|
|
|
291
291
|
If( sampled.not(), () => {
|
|
292
292
|
|
|
293
293
|
const entering = dot( V, N ).greaterThan( 0.0 ).toVar();
|
|
294
|
+
// pathWavelength=0 — only direction/PDF are consumed here, throughput goes via handleTransmission
|
|
294
295
|
const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission(
|
|
295
|
-
V, N, material.ior, material.roughness, entering, material.dispersion, xi, rngState,
|
|
296
|
+
V, N, material.ior, material.roughness, entering, material.dispersion, xi, rngState, float( 0.0 ),
|
|
296
297
|
) );
|
|
297
298
|
resultDirection.assign( mtResult.direction );
|
|
298
299
|
resultPdf.assign( max( mtResult.pdf, MIN_PDF ) );
|
|
@@ -619,6 +620,10 @@ export const Trace = Fn( ( [
|
|
|
619
620
|
const mediumStack_ior_2 = float( 1.0 ).toVar();
|
|
620
621
|
const mediumStack_ior_3 = float( 1.0 ).toVar();
|
|
621
622
|
|
|
623
|
+
// Locked at the first dispersive transmission; reused for subsequent transmissions on
|
|
624
|
+
// the path so multi-bounce dispersion doesn't collapse under repeated colorWeight ×.
|
|
625
|
+
const pathWavelength = float( 0.0 ).toVar();
|
|
626
|
+
|
|
622
627
|
// Render state
|
|
623
628
|
const stateTraversals = maxBounceCount.toVar();
|
|
624
629
|
const stateTransmissiveTraversals = transmissiveBounces.toVar();
|
|
@@ -802,7 +807,9 @@ export const Trace = Fn( ( [
|
|
|
802
807
|
currentRay, hitInfo.hitPoint, N, material, rngState,
|
|
803
808
|
stateTransmissiveTraversals,
|
|
804
809
|
currentMediumIOR, previousMediumIOR,
|
|
810
|
+
pathWavelength,
|
|
805
811
|
) ).toVar();
|
|
812
|
+
pathWavelength.assign( interaction.pathWavelength );
|
|
806
813
|
|
|
807
814
|
If( interaction.continueRay, () => {
|
|
808
815
|
|