rayzee 9.4.2 → 9.5.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 +223 -21
- package/dist/ActiveColor-YvuQq44O.js +13 -0
- package/dist/ArchiveImporter-xuUJiklP.js +4301 -0
- package/dist/AssetConfig-DSEntE65.js +30 -0
- package/dist/BidirectionalIntegrator-C5PKjJoT.js +934 -0
- package/dist/ColorManagement-CheBCPaW.js +1488 -0
- package/dist/EngineDefaults-DH3bI5t-.js +844 -0
- package/dist/EngineEvents-Ci8dVWCP.js +73 -0
- package/dist/EngineIssues-CIZnXHsF.js +72 -0
- package/dist/FileFormats-sPGJeCRc.js +102 -0
- package/dist/Logger-BnHghprG.js +97 -0
- package/dist/{NeuralRetouch-vkdXXyK2.js → NeuralRetouch-CW11yLZ_.js} +8 -6
- package/dist/{NeuralSuperRes-FVqRJKQV.js → NeuralSuperRes-BekaecYg.js} +55 -120
- package/dist/PathTracer-Ccl9KOuF.js +29729 -0
- package/dist/PhysicalSky-Dv2PMFGW.js +1219 -0
- package/dist/RayzeeRenderer-BQxnzCTD.js +3542 -0
- package/dist/SolarLimb-ken-JbDK.js +721 -0
- package/dist/ToneMapGPU-BHi5JzIq.js +177 -0
- package/dist/ViewTransforms-D0rTLBsX.js +255 -0
- package/dist/WorkingMatrix-C6y233hT.js +27 -0
- package/dist/ZipReader-gDSWr0I5.js +98 -0
- package/dist/addons/archives.es.js +3 -0
- package/dist/addons/bidirectional.es.js +2 -0
- package/dist/addons/color.es.js +3 -0
- package/dist/addons/formats.es.js +2 -0
- package/dist/addons/physical-sky.es.js +2 -0
- package/dist/addons/storage.es.js +3 -0
- package/dist/archiveFormats-BZw7TiDp.js +75 -0
- package/dist/identity-DrQsGT2o.js +39 -0
- package/dist/openStorage-DwcBRUj7.js +820 -0
- package/dist/rayzee-core.es.js +12 -0
- package/dist/rayzee.es.js +2266 -44079
- package/dist/rayzee.umd.js +485 -473
- package/dist/sceneCachePolicy-BmwYOYDu.js +183 -0
- package/dist/shared-BGBcSxBQ.js +17 -0
- package/package.json +23 -2
- package/src/Color/ActiveColor.js +34 -0
- package/src/Color/BasicColor.js +147 -0
- package/src/Color/ColorManagement.js +7 -31
- package/src/EngineDefaults.js +22 -8
- package/src/EngineEvents.js +2 -0
- package/src/EngineIssues.js +1 -0
- package/src/PathTracerApp.js +617 -4016
- package/src/Pipeline/PipelineContext.js +0 -4
- package/src/Platform.js +36 -0
- package/src/Processor/ArchiveCache.js +1 -1
- package/src/Processor/ArchiveImporter.js +1259 -0
- package/src/Processor/AssetLoader.js +322 -1746
- package/src/Processor/AtmosphereModel.js +1 -2
- package/src/Processor/BVHBuilder.js +4 -95
- package/src/Processor/EnvironmentExactTable.js +187 -0
- package/src/Processor/EquirectHDRInfo.js +30 -223
- package/src/Processor/FileFormats.js +116 -0
- package/src/Processor/GLTFDecoders.js +85 -0
- package/src/Processor/GLTFDiffuseTransmission.js +63 -0
- package/src/Processor/GeometryExtractor.js +12 -1
- package/src/Processor/IESParser.js +47 -35
- package/src/Processor/KernelManager.js +35 -2
- package/src/Processor/MaterialPacking.js +8 -7
- package/src/Processor/PBRT/PBRTMaterials.js +129 -41
- package/src/Processor/PBRT/PBRTParser.js +30 -17
- package/src/Processor/PBRT/PBRTSceneBuilder.js +548 -37
- package/src/Processor/PBRT/PBRTTextureBake.js +154 -0
- package/src/Processor/PBRT/PFM.js +59 -0
- package/src/Processor/PackedRayBuffer.js +116 -9
- package/src/Processor/ParallelBVHBuilder.js +3 -6
- package/src/Processor/PhysicalSky.js +5 -4
- package/src/Processor/PlatformImageLoader.js +50 -6
- package/src/Processor/QueueManager.js +12 -3
- package/src/Processor/ReinsertionOptimizer.js +2 -2
- package/src/Processor/SceneProcessor.js +15 -55
- package/src/Processor/ShaderBuilder.js +6 -16
- package/src/Processor/SolarLimb.js +3 -0
- package/src/Processor/TextureCreator.js +1 -1
- package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
- package/src/Processor/Workers/BVHWorker.js +5 -18
- package/src/Processor/Workers/CDFWorker.js +6 -183
- package/src/Processor/archiveFormats.js +7 -0
- package/src/Processor/utils.js +2 -1
- package/src/RayzeeRenderer.js +3902 -0
- package/src/RenderSettings.js +49 -41
- package/src/Stages/ASVGF.js +5 -7
- package/src/Stages/AutoExposure.js +3 -12
- package/src/Stages/Compositor.js +8 -23
- package/src/Stages/NRD.js +9 -2
- package/src/Stages/NormalDepth.js +9 -6
- package/src/Stages/PathTracer.js +254 -38
- package/src/Stages/PathTracerStage.js +11 -16
- package/src/Storage/BLASCache.js +1 -1
- package/src/Storage/CDFCache.js +18 -17
- package/src/Storage/DownloadCache.js +1 -1
- package/src/Storage/SceneGraphCodec.js +46 -3
- package/src/Storage/SpillStore.js +1 -1
- package/src/Storage/StorageManager.js +2 -4
- package/src/Storage/areas.js +4 -0
- package/src/TSL/BVHTraversal.js +62 -24
- package/src/TSL/Bidirectional.js +204 -0
- package/src/TSL/BidirectionalLamps.js +132 -0
- package/src/TSL/Clearcoat.js +15 -6
- package/src/TSL/Common.js +19 -14
- package/src/TSL/ConnectKernel.js +147 -0
- package/src/TSL/Debugger.js +1 -1
- package/src/TSL/Displacement.js +4 -3
- package/src/TSL/EmissiveSampling.js +64 -17
- package/src/TSL/Environment.js +77 -106
- package/src/TSL/EnvironmentCDF.js +85 -39
- package/src/TSL/ExtendKernel.js +8 -3
- package/src/TSL/FinalWriteKernel.js +4 -4
- package/src/TSL/GenerateKernel.js +18 -4
- package/src/TSL/HitFacet.js +35 -5
- package/src/TSL/LightBVHSampling.js +40 -9
- package/src/TSL/LightGenerateKernel.js +293 -0
- package/src/TSL/LightGuide.js +198 -0
- package/src/TSL/LightSplatKernel.js +165 -0
- package/src/TSL/LightsCore.js +13 -38
- package/src/TSL/LightsDirect.js +28 -29
- package/src/TSL/LightsIndirect.js +3 -1
- package/src/TSL/LightsSampling.js +90 -38
- package/src/TSL/MaterialEvaluation.js +113 -31
- package/src/TSL/MaterialProperties.js +44 -17
- package/src/TSL/MaterialTransmission.js +52 -47
- package/src/TSL/MergeKernel.js +218 -0
- package/src/TSL/PathTracerCore.js +65 -29
- package/src/TSL/Random.js +4 -0
- package/src/TSL/SceneResources.js +51 -0
- package/src/TSL/ShadeKernel.js +881 -103
- package/src/TSL/Struct.js +10 -0
- package/src/TSL/Sun.js +1 -1
- package/src/TSL/TextureSampling.js +65 -60
- package/src/addons/archives.js +14 -0
- package/src/addons/bidirectional.js +13 -0
- package/src/addons/color.js +32 -0
- package/src/addons/formats.js +16 -0
- package/src/addons/physicalSky.js +14 -0
- package/src/addons/storage.js +19 -0
- package/src/core.js +35 -0
- package/src/index.js +3 -1
- package/src/integrators/BidirectionalIntegrator.js +832 -0
- package/src/managers/EnvironmentManager.js +85 -33
- package/src/managers/MaterialDataManager.js +42 -0
- package/src/managers/UniformManager.js +0 -1
- package/dist/ToneMapGPU-DKQM1pMd.js +0 -552
- package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
- package/src/Processor/TreeletOptimizer.js +0 -574
package/src/TSL/Environment.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Fn, wgslFn, vec2, vec3,
|
|
1
|
+
import { Fn, wgslFn, vec2, vec3, ivec2, float, int, If, Loop, sin, sqrt, min, max, clamp, select } from 'three/tsl';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { EXACT_TABLE_MAX_WIDTH, GUIDES_PER_ENTRY } from '../Processor/EnvironmentExactTable.js';
|
|
4
4
|
|
|
5
5
|
// Convert direction to UV coordinates for equirectangular map
|
|
6
6
|
// Exact implementation from three-gpu-pathtracer
|
|
@@ -28,125 +28,96 @@ export const equirectUvToDirection = /*@__PURE__*/ wgslFn( `
|
|
|
28
28
|
}
|
|
29
29
|
` );
|
|
30
30
|
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
export const sampleEquirect = Fn( ( [ environment, direction, environmentMatrix, envTotalSum, envCompensationDelta, envResolution ] ) => {
|
|
31
|
+
// The table (EnvironmentExactTable.js packExactTable): texel ( x, y ) is row y's entry x — its running sum, the one
|
|
32
|
+
// below it, and the guides of steps 2x and 2x + 1 — and texel ( w, y ) the rows' entry y likewise. Sizes as exactTableSize.
|
|
33
|
+
const exactTable = ( cdfTexture, envResolution ) => {
|
|
35
34
|
|
|
36
|
-
const
|
|
35
|
+
const W = int( envResolution.x );
|
|
36
|
+
const H = int( envResolution.y );
|
|
37
|
+
const k = max( W.add( int( EXACT_TABLE_MAX_WIDTH - 1 ) ).div( int( EXACT_TABLE_MAX_WIDTH ) ), int( 1 ) );
|
|
38
|
+
const w = W.add( k ).sub( int( 1 ) ).div( k ).toVar();
|
|
39
|
+
const h = H.add( k ).sub( int( 1 ) ).div( k ).toVar();
|
|
40
|
+
return { w, h, entry: ( x, y ) => cdfTexture.load( ivec2( x, y ) ) };
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
};
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
// The first of `count` entries whose running sum is above target: the guide of target's step names it at once unless
|
|
45
|
+
// the step holds more than one entry boundary, or f32 rounding of target × steps picked the step beside it; then a
|
|
46
|
+
// search from one entry below the guide to the guide two steps on. Returns the entry with its sum and the one below.
|
|
47
|
+
const invert = ( count, entryAt, target ) => {
|
|
42
48
|
|
|
43
|
-
|
|
49
|
+
const steps = count.mul( int( GUIDES_PER_ENTRY ) ).toVar();
|
|
50
|
+
const step = clamp( int( target.mul( float( steps ) ) ), int( 0 ), steps.sub( int( 1 ) ) ).toVar();
|
|
51
|
+
const odd = step.bitAnd( int( 1 ) ).equal( int( 1 ) ).toVar();
|
|
52
|
+
const guides = entryAt( step.shiftRight( int( 1 ) ) ).toVar();
|
|
53
|
+
const index = int( select( odd, guides.w, guides.z ) ).toVar();
|
|
54
|
+
const e = entryAt( index ).toVar();
|
|
55
|
+
If( target.lessThan( e.y ).or( target.greaterThanEqual( e.x ).and( index.lessThan( count.sub( int( 1 ) ) ) ) ), () => {
|
|
44
56
|
|
|
45
|
-
const
|
|
46
|
-
const
|
|
57
|
+
const lo = max( index.sub( int( 1 ) ), int( 0 ) ).toVar();
|
|
58
|
+
const ahead = entryAt( step.shiftRight( int( 1 ) ).add( int( 1 ) ) );
|
|
59
|
+
const hi = select( step.add( int( 2 ) ).lessThan( steps ), int( select( odd, ahead.w, ahead.z ) ), count.sub( int( 1 ) ) ).toVar();
|
|
60
|
+
Loop( lo.lessThan( hi ), () => {
|
|
47
61
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const lum = dot( color, REC709_LUMINANCE_COEFFICIENTS );
|
|
51
|
-
const weightedLum = lum.mul( sinTheta );
|
|
52
|
-
// MIS Compensation: subtract delta to match the sharpened CDF
|
|
53
|
-
const compensatedWeight = max( float( 0.0 ), weightedLum.sub( envCompensationDelta ) );
|
|
54
|
-
const pdf = compensatedWeight.div( envTotalSum );
|
|
62
|
+
const mid = lo.add( hi ).div( 2 ).toVar();
|
|
63
|
+
If( entryAt( mid ).x.lessThanEqual( target ), () => {
|
|
55
64
|
|
|
56
|
-
|
|
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
|
-
);
|
|
62
|
-
const finalPdf = float( envResolution.x ).mul( float( envResolution.y ) ).mul( pdf ).mul( dirPdf );
|
|
65
|
+
lo.assign( mid.add( 1 ) );
|
|
63
66
|
|
|
64
|
-
|
|
67
|
+
} ).Else( () => {
|
|
68
|
+
|
|
69
|
+
hi.assign( mid );
|
|
70
|
+
|
|
71
|
+
} );
|
|
72
|
+
|
|
73
|
+
} );
|
|
74
|
+
index.assign( lo );
|
|
75
|
+
e.assign( entryAt( lo ) );
|
|
65
76
|
|
|
66
77
|
} );
|
|
78
|
+
return { index, top: e.x, bottom: e.y };
|
|
67
79
|
|
|
68
|
-
|
|
80
|
+
};
|
|
69
81
|
|
|
70
|
-
|
|
82
|
+
// Cell density over uv, per steradian at v.
|
|
83
|
+
const cellPdf = ( share, w, h, v ) => {
|
|
71
84
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
export const sampleEquirectProbability = Fn( ( [
|
|
76
|
-
environment,
|
|
77
|
-
envCDFTexture,
|
|
78
|
-
environmentMatrix,
|
|
79
|
-
environmentIntensity,
|
|
80
|
-
envTotalSum,
|
|
81
|
-
envCompensationDelta,
|
|
82
|
-
envResolution,
|
|
83
|
-
r,
|
|
84
|
-
colorOutput
|
|
85
|
-
] ) => {
|
|
86
|
-
|
|
87
|
-
// CDF texture layout: (W+1)×H R32F — conditional[cy*W+cx] at texel (cx,cy); marginal[cy] at column W.
|
|
88
|
-
const cdfMarginalCol = int( envResolution.x ).toVar();
|
|
89
|
-
|
|
90
|
-
// Sample marginal CDF for V coordinate (1D, linear interpolation)
|
|
91
|
-
const marginalSize = envResolution.y;
|
|
92
|
-
const mIdx = clamp( r.x.mul( marginalSize.sub( 1.0 ) ), 0.0, marginalSize.sub( 1.0 ) );
|
|
93
|
-
const mI0 = int( floor( mIdx ) );
|
|
94
|
-
const mI1 = min( mI0.add( 1 ), int( marginalSize ).sub( 1 ) );
|
|
95
|
-
const mFrac = fract( mIdx );
|
|
96
|
-
const v = mix(
|
|
97
|
-
envCDFTexture.load( ivec2( cdfMarginalCol, mI0 ) ).x,
|
|
98
|
-
envCDFTexture.load( ivec2( cdfMarginalCol, mI1 ) ).x,
|
|
99
|
-
mFrac,
|
|
100
|
-
).toVar();
|
|
101
|
-
|
|
102
|
-
// Sample conditional CDF for U coordinate (2D grid, bilinear interpolation)
|
|
103
|
-
const condW = envResolution.x;
|
|
104
|
-
const condH = envResolution.y;
|
|
105
|
-
const cxf = clamp( r.y.mul( condW.sub( 1.0 ) ), 0.0, condW.sub( 1.0 ) );
|
|
106
|
-
const cyf = clamp( v.mul( condH.sub( 1.0 ) ), 0.0, condH.sub( 1.0 ) );
|
|
107
|
-
const cx0 = int( floor( cxf ) );
|
|
108
|
-
const cy0 = int( floor( cyf ) );
|
|
109
|
-
const cx1 = min( cx0.add( 1 ), int( condW ).sub( 1 ) );
|
|
110
|
-
const cy1 = min( cy0.add( 1 ), int( condH ).sub( 1 ) );
|
|
111
|
-
const fx = fract( cxf );
|
|
112
|
-
const fy = fract( cyf );
|
|
113
|
-
const v00 = envCDFTexture.load( ivec2( cx0, cy0 ) ).x;
|
|
114
|
-
const v10 = envCDFTexture.load( ivec2( cx1, cy0 ) ).x;
|
|
115
|
-
const v01 = envCDFTexture.load( ivec2( cx0, cy1 ) ).x;
|
|
116
|
-
const v11 = envCDFTexture.load( ivec2( cx1, cy1 ) ).x;
|
|
117
|
-
const u = mix( mix( v00, v10, fx ), mix( v01, v11, fx ), fy ).toVar();
|
|
118
|
-
|
|
119
|
-
const uv = vec2( u, v ).toVar();
|
|
120
|
-
|
|
121
|
-
// Convert UV to direction
|
|
122
|
-
const direction = equirectUvToDirection( { uv, environmentMatrix } ).toVar();
|
|
123
|
-
|
|
124
|
-
// Sample color
|
|
125
|
-
const color = texture( environment, uv, 0 ).rgb.mul( environmentIntensity ).toVar();
|
|
126
|
-
|
|
127
|
-
// Write color to output parameter (avoids redundant CDF texture lookups)
|
|
128
|
-
colorOutput.assign( color );
|
|
129
|
-
|
|
130
|
-
// Calculate PDF — sin(theta) weighting + MIS Compensation (Karlík et al. 2019)
|
|
131
|
-
const sinTheta = sin( uv.y.mul( Math.PI ) ).toVar();
|
|
132
|
-
const lum = dot( color.div( environmentIntensity ), REC709_LUMINANCE_COEFFICIENTS );
|
|
133
|
-
const weightedLum = lum.mul( sinTheta );
|
|
134
|
-
const compensatedWeight = max( float( 0.0 ), weightedLum.sub( envCompensationDelta ) );
|
|
135
|
-
const pdf = compensatedWeight.div( envTotalSum );
|
|
136
|
-
|
|
137
|
-
// Inline equirectDirectionPdf — uv + sinTheta are already in scope, so we
|
|
138
|
-
// skip the helper's redundant uv-from-direction + sin recompute.
|
|
139
|
-
const dirPdf = sinTheta.greaterThan( 0.0 ).select(
|
|
140
|
-
float( 1.0 ).div( float( 2.0 * Math.PI * Math.PI ).mul( sinTheta ) ),
|
|
141
|
-
float( 0.0 )
|
|
142
|
-
);
|
|
143
|
-
const finalPdf = float( envResolution.x ).mul( float( envResolution.y ) ).mul( pdf ).mul( dirPdf );
|
|
144
|
-
|
|
145
|
-
return vec4( direction, finalPdf );
|
|
85
|
+
const sinTheta = sin( v.mul( Math.PI ) );
|
|
86
|
+
return select( sinTheta.greaterThan( 0.0 ).and( share.greaterThan( 0.0 ) ),
|
|
87
|
+
share.mul( float( w ) ).mul( float( h ) ).div( max( sinTheta.mul( 2 * Math.PI * Math.PI ), 1e-30 ) ), float( 0.0 ) );
|
|
146
88
|
|
|
147
|
-
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** A direction drawn exactly from the table, and its density per steradian. */
|
|
92
|
+
export function sampleEnvironmentExact( cdfTexture, environmentMatrix, envResolution, xi ) {
|
|
93
|
+
|
|
94
|
+
const { w, h, entry } = exactTable( cdfTexture, envResolution );
|
|
95
|
+
const row = invert( h, ( i ) => entry( w, i ), xi.y );
|
|
96
|
+
const y = row.index.toVar();
|
|
97
|
+
const cell = invert( w, ( i ) => entry( i, y ), xi.x );
|
|
98
|
+
const pRow = row.top.sub( row.bottom ).toVar();
|
|
99
|
+
const pCell = cell.top.sub( cell.bottom ).toVar();
|
|
100
|
+
|
|
101
|
+
const fv = clamp( xi.y.sub( row.bottom ).div( max( pRow, 1e-30 ) ), 0.0, 0.99999994 );
|
|
102
|
+
const fu = clamp( xi.x.sub( cell.bottom ).div( max( pCell, 1e-30 ) ), 0.0, 0.99999994 );
|
|
103
|
+
const uv = vec2( float( cell.index ).add( fu ).div( float( w ) ), float( y ).add( fv ).div( float( h ) ) ).toVar();
|
|
104
|
+
|
|
105
|
+
return { direction: equirectUvToDirection( { uv, environmentMatrix } ), pdf: cellPdf( pRow.mul( pCell ), w, h, uv.y ) };
|
|
106
|
+
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** sampleEnvironmentExact's density per steradian for `direction`. */
|
|
110
|
+
export function environmentPdfExact( cdfTexture, environmentMatrix, envResolution, direction ) {
|
|
111
|
+
|
|
112
|
+
const { w, h, entry } = exactTable( cdfTexture, envResolution );
|
|
113
|
+
const uv = equirectDirectionToUv( { direction, environmentMatrix } ).toVar();
|
|
114
|
+
const x = clamp( int( uv.x.mul( float( w ) ) ), int( 0 ), w.sub( int( 1 ) ) ).toVar();
|
|
115
|
+
const y = clamp( int( uv.y.mul( float( h ) ) ), int( 0 ), h.sub( int( 1 ) ) ).toVar();
|
|
116
|
+
const rowEntry = entry( w, y ).toVar();
|
|
117
|
+
const cellEntry = entry( x, y ).toVar();
|
|
118
|
+
return cellPdf( rowEntry.x.sub( rowEntry.y ).mul( cellEntry.x.sub( cellEntry.y ) ), w, h, uv.y );
|
|
148
119
|
|
|
149
|
-
|
|
120
|
+
}
|
|
150
121
|
|
|
151
122
|
// Simple environment lookup (no importance sampling) — native WGSL
|
|
152
123
|
export const sampleEnvironment = /*@__PURE__*/ wgslFn( `
|
|
@@ -2,11 +2,11 @@ import { Fn, float, int, uint, vec2, vec4, If, Loop, Return, instanceIndex, max,
|
|
|
2
2
|
|
|
3
3
|
import { REC709_LUMINANCE_COEFFICIENTS } from './Common.js';
|
|
4
4
|
|
|
5
|
-
/** Floats per row of the packed table: width + 1 texels, padded to the 256 bytes a buffer→texture copy needs. */
|
|
6
|
-
export const cdfRowStride = width => Math.ceil( ( width + 1 ) *
|
|
5
|
+
/** Floats per row of the packed table: width + 1 RGBA texels, padded to the 256 bytes a buffer→texture copy needs. */
|
|
6
|
+
export const cdfRowStride = width => Math.ceil( ( width + 1 ) * 16 / 256 ) * 64;
|
|
7
7
|
|
|
8
|
-
// First index in [0, n) whose value is
|
|
9
|
-
function
|
|
8
|
+
// First index in [0, n) whose value is above target, n − 1 when none is — as the CPU builder's cutpoints.
|
|
9
|
+
function firstAbove( n, valueAt, target ) {
|
|
10
10
|
|
|
11
11
|
const lo = int( 0 ).toVar();
|
|
12
12
|
const hi = int( n - 1 ).toVar();
|
|
@@ -15,7 +15,7 @@ function lowerBound( n, valueAt, target ) {
|
|
|
15
15
|
If( lo.lessThan( hi ), () => {
|
|
16
16
|
|
|
17
17
|
const mid = lo.add( hi ).div( 2 ).toVar();
|
|
18
|
-
If( valueAt( mid ).
|
|
18
|
+
If( valueAt( mid ).lessThanEqual( target ), () => {
|
|
19
19
|
|
|
20
20
|
lo.assign( mid.add( 1 ) );
|
|
21
21
|
|
|
@@ -36,13 +36,16 @@ function lowerBound( n, valueAt, target ) {
|
|
|
36
36
|
// against the row total it ends on.
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* The GPU twin of `
|
|
40
|
-
*
|
|
39
|
+
* The GPU twin of `buildExactEnvironmentTable`, in packExactTable's (width + 1) × height RGBA layout (one cell a
|
|
40
|
+
* texel, compensated by the same mean, without its neighbour weighting: the sky has no sharp texel).
|
|
41
|
+
* `stats[ 1 ]` ends as ( totalSum, compensationDelta, compensated, 0 ).
|
|
41
42
|
* @returns {Array} kernels, dispatched in order
|
|
42
43
|
*/
|
|
43
44
|
export function buildEnvironmentCDFKernels( { pixels, rows, prefix, stats, cdf, width, height } ) {
|
|
44
45
|
|
|
45
46
|
const stride = cdfRowStride( width );
|
|
47
|
+
// Channel c of texel ( x, y ).
|
|
48
|
+
const at = ( x, y, c ) => uint( y ).mul( uint( stride ) ).add( uint( x ).mul( uint( 4 ) ) ).add( uint( c ) );
|
|
46
49
|
const sinTheta = y => sin( float( y ).add( 0.5 ).mul( Math.PI / height ) );
|
|
47
50
|
const weight = ( y, x, sinT ) => dot( pixels.element( y.mul( width ).add( x ) ).xyz, REC709_LUMINANCE_COEFFICIENTS ).mul( sinT );
|
|
48
51
|
const firstThreadOnly = () => If( instanceIndex.greaterThan( uint( 0 ) ), () => {
|
|
@@ -109,7 +112,7 @@ export function buildEnvironmentCDFKernels( { pixels, rows, prefix, stats, cdf,
|
|
|
109
112
|
|
|
110
113
|
} )().compute( height, [ 64 ] );
|
|
111
114
|
|
|
112
|
-
const
|
|
115
|
+
const totals = Fn( () => {
|
|
113
116
|
|
|
114
117
|
firstThreadOnly();
|
|
115
118
|
const compensatedTotal = float( 0 ).toVar();
|
|
@@ -118,59 +121,102 @@ export function buildEnvironmentCDFKernels( { pixels, rows, prefix, stats, cdf,
|
|
|
118
121
|
compensatedTotal.addAssign( rows.element( i ).y );
|
|
119
122
|
|
|
120
123
|
} );
|
|
121
|
-
const
|
|
124
|
+
const s = stats.element( 0 ).toVar();
|
|
125
|
+
stats.element( 1 ).assign( select( compensatedTotal.greaterThan( 0 ), vec4( compensatedTotal, s.y, 1, 0 ), vec4( s.x, 0, 0, 0 ) ) );
|
|
122
126
|
|
|
123
|
-
|
|
124
|
-
|
|
127
|
+
} )().compute( 1, [ 1 ] );
|
|
128
|
+
|
|
129
|
+
// buildExactEnvironmentTable's floor: a share of the mean weight in every cell.
|
|
130
|
+
const exactFloor = () => stats.element( 0 ).x.mul( 1e-4 / ( width * height ) );
|
|
125
131
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
132
|
+
const exactRows = Fn( () => {
|
|
133
|
+
|
|
134
|
+
const y = instanceIndex;
|
|
135
|
+
If( y.greaterThanEqual( uint( height ) ), () => {
|
|
136
|
+
|
|
137
|
+
Return();
|
|
129
138
|
|
|
130
139
|
} );
|
|
131
140
|
|
|
132
|
-
const
|
|
133
|
-
stats.element( 1 ).
|
|
141
|
+
const floor = exactFloor().toVar();
|
|
142
|
+
const compensated = stats.element( 1 ).z.greaterThan( 0 ).toVar();
|
|
143
|
+
const r = rows.element( y ).toVar();
|
|
144
|
+
const rowSum = select( compensated, r.y, r.x ).add( floor.mul( width ) ).toVar();
|
|
145
|
+
const below = float( 0 ).toVar();
|
|
146
|
+
Loop( { start: int( 0 ), end: int( width ), type: 'int', condition: '<' }, ( { i } ) => {
|
|
147
|
+
|
|
148
|
+
const p = prefix.element( y.mul( width ).add( uint( i ) ) ).toVar();
|
|
149
|
+
const c = select( compensated, p.y, p.x ).add( floor.mul( float( i ).add( 1 ) ) ).div( max( rowSum, 1e-30 ) );
|
|
150
|
+
const sum = select( rowSum.greaterThan( 0 ), select( i.equal( int( width - 1 ) ), float( 1 ), c ), float( 0 ) ).toVar();
|
|
151
|
+
cdf.element( at( i, y, 0 ) ).assign( sum );
|
|
152
|
+
cdf.element( at( i, y, 1 ) ).assign( below );
|
|
153
|
+
below.assign( sum );
|
|
154
|
+
|
|
155
|
+
} );
|
|
156
|
+
|
|
157
|
+
} )().compute( height, [ 64 ] );
|
|
158
|
+
|
|
159
|
+
const exactMarginal = Fn( () => {
|
|
134
160
|
|
|
135
|
-
|
|
161
|
+
firstThreadOnly();
|
|
162
|
+
const floor = exactFloor().mul( width ).toVar();
|
|
163
|
+
const compensated = stats.element( 1 ).z.greaterThan( 0 ).toVar();
|
|
164
|
+
const rowSum = ( i ) => {
|
|
136
165
|
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
166
|
+
const r = rows.element( i ); return select( compensated, r.y, r.x ).add( floor );
|
|
167
|
+
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const total = float( 0 ).toVar();
|
|
171
|
+
Loop( { start: int( 0 ), end: int( height ), type: 'int', condition: '<' }, ( { i } ) => {
|
|
172
|
+
|
|
173
|
+
total.addAssign( rowSum( i ) );
|
|
174
|
+
|
|
175
|
+
} );
|
|
176
|
+
const cumulative = float( 0 ).toVar();
|
|
177
|
+
const below = float( 0 ).toVar();
|
|
178
|
+
Loop( { start: int( 0 ), end: int( height ), type: 'int', condition: '<' }, ( { i } ) => {
|
|
179
|
+
|
|
180
|
+
cumulative.addAssign( rowSum( i ) );
|
|
181
|
+
const c = select( i.equal( int( height - 1 ) ), float( 1 ), cumulative.div( max( total, 1e-30 ) ) );
|
|
182
|
+
const sum = select( total.greaterThan( 0 ), c, float( 0 ) ).toVar();
|
|
183
|
+
cdf.element( at( width, i, 0 ) ).assign( sum );
|
|
184
|
+
cdf.element( at( width, i, 1 ) ).assign( below );
|
|
185
|
+
below.assign( sum );
|
|
143
186
|
|
|
144
187
|
} );
|
|
145
188
|
|
|
146
189
|
} )().compute( 1, [ 1 ] );
|
|
147
190
|
|
|
148
|
-
|
|
191
|
+
// GUIDES_PER_ENTRY (2) guides an entry, in its texel's z and w: the rows' in columns [0, width), the marginal's in
|
|
192
|
+
// column width.
|
|
193
|
+
const rowGuides = 2 * width * height;
|
|
194
|
+
const guides = Fn( () => {
|
|
149
195
|
|
|
150
196
|
const idx = instanceIndex;
|
|
151
|
-
If( idx.greaterThanEqual( uint(
|
|
197
|
+
If( idx.greaterThanEqual( uint( rowGuides + 2 * height ) ), () => {
|
|
152
198
|
|
|
153
199
|
Return();
|
|
154
200
|
|
|
155
201
|
} );
|
|
156
202
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const k = lowerBound( width, j => {
|
|
203
|
+
If( idx.lessThan( uint( rowGuides ) ), () => {
|
|
204
|
+
|
|
205
|
+
const g = idx.mod( 2 * width ).toVar();
|
|
206
|
+
const y = idx.div( 2 * width ).toVar();
|
|
207
|
+
const x = firstAbove( width, j => cdf.element( at( j, y, 0 ) ), float( g ).div( 2 * width ) );
|
|
208
|
+
cdf.element( at( g.shiftRight( uint( 1 ) ), y, uint( 2 ).add( g.bitAnd( uint( 1 ) ) ) ) ).assign( float( x ) );
|
|
164
209
|
|
|
165
|
-
|
|
166
|
-
return select( useCompensated, p.y, p.x );
|
|
210
|
+
} ).Else( () => {
|
|
167
211
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
212
|
+
const g = idx.sub( uint( rowGuides ) ).toVar();
|
|
213
|
+
const y = firstAbove( height, j => cdf.element( at( width, j, 0 ) ), float( g ).div( 2 * height ) );
|
|
214
|
+
cdf.element( at( width, g.shiftRight( uint( 1 ) ), uint( 2 ).add( g.bitAnd( uint( 1 ) ) ) ) ).assign( float( y ) );
|
|
215
|
+
|
|
216
|
+
} );
|
|
171
217
|
|
|
172
|
-
} )().compute(
|
|
218
|
+
} )().compute( rowGuides + 2 * height, [ 64 ] );
|
|
173
219
|
|
|
174
|
-
return [ rowTotals, mean, prefixSums,
|
|
220
|
+
return [ rowTotals, mean, prefixSums, totals, exactRows, exactMarginal, guides ];
|
|
175
221
|
|
|
176
222
|
}
|
package/src/TSL/ExtendKernel.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
-
Fn, uint, int,
|
|
7
|
-
If,
|
|
6
|
+
Fn, uint, int, float,
|
|
7
|
+
If, dot, abs, select,
|
|
8
8
|
instanceIndex,
|
|
9
9
|
atomicLoad,
|
|
10
10
|
Return,
|
|
@@ -76,9 +76,14 @@ export function buildExtendKernel( params ) {
|
|
|
76
76
|
didHit: hitInfo.didHit, liftEnabled: shadowTerminatorOffset.greaterThan( 0.0 ),
|
|
77
77
|
} );
|
|
78
78
|
|
|
79
|
+
// origin + t · direction sits off a large triangle by t's own error, which grows with the triangle's size: the
|
|
80
|
+
// distance stored is moved along the ray onto the triangle's plane (not where it grazes the plane).
|
|
81
|
+
const cosToPlane = dot( direction, facet.faceN ).toVar();
|
|
82
|
+
const planeDst = hitInfo.dst.add( select( abs( cosToPlane ).greaterThan( 1e-3 ), facet.surfaceOffset.div( cosToPlane ), float( 0.0 ) ) );
|
|
83
|
+
|
|
79
84
|
writeHitPacked(
|
|
80
85
|
hitBufferRW, rayID,
|
|
81
|
-
|
|
86
|
+
planeDst,
|
|
82
87
|
uint( hitInfo.triangleIndex ),
|
|
83
88
|
hitInfo.uv.x, hitInfo.uv.y,
|
|
84
89
|
hitInfo.normal,
|
|
@@ -46,7 +46,7 @@ export function buildFinalWriteKernel( params ) {
|
|
|
46
46
|
visMode,
|
|
47
47
|
// Aux MRT (normalDepth + albedo) feeds only the denoiser/OIDN. Gated by a live uniform (1 = denoiser
|
|
48
48
|
// on): when off, skip the G-buffer decode, the prev-frame aux mix, and the two aux stores.
|
|
49
|
-
auxGBufferEnabled,
|
|
49
|
+
auxGBufferEnabled, gBuffer = true, // compiled in only when requested (PathTracer.requestOutput( 'gBuffer' ))
|
|
50
50
|
// Clean-aux normal (1 = temporally accumulate + renormalize the aux normal). On only for clean-aux
|
|
51
51
|
// OIDN models (calb_cnrm/high, alb_nrm/balanced); off for fast/ASVGF which want the bump normal.
|
|
52
52
|
cleanAuxNormalEnabled,
|
|
@@ -98,7 +98,7 @@ export function buildFinalWriteKernel( params ) {
|
|
|
98
98
|
const finalAlbedo = vec4( 0.0 ).xyz.toVar();
|
|
99
99
|
// Albedo .w carries the hit distance — OIDN reads albedo as 3 channels, so it is free.
|
|
100
100
|
const finalHitDist = float( 0.0 ).toVar();
|
|
101
|
-
If( auxOn, () => {
|
|
101
|
+
if ( gBuffer ) If( auxOn, () => {
|
|
102
102
|
|
|
103
103
|
const gbuf = readGBuffer( gBufferRO, rayID );
|
|
104
104
|
finalNormalDepth.assign( gbDecodeNormalDepth( gbuf ) );
|
|
@@ -118,7 +118,7 @@ export function buildFinalWriteKernel( params ) {
|
|
|
118
118
|
|
|
119
119
|
// Frozen pixels pass prev colour through unchanged (stale sample); active pixels accumulate.
|
|
120
120
|
finalColor.assign( select( wasFrozen, prevAccumSample.xyz, mix( prevAccumSample.xyz, sampleColor.xyz, accumulationAlpha ) ) );
|
|
121
|
-
If( auxOn.and( hasPreviousAux ), () => {
|
|
121
|
+
if ( gBuffer ) If( auxOn.and( hasPreviousAux ), () => {
|
|
122
122
|
|
|
123
123
|
// Albedo averages cleanly (it's a colour); so does the normalized hit distance in .w.
|
|
124
124
|
const prevAlbedoSample = texture( prevAlbedoTexture, prevUV, 0 ).toVar();
|
|
@@ -253,7 +253,7 @@ export function buildFinalWriteKernel( params ) {
|
|
|
253
253
|
|
|
254
254
|
const uintCoord = uvec2( uint( gx ), uint( gy ) );
|
|
255
255
|
textureStore( writeColorTex, uintCoord, vec4( finalColor, outputAlpha ) ).toWriteOnly();
|
|
256
|
-
If( auxOn, () => {
|
|
256
|
+
if ( gBuffer ) If( auxOn, () => {
|
|
257
257
|
|
|
258
258
|
textureStore( writeNDTex, uintCoord, finalNormalDepth ).toWriteOnly();
|
|
259
259
|
textureStore( writeAlbedoTex, uintCoord, vec4( finalAlbedo, finalHitDist ) ).toWriteOnly();
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import {
|
|
14
14
|
Fn, float, vec2, vec3, vec4, int, uint,
|
|
15
15
|
If, instanceIndex, atomicLoad,
|
|
16
|
-
localId, workgroupId,
|
|
16
|
+
localId, workgroupId, dot, max, select,
|
|
17
17
|
} from 'three/tsl';
|
|
18
18
|
|
|
19
19
|
import {
|
|
@@ -28,7 +28,7 @@ import { RAY_FLAG, COUNTER } from '../Processor/QueueManager.js';
|
|
|
28
28
|
import {
|
|
29
29
|
writeRayOriginMeta, writeRayDirFlags, writeRayThroughputPdf,
|
|
30
30
|
writeRayRadiance, writeGBuffer,
|
|
31
|
-
writeMediumStack, writeFeatureThroughput, writeRngState,
|
|
31
|
+
writeMediumStack, writeFeatureThroughput, writeRngState, writeRngMis,
|
|
32
32
|
} from '../Processor/PackedRayBuffer.js';
|
|
33
33
|
|
|
34
34
|
const WG_SIZE = 16;
|
|
@@ -45,8 +45,10 @@ export function buildGenerateKernel( params ) {
|
|
|
45
45
|
chunkRowBase, chunkRows, // row band offset (global first row) + row count for this chunk
|
|
46
46
|
transmissiveBounces, // per-ray refraction budget (megakernel parity: PathTracerCore.js:606)
|
|
47
47
|
auxGBufferEnabled, // live uniform: 1 = init the per-pixel G-buffer (denoiser on), 0 = skip it
|
|
48
|
+
gBuffer = true, // compiled in only when requested (PathTracer.requestOutput( 'gBuffer' ))
|
|
48
49
|
// listDriven: 1D dispatch over the active-pixel list (activeIndicesRO[tid] = LOCAL slot) instead of 2D.
|
|
49
50
|
listDriven = false, activeIndicesRO = null, counters = null,
|
|
51
|
+
bidirectional: bdpt = null,
|
|
50
52
|
} = params;
|
|
51
53
|
|
|
52
54
|
const auxOn = auxGBufferEnabled.greaterThan( uint( 0 ) );
|
|
@@ -89,7 +91,7 @@ export function buildGenerateKernel( params ) {
|
|
|
89
91
|
// tell subject from background. Output is unaffected: FinalWrite forces alpha 1 when transparent-bg is off.
|
|
90
92
|
writeRayRadiance( rayBufferRW, rayID, vec4( vec3( 0.0 ), float( 1.0 ) ) );
|
|
91
93
|
|
|
92
|
-
If( auxOn, () => {
|
|
94
|
+
if ( gBuffer ) If( auxOn, () => {
|
|
93
95
|
|
|
94
96
|
// default: normal +Z, depth 1 (far), black albedo (background/miss). Per-CHUNK G-buffer → LOCAL slot.
|
|
95
97
|
writeGBuffer( gBufferRW, rayID, vec3( 0.0, 0.0, 1.0 ), float( 1.0 ), vec3( 0.0 ), float( 0.0 ) );
|
|
@@ -100,7 +102,19 @@ export function buildGenerateKernel( params ) {
|
|
|
100
102
|
|
|
101
103
|
writeMediumStack( rayBufferRW, rayID, uint( 0 ), uint( transmissiveBounces ), float( 1.0 ), float( 1.0 ), float( 1.0 ) );
|
|
102
104
|
|
|
103
|
-
|
|
105
|
+
if ( bdpt ) {
|
|
106
|
+
|
|
107
|
+
// Georgiev (31): light tracing's N_L samples over this ray's density 1 / (A_pixel cos³θ).
|
|
108
|
+
const cosCamera = max( dot( ray.direction, bdpt.cameraForward ), 1e-4 );
|
|
109
|
+
const dVCM = select( bdpt.lightTrace.greaterThan( uint( 0 ) ),
|
|
110
|
+
bdpt.lib.mis( float( bdpt.lightPaths ).mul( bdpt.pixelArea ).mul( cosCamera ).mul( cosCamera ).mul( cosCamera ) ), float( 0.0 ) );
|
|
111
|
+
writeRngMis( hitBufferRW, rayID, seed, dVCM, float( 0.0 ) );
|
|
112
|
+
|
|
113
|
+
} else {
|
|
114
|
+
|
|
115
|
+
writeRngState( hitBufferRW, rayID, seed );
|
|
116
|
+
|
|
117
|
+
}
|
|
104
118
|
|
|
105
119
|
};
|
|
106
120
|
|
package/src/TSL/HitFacet.js
CHANGED
|
@@ -15,18 +15,20 @@ import {
|
|
|
15
15
|
packHalf2x16, unpackHalf2x16,
|
|
16
16
|
} from 'three/tsl';
|
|
17
17
|
import {
|
|
18
|
-
unpackTriangleNormal, triangleRow, instanceRows, instanceFaceNormalToWorld,
|
|
18
|
+
unpackTriangleNormal, triangleRow, instanceRows, instanceFaceNormalToWorld, instancePointToWorld,
|
|
19
19
|
} from './Common.js';
|
|
20
20
|
import { shadowTerminatorLift } from './ShadowTerminator.js';
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* @returns {{ faceN: Node, liftScale: Node }} the facet normal on the viewer's side (the
|
|
24
|
-
* interpolated one for a degenerate triangle),
|
|
23
|
+
* @returns {{ faceN: Node, liftScale: Node, surfaceOffset: Node }} the facet normal on the viewer's side (the
|
|
24
|
+
* interpolated one for a degenerate triangle), the terminator lift (0 unless `liftEnabled`), and how far along
|
|
25
|
+
* faceN `hitPoint` lies from the triangle's plane (ExtendKernel moves the stored distance onto it)
|
|
25
26
|
*/
|
|
26
27
|
export function hitFacet( { triangleBuffer, bvhBuffer, triIdx, instanceLeaf, hitPoint, smoothNormal, viewDir, didHit, liftEnabled } ) {
|
|
27
28
|
|
|
28
29
|
const faceN = vec3( 0.0, 0.0, 1.0 ).toVar();
|
|
29
30
|
const liftScale = float( 0.0 ).toVar();
|
|
31
|
+
const surfaceOffset = float( 0.0 ).toVar();
|
|
30
32
|
|
|
31
33
|
If( didHit, () => {
|
|
32
34
|
|
|
@@ -47,9 +49,18 @@ export function hitFacet( { triangleBuffer, bvhBuffer, triIdx, instanceLeaf, hit
|
|
|
47
49
|
|
|
48
50
|
} );
|
|
49
51
|
|
|
50
|
-
const
|
|
52
|
+
const degenerate = dot( face, face ).lessThanEqual( 0.0 ).toVar();
|
|
53
|
+
const n = select( degenerate, smoothNormal, normalize( face ) ).toVar();
|
|
51
54
|
faceN.assign( select( dot( n, viewDir ).lessThan( 0.0 ), n.negate(), n ) );
|
|
52
55
|
|
|
56
|
+
const corner = V0.toVar();
|
|
57
|
+
If( instanced, () => {
|
|
58
|
+
|
|
59
|
+
corner.assign( instancePointToWorld( instanceRows( bvhBuffer, instanceLeaf ), V0 ) );
|
|
60
|
+
|
|
61
|
+
} );
|
|
62
|
+
surfaceOffset.assign( select( degenerate, float( 0.0 ), dot( corner.sub( hitPoint ), faceN ) ) );
|
|
63
|
+
|
|
53
64
|
If( liftEnabled, () => {
|
|
54
65
|
|
|
55
66
|
liftScale.assign( shadowTerminatorLift( {
|
|
@@ -62,7 +73,26 @@ export function hitFacet( { triangleBuffer, bvhBuffer, triIdx, instanceLeaf, hit
|
|
|
62
73
|
|
|
63
74
|
} );
|
|
64
75
|
|
|
65
|
-
return { faceN, liftScale };
|
|
76
|
+
return { faceN, liftScale, surfaceOffset };
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The triangle's unit winding normal in world space: what the traversal's front/back test sees (a mirrored
|
|
82
|
+
* placement keeps its object-space sense). The interpolated normal can face the other way near a coarse mesh's
|
|
83
|
+
* silhouette. Unit length because sideAccepts has a threshold: a raw cross product of a small triangle is under it.
|
|
84
|
+
*/
|
|
85
|
+
export function windingNormal( triangleBuffer, bvhBuffer, triIdx, instanceLeaf ) {
|
|
86
|
+
|
|
87
|
+
const V0 = uintBitsToFloat( triangleRow( triangleBuffer, triIdx, 0 ).xyz ).toVar();
|
|
88
|
+
const local = cross( uintBitsToFloat( triangleRow( triangleBuffer, triIdx, 1 ).xyz ).sub( V0 ), uintBitsToFloat( triangleRow( triangleBuffer, triIdx, 2 ).xyz ).sub( V0 ) ).toVar();
|
|
89
|
+
const n = local.toVar();
|
|
90
|
+
If( instanceLeaf.greaterThanEqual( int( 0 ) ), () => {
|
|
91
|
+
|
|
92
|
+
n.assign( instanceFaceNormalToWorld( instanceRows( bvhBuffer, instanceLeaf ), local ) );
|
|
93
|
+
|
|
94
|
+
} );
|
|
95
|
+
return normalize( n );
|
|
66
96
|
|
|
67
97
|
}
|
|
68
98
|
|