rayzee 6.0.1 → 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 +2419 -2078
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +48 -52
- 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 +4 -6
- package/src/TSL/Common.js +1 -1
- package/src/TSL/Debugger.js +0 -2
- 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 +4 -37
- 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 +18 -37
- package/src/TSL/PathTracer.js +5 -4
- package/src/TSL/PathTracerCore.js +144 -139
- 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
|
@@ -93,7 +93,6 @@ export class NormalDepth extends RenderStage {
|
|
|
93
93
|
// Own storage nodes — created lazily when data is available
|
|
94
94
|
this._triStorageNode = null;
|
|
95
95
|
this._bvhStorageNode = null;
|
|
96
|
-
this._matStorageNode = null;
|
|
97
96
|
|
|
98
97
|
// Last-seen attribute identities. PathTracer replaces these in-place
|
|
99
98
|
// across model load / BVH rebuild; the compute's bind group is locked
|
|
@@ -101,7 +100,6 @@ export class NormalDepth extends RenderStage {
|
|
|
101
100
|
// when any of them swaps to a new object.
|
|
102
101
|
this._lastTriAttr = null;
|
|
103
102
|
this._lastBvhAttr = null;
|
|
104
|
-
this._lastMatAttr = null;
|
|
105
103
|
|
|
106
104
|
// Compute node — built once when storage buffers are ready
|
|
107
105
|
this._computeNode = null;
|
|
@@ -146,8 +144,6 @@ export class NormalDepth extends RenderStage {
|
|
|
146
144
|
const pt = this.pathTracer;
|
|
147
145
|
if ( ! pt ) return false;
|
|
148
146
|
|
|
149
|
-
const matStorageAttr = pt.materialData.materialStorageAttr;
|
|
150
|
-
|
|
151
147
|
// Detect attribute identity swap (PathTracer.setTriangleData /
|
|
152
148
|
// setBVHData replace the attribute object on growth). The compute
|
|
153
149
|
// node's bind group is locked to the buffer bound at compile time —
|
|
@@ -155,9 +151,8 @@ export class NormalDepth extends RenderStage {
|
|
|
155
151
|
// pointing at the now-discarded buffer, so every traversal misses.
|
|
156
152
|
const triSwapped = pt.triangleStorageAttr && pt.triangleStorageAttr !== this._lastTriAttr;
|
|
157
153
|
const bvhSwapped = pt.bvhStorageAttr && pt.bvhStorageAttr !== this._lastBvhAttr;
|
|
158
|
-
const matSwapped = matStorageAttr && matStorageAttr !== this._lastMatAttr;
|
|
159
154
|
|
|
160
|
-
if ( triSwapped || bvhSwapped
|
|
155
|
+
if ( triSwapped || bvhSwapped ) {
|
|
161
156
|
|
|
162
157
|
// Drop compute + storage nodes so they get rebuilt against the
|
|
163
158
|
// current buffers. Cheap: this only happens on model load.
|
|
@@ -166,7 +161,6 @@ export class NormalDepth extends RenderStage {
|
|
|
166
161
|
this._computeBuilt = false;
|
|
167
162
|
this._triStorageNode = null;
|
|
168
163
|
this._bvhStorageNode = null;
|
|
169
|
-
this._matStorageNode = null;
|
|
170
164
|
this._dirty = true;
|
|
171
165
|
|
|
172
166
|
}
|
|
@@ -187,19 +181,10 @@ export class NormalDepth extends RenderStage {
|
|
|
187
181
|
|
|
188
182
|
}
|
|
189
183
|
|
|
190
|
-
if ( matStorageAttr && ! this._matStorageNode ) {
|
|
191
|
-
|
|
192
|
-
this._matStorageNode = storage(
|
|
193
|
-
matStorageAttr, 'vec4', matStorageAttr.count
|
|
194
|
-
).toReadOnly();
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
|
|
198
184
|
this._lastTriAttr = pt.triangleStorageAttr || this._lastTriAttr;
|
|
199
185
|
this._lastBvhAttr = pt.bvhStorageAttr || this._lastBvhAttr;
|
|
200
|
-
this._lastMatAttr = matStorageAttr || this._lastMatAttr;
|
|
201
186
|
|
|
202
|
-
return !! ( this._triStorageNode && this._bvhStorageNode
|
|
187
|
+
return !! ( this._triStorageNode && this._bvhStorageNode );
|
|
203
188
|
|
|
204
189
|
}
|
|
205
190
|
|
|
@@ -211,7 +196,6 @@ export class NormalDepth extends RenderStage {
|
|
|
211
196
|
|
|
212
197
|
const triStorage = this._triStorageNode;
|
|
213
198
|
const bvhStorage = this._bvhStorageNode;
|
|
214
|
-
const matStorage = this._matStorageNode;
|
|
215
199
|
const camWorld = this.cameraWorldMatrix;
|
|
216
200
|
const camProjInv = this.cameraProjectionMatrixInverse;
|
|
217
201
|
const resW = this.resolutionWidth;
|
|
@@ -249,7 +233,7 @@ export class NormalDepth extends RenderStage {
|
|
|
249
233
|
const ray = Ray( { origin: rayOrigin, direction: rayDirWorld } );
|
|
250
234
|
|
|
251
235
|
// BVH traversal (primary ray only) — wrap result for struct field access
|
|
252
|
-
const hit = HitInfo.wrap( traverseBVH( ray, bvhStorage, triStorage
|
|
236
|
+
const hit = HitInfo.wrap( traverseBVH( ray, bvhStorage, triStorage ) );
|
|
253
237
|
|
|
254
238
|
// Encode: normal * 0.5 + 0.5 in RGB, linear depth in A
|
|
255
239
|
const encodedNormal = hit.normal.mul( 0.5 ).add( 0.5 );
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -188,6 +188,16 @@ export class PathTracer extends RenderStage {
|
|
|
188
188
|
this.spotLightsData = null;
|
|
189
189
|
this.areaLightsData = null;
|
|
190
190
|
|
|
191
|
+
// Spot light gobo (projection mask) DataArrayTexture. Owned externally
|
|
192
|
+
// (GoboManager); ShaderBuilder reads via this property at graph build time
|
|
193
|
+
// and refreshes the bound TextureNode in-place when it changes.
|
|
194
|
+
this.goboMaps = null;
|
|
195
|
+
|
|
196
|
+
// Spot light IES photometric profiles DataArrayTexture. Owned externally
|
|
197
|
+
// (IESManager); ShaderBuilder reads via this property at graph build time
|
|
198
|
+
// and refreshes the bound TextureNode in-place when it changes.
|
|
199
|
+
this.iesProfiles = null;
|
|
200
|
+
|
|
191
201
|
// STBN noise textures
|
|
192
202
|
this.stbnScalarTexture = null;
|
|
193
203
|
this.stbnVec2Texture = null;
|
|
@@ -487,9 +497,6 @@ export class PathTracer extends RenderStage {
|
|
|
487
497
|
this.setInstanceTable( this.sdfs.instanceTable );
|
|
488
498
|
this.materialData.setMaterialData( this.sdfs.materialData );
|
|
489
499
|
|
|
490
|
-
// Update triangle count
|
|
491
|
-
this.totalTriangleCount.value = this.sdfs.triangleCount || 0;
|
|
492
|
-
|
|
493
500
|
// Material texture arrays
|
|
494
501
|
this.materialData.loadTexturesFromSdfs();
|
|
495
502
|
|
|
@@ -588,11 +595,11 @@ export class PathTracer extends RenderStage {
|
|
|
588
595
|
*/
|
|
589
596
|
_updateLightBufferNodes() {
|
|
590
597
|
|
|
591
|
-
// Directional lights (
|
|
598
|
+
// Directional lights (12 floats per light — 8 light fields + gobo {index, signed intensity, scale, pad})
|
|
592
599
|
if ( this.directionalLightsData && this.directionalLightsData.length > 0 ) {
|
|
593
600
|
|
|
594
601
|
this.directionalLightsBufferNode.array = Array.from( this.directionalLightsData );
|
|
595
|
-
this.numDirectionalLights.value = Math.floor( this.directionalLightsData.length /
|
|
602
|
+
this.numDirectionalLights.value = Math.floor( this.directionalLightsData.length / 12 );
|
|
596
603
|
|
|
597
604
|
} else {
|
|
598
605
|
|
|
@@ -624,11 +631,11 @@ export class PathTracer extends RenderStage {
|
|
|
624
631
|
|
|
625
632
|
}
|
|
626
633
|
|
|
627
|
-
// Spot lights (
|
|
634
|
+
// Spot lights (20 floats per light — 14 light fields + gobo {idx, signed intensity} + IES {idx, intensity} + 2 reserved)
|
|
628
635
|
if ( this.spotLightsData && this.spotLightsData.length > 0 ) {
|
|
629
636
|
|
|
630
637
|
this.spotLightsBufferNode.array = Array.from( this.spotLightsData );
|
|
631
|
-
this.numSpotLights.value = Math.floor( this.spotLightsData.length /
|
|
638
|
+
this.numSpotLights.value = Math.floor( this.spotLightsData.length / 20 );
|
|
632
639
|
|
|
633
640
|
} else {
|
|
634
641
|
|
|
@@ -1087,9 +1094,8 @@ export class PathTracer extends RenderStage {
|
|
|
1087
1094
|
/**
|
|
1088
1095
|
* Renders the path tracing pass with accumulation.
|
|
1089
1096
|
* @param {PipelineContext} context - Pipeline context
|
|
1090
|
-
* @param {RenderTarget} writeBuffer - Output render target
|
|
1091
1097
|
*/
|
|
1092
|
-
render( context
|
|
1098
|
+
render( context ) {
|
|
1093
1099
|
|
|
1094
1100
|
if ( ! this.isReady ) return;
|
|
1095
1101
|
|
package/src/TSL/BVHTraversal.js
CHANGED
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
sign,
|
|
14
14
|
min,
|
|
15
15
|
normalize,
|
|
16
|
-
cross,
|
|
17
16
|
mix,
|
|
18
17
|
vec4,
|
|
19
18
|
notEqual,
|
|
@@ -116,7 +115,6 @@ export const traverseBVH = Fn( ( [
|
|
|
116
115
|
ray,
|
|
117
116
|
bvhBuffer,
|
|
118
117
|
triangleBuffer,
|
|
119
|
-
materialBuffer,
|
|
120
118
|
] ) => {
|
|
121
119
|
|
|
122
120
|
const closestHit = HitInfo( {
|
|
@@ -332,7 +330,6 @@ export const traverseBVHShadow = Fn( ( [
|
|
|
332
330
|
ray,
|
|
333
331
|
bvhBuffer,
|
|
334
332
|
triangleBuffer,
|
|
335
|
-
_materialBuffer, // eslint-disable-line no-unused-vars -- kept for call-site compatibility
|
|
336
333
|
maxShadowDist,
|
|
337
334
|
] ) => {
|
|
338
335
|
|
|
@@ -398,10 +395,11 @@ export const traverseBVHShadow = Fn( ( [
|
|
|
398
395
|
closestHit.materialIndex.assign( int( uvData2.z ) );
|
|
399
396
|
closestHit.meshIndex.assign( int( uvData2.w ) );
|
|
400
397
|
|
|
401
|
-
//
|
|
402
|
-
//
|
|
398
|
+
// Hit point is cheap (origin + dir*t). Geometric normal is deferred
|
|
399
|
+
// to traceShadowRay — only the transmission branch needs it, so we
|
|
400
|
+
// skip the cross+normalize for the (much more common) opaque-blocker
|
|
401
|
+
// and alpha-cutout paths. Normal stays vec3(0) from struct init.
|
|
403
402
|
closestHit.hitPoint.assign( ray.origin.add( ray.direction.mul( triResult.x ) ) );
|
|
404
|
-
closestHit.normal.assign( normalize( cross( pB.sub( pA ), pC.sub( pA ) ) ) );
|
|
405
403
|
|
|
406
404
|
// Store barycentrics + triangle index for deferred UV computation.
|
|
407
405
|
// Actual UV interpolation happens in traceShadowRay only when
|
package/src/TSL/Common.js
CHANGED
|
@@ -246,7 +246,7 @@ export const classifyMaterial = Fn( ( [ metalness, roughness, transmission, clea
|
|
|
246
246
|
} );
|
|
247
247
|
|
|
248
248
|
// Dynamic MIS strategy based on material properties
|
|
249
|
-
export const selectOptimalMISStrategy = Fn( ( [ roughness, metalness,
|
|
249
|
+
export const selectOptimalMISStrategy = Fn( ( [ roughness, metalness, bounceIndex, throughput ] ) => {
|
|
250
250
|
|
|
251
251
|
const throughputStrength = maxComponent( { v: throughput } ).toVar();
|
|
252
252
|
|
package/src/TSL/Debugger.js
CHANGED
|
@@ -88,7 +88,6 @@ export const TraceDebugMode = Fn( ( [
|
|
|
88
88
|
ray,
|
|
89
89
|
bvhBuffer,
|
|
90
90
|
triangleBuffer,
|
|
91
|
-
materialBuffer,
|
|
92
91
|
).toVar() );
|
|
93
92
|
|
|
94
93
|
// Case 7: Triangle Tests
|
|
@@ -351,7 +350,6 @@ export const TraceDebugMode = Fn( ( [
|
|
|
351
350
|
bounceRay,
|
|
352
351
|
bvhBuffer,
|
|
353
352
|
triangleBuffer,
|
|
354
|
-
materialBuffer,
|
|
355
353
|
).toVar() );
|
|
356
354
|
|
|
357
355
|
const incoming = vec3( 0.0 ).toVar();
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
Fn,
|
|
6
6
|
vec2,
|
|
7
7
|
vec3,
|
|
8
|
-
vec4,
|
|
9
8
|
float,
|
|
10
9
|
int,
|
|
11
10
|
bool as tslBool,
|
|
@@ -16,7 +15,6 @@ import {
|
|
|
16
15
|
cross,
|
|
17
16
|
length,
|
|
18
17
|
max,
|
|
19
|
-
min,
|
|
20
18
|
sqrt,
|
|
21
19
|
abs,
|
|
22
20
|
clamp,
|
|
@@ -28,9 +26,11 @@ import {
|
|
|
28
26
|
} from 'three/tsl';
|
|
29
27
|
|
|
30
28
|
import { struct } from './patches.js';
|
|
31
|
-
import { MIN_PDF, getDatafromStorageBuffer, powerHeuristic, MATERIAL_SLOTS, MATERIAL_SLOT } from './Common.js';
|
|
29
|
+
import { MIN_PDF, getDatafromStorageBuffer, powerHeuristic, MATERIAL_SLOTS, MATERIAL_SLOT, computeDotProducts } from './Common.js';
|
|
32
30
|
import { RandomValue } from './Random.js';
|
|
33
|
-
import {
|
|
31
|
+
import { calculateMaterialPDFFromDots } from './LightsSampling.js';
|
|
32
|
+
import { evaluateMaterialResponseFromDots } from './MaterialEvaluation.js';
|
|
33
|
+
import { DotProducts } from './Struct.js';
|
|
34
34
|
|
|
35
35
|
// ================================================================================
|
|
36
36
|
// STRUCTS
|
|
@@ -378,7 +378,7 @@ const binarySearchCDF = Fn( ( [ emissiveTriangleBuffer, emissiveOffset, emissive
|
|
|
378
378
|
// `emissiveTriangleBuffer` may be the shared packed light buffer; `emissiveVec4Offset`
|
|
379
379
|
// gives the vec4 offset where emissive entries begin.
|
|
380
380
|
export const sampleEmissiveTriangle = Fn( ( [
|
|
381
|
-
hitPoint, surfaceNormal,
|
|
381
|
+
hitPoint, surfaceNormal,
|
|
382
382
|
rngState,
|
|
383
383
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
384
384
|
triangleBuffer,
|
|
@@ -514,19 +514,19 @@ export const sampleEmissiveTriangle = Fn( ( [
|
|
|
514
514
|
// EMISSIVE TRIANGLE DIRECT LIGHTING CONTRIBUTION
|
|
515
515
|
// ================================================================================
|
|
516
516
|
|
|
517
|
-
// Note:
|
|
518
|
-
//
|
|
519
|
-
//
|
|
517
|
+
// Note: traceShadowRay and calculateRayOffset are passed as Fn parameters to
|
|
518
|
+
// avoid a circular module dependency. BRDF evaluation no longer goes through a
|
|
519
|
+
// callback — we import the FromDots variant directly so we can share dot
|
|
520
|
+
// products with the PDF call below.
|
|
520
521
|
|
|
521
522
|
export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
522
523
|
hitPoint, normal, viewDir, material,
|
|
523
|
-
|
|
524
|
+
bounceIndex, rngState,
|
|
524
525
|
emissiveBoost,
|
|
525
526
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
526
527
|
triangleBuffer,
|
|
527
528
|
// Callback functions to avoid circular deps
|
|
528
529
|
traceShadowRayFn,
|
|
529
|
-
evaluateMaterialResponseFn,
|
|
530
530
|
calculateRayOffsetFn,
|
|
531
531
|
] ) => {
|
|
532
532
|
|
|
@@ -544,7 +544,7 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
|
544
544
|
|
|
545
545
|
// Sample emissive triangle (CDF importance-weighted)
|
|
546
546
|
const emissiveSample = EmissiveSample.wrap( sampleEmissiveTriangle(
|
|
547
|
-
hitPoint, normal,
|
|
547
|
+
hitPoint, normal, rngState,
|
|
548
548
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
549
549
|
triangleBuffer,
|
|
550
550
|
) );
|
|
@@ -566,15 +566,15 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
|
566
566
|
|
|
567
567
|
// Trace shadow ray
|
|
568
568
|
const shadowDist = emissiveSample.distance.sub( 0.001 );
|
|
569
|
-
const visibility = traceShadowRayFn( rayOrigin, emissiveSample.direction, shadowDist
|
|
569
|
+
const visibility = traceShadowRayFn( rayOrigin, emissiveSample.direction, shadowDist );
|
|
570
570
|
|
|
571
571
|
If( visibility.greaterThan( 0.0 ), () => {
|
|
572
572
|
|
|
573
|
-
//
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const brdfPdf =
|
|
573
|
+
// Share H + dot products between BRDF eval and PDF (computeDotProducts
|
|
574
|
+
// would otherwise run twice with identical inputs).
|
|
575
|
+
const dots = DotProducts.wrap( computeDotProducts( normal, viewDir, emissiveSample.direction ) );
|
|
576
|
+
const brdfValue = evaluateMaterialResponseFromDots( material, dots );
|
|
577
|
+
const brdfPdf = calculateMaterialPDFFromDots( material, dots );
|
|
578
578
|
|
|
579
579
|
// MIS weight: balance light sampling vs BRDF sampling
|
|
580
580
|
const misWeight = select(
|
|
@@ -602,26 +602,24 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
|
602
602
|
|
|
603
603
|
} );
|
|
604
604
|
|
|
605
|
-
// Wrapper
|
|
605
|
+
// Wrapper that returns just the contribution vec3
|
|
606
606
|
export const calculateEmissiveTriangleContribution = Fn( ( [
|
|
607
607
|
hitPoint, normal, viewDir, material,
|
|
608
|
-
|
|
608
|
+
bounceIndex, rngState,
|
|
609
609
|
emissiveBoost,
|
|
610
610
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
611
611
|
triangleBuffer,
|
|
612
612
|
traceShadowRayFn,
|
|
613
|
-
evaluateMaterialResponseFn,
|
|
614
613
|
calculateRayOffsetFn,
|
|
615
614
|
] ) => {
|
|
616
615
|
|
|
617
616
|
const result = EmissiveContributionResult.wrap( calculateEmissiveTriangleContributionDebug(
|
|
618
617
|
hitPoint, normal, viewDir, material,
|
|
619
|
-
|
|
618
|
+
bounceIndex, rngState,
|
|
620
619
|
emissiveBoost,
|
|
621
620
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
622
621
|
triangleBuffer,
|
|
623
622
|
traceShadowRayFn,
|
|
624
|
-
evaluateMaterialResponseFn,
|
|
625
623
|
calculateRayOffsetFn,
|
|
626
624
|
) );
|
|
627
625
|
return result.contribution;
|
package/src/TSL/Environment.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fn, wgslFn, vec2, vec4, float, int, If, texture,
|
|
1
|
+
import { Fn, wgslFn, vec2, vec4, float, int, If, texture, dot, sin, sqrt, floor, fract, min, max, mix, clamp } from 'three/tsl';
|
|
2
2
|
|
|
3
3
|
import { REC709_LUMINANCE_COEFFICIENTS } from './Common.js';
|
|
4
4
|
|
|
@@ -28,17 +28,6 @@ export const equirectUvToDirection = /*@__PURE__*/ wgslFn( `
|
|
|
28
28
|
}
|
|
29
29
|
` );
|
|
30
30
|
|
|
31
|
-
// Calculate PDF for uniform sphere sampling with Jacobian
|
|
32
|
-
export const equirectDirectionPdf = /*@__PURE__*/ wgslFn( `
|
|
33
|
-
fn equirectDirectionPdf( direction: vec3f, environmentMatrix: mat4x4f ) -> f32 {
|
|
34
|
-
let uv = equirectDirectionToUv( direction, environmentMatrix );
|
|
35
|
-
let theta = uv.y * 3.14159265358979323846f;
|
|
36
|
-
let sinTheta = sin( theta );
|
|
37
|
-
if ( sinTheta == 0.0f ) { return 0.0f; }
|
|
38
|
-
return 1.0f / ( 6.28318530717958647692f * 3.14159265358979323846f * sinTheta );
|
|
39
|
-
}
|
|
40
|
-
`, [ equirectDirectionToUv ] );
|
|
41
|
-
|
|
42
31
|
// Evaluate PDF for a given direction (for MIS)
|
|
43
32
|
// Returns vec4(color.rgb, pdf) since TSL cannot use inout params
|
|
44
33
|
// Uses MIS-compensated PDF (Karlík et al. 2019): max(0, lum - delta) / compensatedTotalSum
|
|
@@ -64,7 +53,12 @@ export const sampleEquirect = Fn( ( [ environment, direction, environmentMatrix,
|
|
|
64
53
|
const compensatedWeight = max( float( 0.0 ), weightedLum.sub( envCompensationDelta ) ).toVar();
|
|
65
54
|
const pdf = compensatedWeight.div( envTotalSum ).toVar();
|
|
66
55
|
|
|
67
|
-
|
|
56
|
+
// Inline equirectDirectionPdf using the uv + sinTheta already in scope —
|
|
57
|
+
// the helper would otherwise re-derive uv via atan2+acos and recompute sin.
|
|
58
|
+
const dirPdf = sinTheta.greaterThan( 0.0 ).select(
|
|
59
|
+
float( 1.0 ).div( float( 2.0 * Math.PI * Math.PI ).mul( sinTheta ) ),
|
|
60
|
+
float( 0.0 )
|
|
61
|
+
).toVar();
|
|
68
62
|
const finalPdf = float( envResolution.x ).mul( float( envResolution.y ) ).mul( pdf ).mul( dirPdf ).toVar();
|
|
69
63
|
|
|
70
64
|
result.assign( vec4( color, finalPdf ) );
|
|
@@ -138,7 +132,12 @@ export const sampleEquirectProbability = Fn( ( [
|
|
|
138
132
|
const compensatedWeight = max( float( 0.0 ), weightedLum.sub( envCompensationDelta ) ).toVar();
|
|
139
133
|
const pdf = compensatedWeight.div( envTotalSum ).toVar();
|
|
140
134
|
|
|
141
|
-
|
|
135
|
+
// Inline equirectDirectionPdf — uv + sinTheta are already in scope, so we
|
|
136
|
+
// skip the helper's redundant uv-from-direction + sin recompute.
|
|
137
|
+
const dirPdf = sinTheta.greaterThan( 0.0 ).select(
|
|
138
|
+
float( 1.0 ).div( float( 2.0 * Math.PI * Math.PI ).mul( sinTheta ) ),
|
|
139
|
+
float( 0.0 )
|
|
140
|
+
).toVar();
|
|
142
141
|
const finalPdf = float( envResolution.x ).mul( float( envResolution.y ) ).mul( pdf ).mul( dirPdf ).toVar();
|
|
143
142
|
|
|
144
143
|
return vec4( direction, finalPdf );
|
|
@@ -163,3 +162,50 @@ export const sampleEnvironment = /*@__PURE__*/ wgslFn( `
|
|
|
163
162
|
return texSample * environmentIntensity;
|
|
164
163
|
}
|
|
165
164
|
`, [ equirectDirectionToUv ] );
|
|
165
|
+
|
|
166
|
+
// Port of three.js PR #33611 (getGroundProjectedNormal) adapted from rasterizer fragment math
|
|
167
|
+
// (cameraPosition + positionWorld) to path-tracer ray math (rayOrigin + rayDirection). When the
|
|
168
|
+
// ray misses the projection sphere it falls back to rayDirection so distant scenes degrade gracefully.
|
|
169
|
+
export const getGroundProjectedDirection = Fn( ( [ rayOrigin, rayDirection, radius, height ] ) => {
|
|
170
|
+
|
|
171
|
+
const p = rayDirection.toConst();
|
|
172
|
+
const camPos = rayOrigin.toVar();
|
|
173
|
+
camPos.y.subAssign( height );
|
|
174
|
+
|
|
175
|
+
const r2 = radius.mul( radius ).toConst();
|
|
176
|
+
const b = camPos.dot( p ).toConst();
|
|
177
|
+
const c = camPos.dot( camPos ).sub( r2 ).toConst();
|
|
178
|
+
const h = b.mul( b ).sub( c ).toConst();
|
|
179
|
+
|
|
180
|
+
const projected = rayDirection.toVar();
|
|
181
|
+
|
|
182
|
+
If( h.greaterThanEqual( 0.0 ), () => {
|
|
183
|
+
|
|
184
|
+
const tSphere = sqrt( h ).sub( b ).toVar();
|
|
185
|
+
|
|
186
|
+
// Disk sits at world y=0; the camPos shift only repositions the sphere.
|
|
187
|
+
const tDisk = float( 1e6 ).toVar();
|
|
188
|
+
const py = p.y.toConst();
|
|
189
|
+
If( py.lessThanEqual( 0.0 ), () => {
|
|
190
|
+
|
|
191
|
+
const t = rayOrigin.y.negate().div( py ).toConst();
|
|
192
|
+
const q = rayOrigin.add( p.mul( t ) ).toConst();
|
|
193
|
+
If( q.dot( q ).lessThan( r2 ), () => {
|
|
194
|
+
|
|
195
|
+
tDisk.assign( t );
|
|
196
|
+
|
|
197
|
+
} );
|
|
198
|
+
|
|
199
|
+
} );
|
|
200
|
+
|
|
201
|
+
If( tSphere.greaterThan( 0.0 ), () => {
|
|
202
|
+
|
|
203
|
+
projected.assign( camPos.add( p.mul( min( tSphere, tDisk ) ) ).div( radius ) );
|
|
204
|
+
|
|
205
|
+
} );
|
|
206
|
+
|
|
207
|
+
} );
|
|
208
|
+
|
|
209
|
+
return projected;
|
|
210
|
+
|
|
211
|
+
} );
|
package/src/TSL/Fresnel.js
CHANGED
|
@@ -1,25 +1,34 @@
|
|
|
1
|
-
import { Fn, float, vec3, max,
|
|
1
|
+
import { Fn, float, vec3, max, clamp, sqrt } from 'three/tsl';
|
|
2
2
|
|
|
3
3
|
const EPSILON = 1e-6;
|
|
4
4
|
|
|
5
|
+
// Schlick exponent factored as 4 multiplies — pow(x, 5.0) compiles to
|
|
6
|
+
// exp2(5*log2(x)) on most backends, far slower than (x²)²·x.
|
|
7
|
+
const pow5 = ( c ) => {
|
|
8
|
+
|
|
9
|
+
const c2 = c.mul( c );
|
|
10
|
+
return c2.mul( c2 ).mul( c );
|
|
11
|
+
|
|
12
|
+
};
|
|
13
|
+
|
|
5
14
|
export const fresnel = Fn( ( [ f0, NoV, roughness ] ) => {
|
|
6
15
|
|
|
7
16
|
const maxR = max( vec3( float( 1.0 ).sub( roughness ) ), f0 );
|
|
8
|
-
return f0.add( maxR.sub( f0 ).mul(
|
|
17
|
+
return f0.add( maxR.sub( f0 ).mul( pow5( float( 1.0 ).sub( NoV ) ) ) );
|
|
9
18
|
|
|
10
19
|
} );
|
|
11
20
|
|
|
12
21
|
export const fresnelSchlickFloat = Fn( ( [ cosTheta, F0 ] ) => {
|
|
13
22
|
|
|
14
23
|
const clampedCos = clamp( cosTheta, 0.0, 1.0 );
|
|
15
|
-
return F0.add( float( 1.0 ).sub( F0 ).mul(
|
|
24
|
+
return F0.add( float( 1.0 ).sub( F0 ).mul( pow5( float( 1.0 ).sub( clampedCos ) ) ) );
|
|
16
25
|
|
|
17
26
|
} );
|
|
18
27
|
|
|
19
28
|
export const fresnelSchlick = Fn( ( [ cosTheta, F0 ] ) => {
|
|
20
29
|
|
|
21
30
|
const clampedCos = clamp( cosTheta, 0.0, 1.0 );
|
|
22
|
-
return F0.add( vec3( 1.0 ).sub( F0 ).mul(
|
|
31
|
+
return F0.add( vec3( 1.0 ).sub( F0 ).mul( pow5( float( 1.0 ).sub( clampedCos ) ) ) );
|
|
23
32
|
|
|
24
33
|
} );
|
|
25
34
|
|