rayzee 7.28.0 → 7.28.2
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 +2 -0
- package/dist/rayzee.es.js +2398 -2389
- package/dist/rayzee.umd.js +32 -32
- package/package.json +1 -1
- package/src/EngineDefaults.js +1 -1
- package/src/PathTracerApp.js +3 -0
- package/src/Processor/AssetLoader.js +24 -12
- package/src/Processor/QueueManager.js +9 -3
- package/src/Stages/PathTracer.js +20 -18
- package/src/TSL/CompactKernel.js +16 -3
- package/src/TSL/EmissiveSampling.js +5 -5
- package/src/TSL/LightsDirect.js +5 -10
- package/src/TSL/LightsSampling.js +154 -150
- package/src/TSL/ShadeKernel.js +18 -10
package/package.json
CHANGED
package/src/EngineDefaults.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
export const RENDER_PROFILES = Object.freeze( {
|
|
12
12
|
viewer: Object.freeze( {
|
|
13
|
-
areaLightIntensityScale: 0.1, //
|
|
13
|
+
areaLightIntensityScale: 0.1, // scales glTF placeholder area-light power (viewer tuning)
|
|
14
14
|
environmentRotation: 270.0, // degrees
|
|
15
15
|
toneMapping: 4, // ACESFilmicToneMapping
|
|
16
16
|
saturation: 1.2, // compensates for ACES desaturation — see Compositor
|
package/src/PathTracerApp.js
CHANGED
|
@@ -728,6 +728,9 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
728
728
|
* by reference, and any ancestor transform is baked in. Later edits to `object3d` do not
|
|
729
729
|
* reach the render — mutate {@link sceneModel}, then {@link refitBVH}/{@link refitBLASes}.
|
|
730
730
|
*
|
|
731
|
+
* Lights keep three.js units — `RectAreaLight.intensity` in nits, point/spot in candela — and are
|
|
732
|
+
* converted to radiant power on the copy. `areaLightIntensityScale` does not apply here.
|
|
733
|
+
*
|
|
731
734
|
* @param {import('three').Object3D} object3d - The Object3D to render; left untouched.
|
|
732
735
|
* @param {string} [name='object3d'] - Display name for the object
|
|
733
736
|
*/
|
|
@@ -51,6 +51,19 @@ function standInForSplit( source ) {
|
|
|
51
51
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// three.js nits → the engine's radiant power, inverting areaLightRadiance.
|
|
55
|
+
function areaLightPowerFactor( node, width, height, userData ) {
|
|
56
|
+
|
|
57
|
+
if ( ! ( userData.normalize ?? true ) ) return Math.PI;
|
|
58
|
+
|
|
59
|
+
const shapeFactor = userData.shape === 'ellipse' || userData.shape === 'disk' ? Math.PI / 4 : 1;
|
|
60
|
+
const scale = node.getWorldScale( new Vector3() );
|
|
61
|
+
// abs: a mirrored ancestor decomposes negative; the serializer takes the area as |u × v|.
|
|
62
|
+
const factor = Math.PI * shapeFactor * Math.abs( width * scale.x * height * scale.y );
|
|
63
|
+
return Number.isFinite( factor ) ? factor : Math.PI;
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
54
67
|
/**
|
|
55
68
|
* AssetLoader class - handles loading of 3D models, environment maps, and archives
|
|
56
69
|
*/
|
|
@@ -1726,7 +1739,6 @@ export class AssetLoader extends EventDispatcher {
|
|
|
1726
1739
|
|
|
1727
1740
|
processModelObjects( model ) {
|
|
1728
1741
|
|
|
1729
|
-
let visitedAreaLights = [];
|
|
1730
1742
|
// Split after the walk: traverse() caches children.length, so splitting in place
|
|
1731
1743
|
// shifts later siblings down a slot and skips one.
|
|
1732
1744
|
const multiMaterialMeshes = [];
|
|
@@ -1734,9 +1746,11 @@ export class AssetLoader extends EventDispatcher {
|
|
|
1734
1746
|
|
|
1735
1747
|
const userData = object.userData;
|
|
1736
1748
|
|
|
1737
|
-
|
|
1749
|
+
// An adopted light carries three.js units; convert as point/spot are below.
|
|
1750
|
+
if ( object.isRectAreaLight && ! userData.__radianceConverted ) {
|
|
1738
1751
|
|
|
1739
|
-
|
|
1752
|
+
object.intensity *= areaLightPowerFactor( object, object.width, object.height, userData );
|
|
1753
|
+
userData.__radianceConverted = true;
|
|
1740
1754
|
|
|
1741
1755
|
}
|
|
1742
1756
|
|
|
@@ -1759,23 +1773,21 @@ export class AssetLoader extends EventDispatcher {
|
|
|
1759
1773
|
|
|
1760
1774
|
if ( userData.type === 'RectAreaLight' ) {
|
|
1761
1775
|
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
// Viewer tuning, not physics — see RENDER_PROFILES.
|
|
1776
|
+
const normalize = userData.normalize ?? true;
|
|
1777
|
+
const shape = userData.shape ?? 'rectangle';
|
|
1778
|
+
const power = userData.intensity * areaLightPowerFactor( object, userData.width, userData.height, userData );
|
|
1766
1779
|
const light = new RectAreaLight(
|
|
1767
1780
|
new Color( ...userData.color ),
|
|
1768
|
-
|
|
1781
|
+
power * this._profile.areaLightIntensityScale,
|
|
1769
1782
|
userData.width,
|
|
1770
1783
|
userData.height
|
|
1771
1784
|
);
|
|
1772
|
-
light.userData.normalize =
|
|
1785
|
+
light.userData.normalize = normalize;
|
|
1773
1786
|
light.userData.spread = Number.isFinite( userData.spread ) ? userData.spread : Math.PI;
|
|
1774
|
-
light.userData.shape =
|
|
1775
|
-
light.
|
|
1787
|
+
light.userData.shape = shape;
|
|
1788
|
+
light.userData.__radianceConverted = true; // already power, and traverse() reaches it
|
|
1776
1789
|
light.name = userData.name;
|
|
1777
1790
|
object.add( light );
|
|
1778
|
-
visitedAreaLights.push( light.uuid );
|
|
1779
1791
|
|
|
1780
1792
|
}
|
|
1781
1793
|
|
|
@@ -27,9 +27,15 @@ export const COUNTER = {
|
|
|
27
27
|
// bits restricted to pixels that hit geometry, so the stop can require both fractions.
|
|
28
28
|
GEOMETRY_COUNT: 5,
|
|
29
29
|
CONVERGED_GEOMETRY_COUNT: 6,
|
|
30
|
-
|
|
30
|
+
// Survivors' summed throughput (max channel, fixed-point ENERGY_SCALE). The early exit gates on this,
|
|
31
|
+
// not the count: past Russian roulette a few survivors carry the weight of many.
|
|
32
|
+
ACTIVE_ENERGY: 7,
|
|
33
|
+
COUNT: 8,
|
|
31
34
|
};
|
|
32
35
|
|
|
36
|
+
export const ENERGY_SCALE = 64;
|
|
37
|
+
export const ENERGY_RAY_CLAMP = 65535;
|
|
38
|
+
|
|
33
39
|
/** Ray flag bits packed into rayBounceFlags (uint) */
|
|
34
40
|
export const RAY_FLAG = {
|
|
35
41
|
BOUNCE_MASK: 0xFF, // bits 0-7: bounce count (0-255)
|
|
@@ -81,10 +87,10 @@ export class QueueManager {
|
|
|
81
87
|
this._countersAttr = new StorageInstancedBufferAttribute( new Uint32Array( COUNTER.COUNT ), 1 );
|
|
82
88
|
this.counters = storage( this._countersAttr, 'uint' ).toAtomic();
|
|
83
89
|
|
|
84
|
-
// per-bounce
|
|
90
|
+
// per-bounce snapshots for the async readback: [0, MAX) ACTIVE_RAY_COUNT, [MAX, 2·MAX) ACTIVE_ENERGY
|
|
85
91
|
this.MAX_BOUNCE_SNAPSHOTS = 32;
|
|
86
92
|
this._bounceCountsAttr = new StorageInstancedBufferAttribute(
|
|
87
|
-
new Uint32Array( this.MAX_BOUNCE_SNAPSHOTS ), 1,
|
|
93
|
+
new Uint32Array( 2 * this.MAX_BOUNCE_SNAPSHOTS ), 1,
|
|
88
94
|
);
|
|
89
95
|
this.bounceCounts = storage( this._bounceCountsAttr, 'uint' );
|
|
90
96
|
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -8,7 +8,7 @@ import { uniform, texture, storage } from 'three/tsl';
|
|
|
8
8
|
import { StorageInstancedBufferAttribute } from 'three/webgpu';
|
|
9
9
|
import { PathTracerStage } from './PathTracerStage.js';
|
|
10
10
|
import { PackedRayBuffer, GBUFFER_STRIDE, RAY_STRIDE, HIT_STRIDE, freeStorageAttribute } from '../Processor/PackedRayBuffer.js';
|
|
11
|
-
import { QueueManager, COUNTER } from '../Processor/QueueManager.js';
|
|
11
|
+
import { QueueManager, COUNTER, ENERGY_SCALE } from '../Processor/QueueManager.js';
|
|
12
12
|
import { VRAMTracker } from '../Processor/VRAMTracker.js';
|
|
13
13
|
import { KernelManager } from '../Processor/KernelManager.js';
|
|
14
14
|
import { buildGenerateKernel, GENERATE_WG_SIZE } from '../TSL/GenerateKernel.js';
|
|
@@ -85,6 +85,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
85
85
|
this._useSubgroupCompact = false;
|
|
86
86
|
|
|
87
87
|
this._lastBounceCounts = null;
|
|
88
|
+
this._lastBounceEnergy = null;
|
|
88
89
|
// maxBounces the curve was measured at; the curve is ignored once this no longer matches (-1 = none).
|
|
89
90
|
this._lastBounceCountsBudget = - 1;
|
|
90
91
|
this._readbackPending = false;
|
|
@@ -95,10 +96,11 @@ export class PathTracer extends PathTracerStage {
|
|
|
95
96
|
// Whether the survivor curve may be trusted to SIZE the dispatch. False during/after camera motion
|
|
96
97
|
// (or resize) until a readback measured at the settled view re-validates it — full-size until then so
|
|
97
98
|
// a stale/mid-motion curve can't under-size the row-major list and drop the bottom rows. The curve is
|
|
98
|
-
// still used for the per-bounce early-exit regardless (
|
|
99
|
+
// still used for the per-bounce early-exit regardless (it only trims bounces holding negligible energy).
|
|
99
100
|
this._curveSizingValid = false;
|
|
100
|
-
//
|
|
101
|
-
|
|
101
|
+
// Early exit once the survivors' summed throughput is below this fraction of a full frame's; -1
|
|
102
|
+
// disables. Never a ray count: after Russian roulette a handful of survivors carry real weight.
|
|
103
|
+
this._bounceEarlyExitThreshold = 1e-4;
|
|
102
104
|
|
|
103
105
|
// Tier-1 convergence early-stop: fraction of pixels converged in the last settled-view readback, and a
|
|
104
106
|
// single-flight guard for its async counter read. Zeroed on reset/camera-move/resize; never refreshed
|
|
@@ -400,6 +402,9 @@ export class PathTracer extends PathTracerStage {
|
|
|
400
402
|
|
|
401
403
|
}
|
|
402
404
|
|
|
405
|
+
const energyCurve = this._lastBounceEnergy;
|
|
406
|
+
const exitEnergy = this._bounceEarlyExitThreshold >= 0 ? this._bounceEarlyExitThreshold * maxRays * ENERGY_SCALE : - 1;
|
|
407
|
+
|
|
403
408
|
for ( let bounce = 0; bounce <= loopBound; bounce ++ ) {
|
|
404
409
|
|
|
405
410
|
this._wfCurrentBounce.value = bounce;
|
|
@@ -474,10 +479,11 @@ export class PathTracer extends PathTracerStage {
|
|
|
474
479
|
|
|
475
480
|
// Early-exit on last frame's per-bounce snapshot (single-chunk only; curveReliableUpto=0 disables it for multi-chunk).
|
|
476
481
|
if (
|
|
477
|
-
|
|
482
|
+
exitEnergy >= 0
|
|
483
|
+
&& bounce < curveReliableUpto
|
|
478
484
|
&& bounce < loopBound
|
|
479
|
-
&&
|
|
480
|
-
&&
|
|
485
|
+
&& energyCurve?.[ bounce ] !== undefined
|
|
486
|
+
&& energyCurve[ bounce ] <= exitEnergy
|
|
481
487
|
) {
|
|
482
488
|
|
|
483
489
|
break;
|
|
@@ -732,6 +738,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
732
738
|
this._readbackPending = true;
|
|
733
739
|
const gen = this._readbackGeneration;
|
|
734
740
|
const budget = this.maxBounces.value;
|
|
741
|
+
const n = this._queueManager.MAX_BOUNCE_SNAPSHOTS;
|
|
735
742
|
this.renderer.getArrayBufferAsync( attr ).then( ( buf ) => {
|
|
736
743
|
|
|
737
744
|
// Drop counts measured at a now-stale generation (a resize or camera move happened mid-flight).
|
|
@@ -739,7 +746,9 @@ export class PathTracer extends PathTracerStage {
|
|
|
739
746
|
// happened before it resolved, so its counts match the current view — safe to size from.
|
|
740
747
|
if ( gen === this._readbackGeneration ) {
|
|
741
748
|
|
|
742
|
-
|
|
749
|
+
const all = new Uint32Array( buf.slice( 0 ) );
|
|
750
|
+
this._lastBounceCounts = all.subarray( 0, n );
|
|
751
|
+
this._lastBounceEnergy = all.subarray( n, 2 * n );
|
|
743
752
|
this._lastBounceCountsBudget = budget;
|
|
744
753
|
this._curveSizingValid = true;
|
|
745
754
|
|
|
@@ -826,6 +835,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
826
835
|
// until the readback re-measures at the new size; bump the generation so any readback already
|
|
827
836
|
// in flight (carrying the old-resolution counts) is discarded when it resolves.
|
|
828
837
|
this._lastBounceCounts = null;
|
|
838
|
+
this._lastBounceEnergy = null;
|
|
829
839
|
this._lastBounceCountsBudget = - 1;
|
|
830
840
|
this._curveSizingValid = false;
|
|
831
841
|
this._readbackFrameCounter = 0;
|
|
@@ -861,11 +871,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
861
871
|
this._wfRenderWidth.value = w;
|
|
862
872
|
this._wfRenderHeight.value = h;
|
|
863
873
|
this._updateChunkLayout();
|
|
864
|
-
if ( this._bounceEarlyExitThreshold !== - 1 ) {
|
|
865
|
-
|
|
866
|
-
this._bounceEarlyExitThreshold = Math.max( 100, Math.floor( ( w * h ) / 1000 ) );
|
|
867
|
-
|
|
868
|
-
}
|
|
869
874
|
|
|
870
875
|
}
|
|
871
876
|
|
|
@@ -969,11 +974,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
969
974
|
// every per-ray kernel's dispatch per chunk; this is just a valid starting grid (always ≤ B).
|
|
970
975
|
const maxRays = this._chunkRows * w;
|
|
971
976
|
|
|
972
|
-
if ( this._bounceEarlyExitThreshold !== - 1 ) {
|
|
973
|
-
|
|
974
|
-
this._bounceEarlyExitThreshold = Math.max( 100, Math.floor( ( w * h ) / 1000 ) );
|
|
975
|
-
|
|
976
|
-
}
|
|
977
977
|
|
|
978
978
|
// Per-path buffers (RAY/HIT/rng) sized to the budget B and indexed by LOCAL slot r ∈ [0,B). _cap = B is
|
|
979
979
|
// baked into the SoA stride but never changes with resolution, so this build happens once (model load).
|
|
@@ -1068,6 +1068,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1068
1068
|
const cnt = atomicLoad( counters.element( uint( COUNTER.ACTIVE_RAY_COUNT ) ) );
|
|
1069
1069
|
const slot = uint( wfCurrentBounce ).clamp( uint( 0 ), uint( qm.MAX_BOUNCE_SNAPSHOTS - 1 ) );
|
|
1070
1070
|
bounceCountsBuf.element( slot ).assign( cnt );
|
|
1071
|
+
bounceCountsBuf.element( slot.add( uint( qm.MAX_BOUNCE_SNAPSHOTS ) ) ).assign( atomicLoad( counters.element( uint( COUNTER.ACTIVE_ENERGY ) ) ) );
|
|
1071
1072
|
// Also set ENTERING_COUNT for the next bounce; the full-dispatch path's enterFull overrides it.
|
|
1072
1073
|
atomicStore( counters.element( uint( COUNTER.ENTERING_COUNT ) ), cnt );
|
|
1073
1074
|
|
|
@@ -1522,6 +1523,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1522
1523
|
|
|
1523
1524
|
const slot = uint( wfCurrentBounce ).clamp( uint( 0 ), uint( qm.MAX_BOUNCE_SNAPSHOTS - 1 ) );
|
|
1524
1525
|
bounceCountsBuf.element( slot ).assign( active );
|
|
1526
|
+
bounceCountsBuf.element( slot.add( uint( qm.MAX_BOUNCE_SNAPSHOTS ) ) ).assign( atomicLoad( counters.element( uint( COUNTER.ACTIVE_ENERGY ) ) ) );
|
|
1525
1527
|
atomicStore( counters.element( uint( COUNTER.ENTERING_COUNT ) ), active );
|
|
1526
1528
|
|
|
1527
1529
|
} );
|
package/src/TSL/CompactKernel.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
-
Fn, uint, select,
|
|
6
|
+
Fn, uint, select, max, min,
|
|
7
7
|
If,
|
|
8
8
|
instanceIndex,
|
|
9
9
|
atomicAdd, atomicLoad,
|
|
@@ -11,11 +11,20 @@ import {
|
|
|
11
11
|
Return,
|
|
12
12
|
} from 'three/tsl';
|
|
13
13
|
|
|
14
|
-
import { readRayBounceFlags } from '../Processor/PackedRayBuffer.js';
|
|
15
|
-
import { RAY_FLAG, COUNTER } from '../Processor/QueueManager.js';
|
|
14
|
+
import { readRayBounceFlags, readRayThroughput } from '../Processor/PackedRayBuffer.js';
|
|
15
|
+
import { RAY_FLAG, COUNTER, ENERGY_SCALE, ENERGY_RAY_CLAMP } from '../Processor/QueueManager.js';
|
|
16
16
|
|
|
17
17
|
const WG_SIZE = 256;
|
|
18
18
|
|
|
19
|
+
// Survivor's remaining throughput as fixed-point u32; clamped so a frame's sum cannot wrap.
|
|
20
|
+
const rayEnergy = ( rayBufferRO, rayID ) => {
|
|
21
|
+
|
|
22
|
+
const t = readRayThroughput( rayBufferRO, rayID );
|
|
23
|
+
const m = max( max( t.x, t.y ), t.z ).max( 0.0 );
|
|
24
|
+
return min( uint( m.mul( ENERGY_SCALE ).add( 0.5 ) ), uint( ENERGY_RAY_CLAMP ) );
|
|
25
|
+
|
|
26
|
+
};
|
|
27
|
+
|
|
19
28
|
export function buildCompactKernel( params ) {
|
|
20
29
|
|
|
21
30
|
const {
|
|
@@ -46,6 +55,7 @@ export function buildCompactKernel( params ) {
|
|
|
46
55
|
|
|
47
56
|
const writeIdx = atomicAdd( counters.element( uint( COUNTER.ACTIVE_RAY_COUNT ) ), uint( 1 ) );
|
|
48
57
|
activeIndicesWriteRW.element( writeIdx ).assign( rayID );
|
|
58
|
+
atomicAdd( counters.element( uint( COUNTER.ACTIVE_ENERGY ) ), rayEnergy( rayBufferRO, rayID ) );
|
|
49
59
|
|
|
50
60
|
} );
|
|
51
61
|
|
|
@@ -80,10 +90,12 @@ export function buildCompactSubgroupKernel( params ) {
|
|
|
80
90
|
const flags = readRayBounceFlags( rayBufferRO, rayID );
|
|
81
91
|
const isActive = inRange.and( flags.bitAnd( uint( RAY_FLAG.ACTIVE ) ).notEqual( uint( 0 ) ) );
|
|
82
92
|
const activeU = select( isActive, uint( 1 ), uint( 0 ) );
|
|
93
|
+
const energyU = select( isActive, rayEnergy( rayBufferRO, rayID ), uint( 0 ) );
|
|
83
94
|
|
|
84
95
|
// .toVar() materializes the subgroup ops at uniform control flow; inlining into the divergent If(isActive) write is rejected by WGSL.
|
|
85
96
|
const localOffset = subgroupExclusiveAdd( activeU ).toVar();
|
|
86
97
|
const sgCount = subgroupAdd( activeU ).toVar();
|
|
98
|
+
const sgEnergy = subgroupAdd( energyU ).toVar();
|
|
87
99
|
|
|
88
100
|
// laneId via exclusiveAdd(1) since TSL lacks subgroup_invocation_id; lane 0 does the single per-subgroup atomicAdd.
|
|
89
101
|
const laneId = subgroupExclusiveAdd( uint( 1 ) ).toVar();
|
|
@@ -91,6 +103,7 @@ export function buildCompactSubgroupKernel( params ) {
|
|
|
91
103
|
If( laneId.equal( uint( 0 ) ), () => {
|
|
92
104
|
|
|
93
105
|
base.assign( atomicAdd( counters.element( uint( COUNTER.ACTIVE_RAY_COUNT ) ), sgCount ) );
|
|
106
|
+
atomicAdd( counters.element( uint( COUNTER.ACTIVE_ENERGY ) ), sgEnergy );
|
|
94
107
|
|
|
95
108
|
} );
|
|
96
109
|
const sgBase = subgroupBroadcast( base, uint( 0 ) ).toVar();
|
|
@@ -527,7 +527,7 @@ export const sampleEmissiveTriangle = Fn( ( [
|
|
|
527
527
|
// products with the PDF call below.
|
|
528
528
|
|
|
529
529
|
export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
530
|
-
hitPoint, normal, viewDir, material,
|
|
530
|
+
hitPoint, normal, geomNormal, viewDir, material,
|
|
531
531
|
bounceIndex, rngState,
|
|
532
532
|
pixelCoord, resolution, frame, dimBase,
|
|
533
533
|
emissiveBoost,
|
|
@@ -565,10 +565,10 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
|
565
565
|
// Check geometric validity
|
|
566
566
|
const NoL = max( float( 0.0 ), dot( normal, emissiveSample.direction ) );
|
|
567
567
|
|
|
568
|
-
If( NoL.greaterThan( 0.0 ), () => {
|
|
568
|
+
If( NoL.greaterThan( 0.0 ).and( dot( emissiveSample.direction, geomNormal ).greaterThan( 0.0 ) ), () => {
|
|
569
569
|
|
|
570
570
|
// Calculate ray offset for shadow ray
|
|
571
|
-
const rayOffset = calculateRayOffsetFn( hitPoint,
|
|
571
|
+
const rayOffset = calculateRayOffsetFn( hitPoint, geomNormal, material );
|
|
572
572
|
const rayOrigin = hitPoint.add( rayOffset );
|
|
573
573
|
|
|
574
574
|
// Trace shadow ray
|
|
@@ -609,7 +609,7 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
|
609
609
|
|
|
610
610
|
// Wrapper that returns just the contribution vec3
|
|
611
611
|
export const calculateEmissiveTriangleContribution = Fn( ( [
|
|
612
|
-
hitPoint, normal, viewDir, material,
|
|
612
|
+
hitPoint, normal, geomNormal, viewDir, material,
|
|
613
613
|
bounceIndex, rngState,
|
|
614
614
|
pixelCoord, resolution, frame, dimBase,
|
|
615
615
|
emissiveBoost,
|
|
@@ -620,7 +620,7 @@ export const calculateEmissiveTriangleContribution = Fn( ( [
|
|
|
620
620
|
] ) => {
|
|
621
621
|
|
|
622
622
|
const result = EmissiveContributionResult.wrap( calculateEmissiveTriangleContributionDebug(
|
|
623
|
-
hitPoint, normal, viewDir, material,
|
|
623
|
+
hitPoint, normal, geomNormal, viewDir, material,
|
|
624
624
|
bounceIndex, rngState,
|
|
625
625
|
pixelCoord, resolution, frame, dimBase,
|
|
626
626
|
emissiveBoost,
|
package/src/TSL/LightsDirect.js
CHANGED
|
@@ -341,8 +341,10 @@ export const estimateLightImportance = Fn( ( [ light, hitPoint, normal, material
|
|
|
341
341
|
|
|
342
342
|
If( lightFacing.greaterThan( 0.0 ), () => {
|
|
343
343
|
|
|
344
|
-
|
|
345
|
-
const
|
|
344
|
+
// Diffuse irradiance ∝ L·Ω·cosθ·cosθ′, Ω bounded by the hemisphere. Unit-free.
|
|
345
|
+
const solidAngle = min( light.area.div( max( distSq, 1e-12 ) ), float( 2.0 * Math.PI ) );
|
|
346
|
+
const invArea = select( light.normalize.greaterThan( 0.5 ), float( 1.0 ).div( max( light.area, 1e-10 ) ), float( 1.0 ) );
|
|
347
|
+
const radiance = light.intensity.mul( dot( light.color, REC709_LUMINANCE_COEFFICIENTS ) ).mul( invArea );
|
|
346
348
|
|
|
347
349
|
// Material-aware weighting
|
|
348
350
|
const materialFactor = float( 1.0 ).toVar();
|
|
@@ -358,20 +360,13 @@ export const estimateLightImportance = Fn( ( [ light, hitPoint, normal, material
|
|
|
358
360
|
|
|
359
361
|
} );
|
|
360
362
|
|
|
361
|
-
If( material.roughness.greaterThan( 0.6 ).and( material.metalness.lessThan( 0.3 ) ), () => {
|
|
362
|
-
|
|
363
|
-
const sizeBoost = min( light.area.mul( 2.0 ), float( 2.0 ) );
|
|
364
|
-
materialFactor.mulAssign( sizeBoost );
|
|
365
|
-
|
|
366
|
-
} );
|
|
367
|
-
|
|
368
363
|
If( material.transmission.greaterThan( 0.5 ), () => {
|
|
369
364
|
|
|
370
365
|
materialFactor.mulAssign( float( 1.0 ).add( material.transmission.mul( 0.3 ) ) );
|
|
371
366
|
|
|
372
367
|
} );
|
|
373
368
|
|
|
374
|
-
result.assign(
|
|
369
|
+
result.assign( radiance.mul( solidAngle ).mul( NoL ).mul( lightFacing ).mul( materialFactor ) );
|
|
375
370
|
|
|
376
371
|
} );
|
|
377
372
|
|