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
|
@@ -18,11 +18,14 @@ import {
|
|
|
18
18
|
normalize,
|
|
19
19
|
cross,
|
|
20
20
|
length,
|
|
21
|
+
abs,
|
|
21
22
|
} from 'three/tsl';
|
|
22
23
|
import { MIN_PDF } from './Common.js';
|
|
24
|
+
import { sideAccepts } from './BVHTraversal.js';
|
|
23
25
|
import { getRandomSample1D, getRandomSample2D } from './Random.js';
|
|
24
26
|
import {
|
|
25
27
|
EmissiveSample,
|
|
28
|
+
EmissiveSampleIndexed,
|
|
26
29
|
sampleTriangle,
|
|
27
30
|
interpolateNormal,
|
|
28
31
|
fetchTriangleData,
|
|
@@ -103,7 +106,7 @@ export const lbvhNodeImportance = Fn( ( [ nMin, power, nMax, coneAxis, cosThetaO
|
|
|
103
106
|
*
|
|
104
107
|
* Returns an EmissiveSample struct.
|
|
105
108
|
*/
|
|
106
|
-
|
|
109
|
+
const makeSampleLightBVHTriangle = ( indexed ) => Fn( ( [
|
|
107
110
|
hitPoint, surfaceNormal,
|
|
108
111
|
rngState,
|
|
109
112
|
pixelCoord, resolution, frame, dimBase,
|
|
@@ -111,9 +114,11 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
111
114
|
emissiveTriangleBuffer,
|
|
112
115
|
emissiveVec4Offset,
|
|
113
116
|
triangleBuffer, bvhBuffer,
|
|
117
|
+
// The surface passes light through diffusely: a point behind it is as good as one in front.
|
|
118
|
+
throughSurface,
|
|
114
119
|
] ) => {
|
|
115
120
|
|
|
116
|
-
const
|
|
121
|
+
const fields = {
|
|
117
122
|
position: vec3( 0.0 ),
|
|
118
123
|
normal: vec3( 0.0 ),
|
|
119
124
|
emission: vec3( 0.0 ),
|
|
@@ -123,7 +128,10 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
123
128
|
area: float( 0.0 ),
|
|
124
129
|
cosThetaLight: float( 0.0 ),
|
|
125
130
|
valid: false,
|
|
126
|
-
}
|
|
131
|
+
};
|
|
132
|
+
const result = ( indexed
|
|
133
|
+
? EmissiveSampleIndexed( { ...fields, triangleIndex: int( - 1 ), faceNormal: vec3( 0.0 ) } )
|
|
134
|
+
: EmissiveSample( fields ) ).toVar();
|
|
127
135
|
|
|
128
136
|
// Accumulated selection PDF (product of per-level choice probabilities)
|
|
129
137
|
const selectionPdf = float( 1.0 ).toVar();
|
|
@@ -279,9 +287,9 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
279
287
|
const samplePos = sphResult.position;
|
|
280
288
|
|
|
281
289
|
const surfaceFacing = dot( dir, surfaceNormal );
|
|
282
|
-
const emissiveFacing = dot( dir, geoNormal
|
|
290
|
+
const emissiveFacing = abs( dot( dir, geoNormal ) );
|
|
283
291
|
|
|
284
|
-
If( surfaceFacing.greaterThan( float( 0.0 ) ).
|
|
292
|
+
If( surfaceFacing.greaterThan( float( 0.0 ) ).or( throughSurface ).and( sideAccepts( triData.side, dot( dir, geoNormal ) ) ), () => {
|
|
285
293
|
|
|
286
294
|
// Interpolate normal at sampled point via barycentric coords
|
|
287
295
|
const barycentricCoords = barycentricFromPoint( samplePos, triData.v0, triData.v1, triData.v2 );
|
|
@@ -305,6 +313,12 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
305
313
|
result.area.assign( area );
|
|
306
314
|
result.cosThetaLight.assign( emissiveFacing );
|
|
307
315
|
result.valid.assign( true );
|
|
316
|
+
if ( indexed ) {
|
|
317
|
+
|
|
318
|
+
result.triangleIndex.assign( triangleIndex );
|
|
319
|
+
result.faceNormal.assign( geoNormal );
|
|
320
|
+
|
|
321
|
+
}
|
|
308
322
|
|
|
309
323
|
} );
|
|
310
324
|
|
|
@@ -322,9 +336,11 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
322
336
|
const dir = toEmissive.div( dist );
|
|
323
337
|
|
|
324
338
|
const surfaceFacing = dot( dir, surfaceNormal );
|
|
325
|
-
|
|
339
|
+
// The facet's cosine, not the shading normal's: it is what turns an area density into a solid-angle one.
|
|
340
|
+
const emissiveFacing = abs( dot( dir, geoNormal ) );
|
|
326
341
|
|
|
327
|
-
|
|
342
|
+
// Edge-on (a two-sided emitter passes its side test there) the density is infinite and the light zero.
|
|
343
|
+
If( surfaceFacing.greaterThan( float( 0.0 ) ).or( throughSurface ).and( sideAccepts( triData.side, dot( dir, geoNormal ) ) ).and( emissiveFacing.greaterThan( float( 0.0 ) ) ), () => {
|
|
328
344
|
|
|
329
345
|
// PDF: selectionPdf / area, converted to solid angle: pdfArea * distSq / cosLight
|
|
330
346
|
const pdfArea = selectionPdf.div( max( area, float( 1e-10 ) ) );
|
|
@@ -339,6 +355,12 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
339
355
|
result.area.assign( area );
|
|
340
356
|
result.cosThetaLight.assign( emissiveFacing );
|
|
341
357
|
result.valid.assign( true );
|
|
358
|
+
if ( indexed ) {
|
|
359
|
+
|
|
360
|
+
result.triangleIndex.assign( triangleIndex );
|
|
361
|
+
result.faceNormal.assign( geoNormal );
|
|
362
|
+
|
|
363
|
+
}
|
|
342
364
|
|
|
343
365
|
} );
|
|
344
366
|
|
|
@@ -350,6 +372,9 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
350
372
|
|
|
351
373
|
} );
|
|
352
374
|
|
|
375
|
+
export const sampleLightBVHTriangle = /*@__PURE__*/ makeSampleLightBVHTriangle( false );
|
|
376
|
+
export const sampleLightBVHTriangleIndexed = /*@__PURE__*/ makeSampleLightBVHTriangle( true );
|
|
377
|
+
|
|
353
378
|
// ================================================================================
|
|
354
379
|
// LIGHT-BVH MIS PDF (re-walk)
|
|
355
380
|
// ================================================================================
|
|
@@ -482,6 +507,12 @@ export const calculateLightBVHPdf = Fn( ( [
|
|
|
482
507
|
|
|
483
508
|
// Convert selection pdf → solid-angle measure using the SAME heuristic as the sampler.
|
|
484
509
|
const triData = TriangleData.wrap( fetchTriangleData( triIdx, triangleBuffer, bvhBuffer, targetInstance ) );
|
|
510
|
+
// The sampler draws only the sides the triangle emits from (a unit normal: sideAccepts has a threshold).
|
|
511
|
+
If( sideAccepts( triData.side, dot( rayDir, normalize( cross( triData.v1.sub( triData.v0 ), triData.v2.sub( triData.v0 ) ) ) ) ).not(), () => {
|
|
512
|
+
|
|
513
|
+
selectionPdf.assign( float( 0.0 ) );
|
|
514
|
+
|
|
515
|
+
} );
|
|
485
516
|
If( useSphericalSampling( triData.v0, triData.v1, triData.v2, shadingPoint ), () => {
|
|
486
517
|
|
|
487
518
|
const solidAngle = sphericalTriangleSolidAngle( triData.v0, triData.v1, triData.v2, shadingPoint );
|
|
@@ -490,7 +521,7 @@ export const calculateLightBVHPdf = Fn( ( [
|
|
|
490
521
|
} ).Else( () => {
|
|
491
522
|
|
|
492
523
|
const geoNormal = normalize( cross( triData.v1.sub( triData.v0 ), triData.v2.sub( triData.v0 ) ) );
|
|
493
|
-
const cosLight = max( dot( rayDir
|
|
524
|
+
const cosLight = max( abs( dot( rayDir, geoNormal ) ), float( 0.001 ) );
|
|
494
525
|
const area = triangleArea( triData.v0, triData.v1, triData.v2 );
|
|
495
526
|
const distSq = hitDistance.mul( hitDistance );
|
|
496
527
|
const pdfArea = selectionPdf.div( max( area, float( 1e-10 ) ) );
|
|
@@ -502,6 +533,6 @@ export const calculateLightBVHPdf = Fn( ( [
|
|
|
502
533
|
|
|
503
534
|
} );
|
|
504
535
|
|
|
505
|
-
return max( result, MIN_PDF );
|
|
536
|
+
return select( result.greaterThan( 0.0 ), max( result, MIN_PDF ), float( 0.0 ) );
|
|
506
537
|
|
|
507
538
|
} );
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LightGenerateKernel.js — starts the bidirectional light subpaths, one per pool slot, on a source picked from
|
|
3
|
+
* the source table: an emissive triangle, the physical sky's sun, the environment, or a lamp. Throughput is kept at a max channel
|
|
4
|
+
* of 1, for Russian roulette; its scale rides with the emission data.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
Fn, float, vec3, vec4, int, uint, If, instanceIndex, atomicStore, normalize, cross, dot, max, select, Return,
|
|
9
|
+
sqrt, cos, sin, Loop, sampler, bool as tslBool,
|
|
10
|
+
} from 'three/tsl';
|
|
11
|
+
|
|
12
|
+
import { getRandomSample1D, getRandomSample2D, getDecorrelatedSeed, pcgHash } from './Random.js';
|
|
13
|
+
import { sampleTriangle, fetchTriangleData, binarySearchCDF, TriangleData } from './EmissiveSampling.js';
|
|
14
|
+
import { cosineWeightedSample } from './MaterialSampling.js';
|
|
15
|
+
import { offsetRayOrigin, triangleRow } from './Common.js';
|
|
16
|
+
import { triangleSide } from './BVHTraversal.js';
|
|
17
|
+
import { sampleSunDisc, sunRadianceToward } from './Sun.js';
|
|
18
|
+
import { sampleEnvironment, sampleEnvironmentExact } from './Environment.js';
|
|
19
|
+
import {
|
|
20
|
+
DirectionalLight, AreaLight, PointLight, SpotLight, getDirectionalLight, getAreaLight, getPointLight, getSpotLight,
|
|
21
|
+
getSpotAttenuation, sampleSpotGoboMask, sampleIESProfile, sampleCone, LIGHT_TYPE_DIRECTIONAL, LIGHT_TYPE_AREA, LIGHT_TYPE_POINT,
|
|
22
|
+
} from './LightsCore.js';
|
|
23
|
+
import { areaLightRadiance, areaLightSpreadAttenuation } from './LightsSampling.js';
|
|
24
|
+
import { spotConeSolidAngle, directionalConeSolidAngle } from './BidirectionalLamps.js';
|
|
25
|
+
import {
|
|
26
|
+
lightPathPixel, mis, sunEmissionPdf, sourcePick, sourceLampType, sourceLampIndex, SOURCE, DIM_EMIT,
|
|
27
|
+
} from './Bidirectional.js';
|
|
28
|
+
import { sampleGuidedDisc, guidedDiscPdf } from './LightGuide.js';
|
|
29
|
+
import { RAY_FLAG, COUNTER } from '../Processor/QueueManager.js';
|
|
30
|
+
import {
|
|
31
|
+
writeRayOriginMeta, writeRayDirFlags, writeRayThroughputPdf, writeRayRadiance, writeMediumStack,
|
|
32
|
+
writeRngMis, writeLightOrigin,
|
|
33
|
+
} from '../Processor/PackedRayBuffer.js';
|
|
34
|
+
|
|
35
|
+
const WG_SIZE = 256;
|
|
36
|
+
|
|
37
|
+
export function buildLightGenerateKernel( params ) {
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
rayBufferRW, hitBufferRW, activeIndicesRW, counters,
|
|
41
|
+
lightBuffer, triangleBuffer, bvhBuffer,
|
|
42
|
+
emissiveTriangleCount, emissiveVec4Offset, emissiveTotalPower, emissiveBoost,
|
|
43
|
+
sunDirection, sunRadiance, sunParams, environmentIntensity,
|
|
44
|
+
envTexture, envCDFTexture, envMatrix, envResolution,
|
|
45
|
+
directionalLightsBuffer, areaLightsBuffer, pointLightsBuffer, spotLightsBuffer,
|
|
46
|
+
bidirectional, resolution, frame, transmissiveBounces,
|
|
47
|
+
} = params;
|
|
48
|
+
const { lightPaths } = bidirectional;
|
|
49
|
+
|
|
50
|
+
return Fn( () => {
|
|
51
|
+
|
|
52
|
+
const tid = instanceIndex;
|
|
53
|
+
|
|
54
|
+
If( tid.equal( uint( 0 ) ), () => {
|
|
55
|
+
|
|
56
|
+
atomicStore( counters.element( uint( COUNTER.ACTIVE_RAY_COUNT ) ), lightPaths );
|
|
57
|
+
atomicStore( counters.element( uint( COUNTER.ENTERING_COUNT ) ), lightPaths );
|
|
58
|
+
|
|
59
|
+
} );
|
|
60
|
+
|
|
61
|
+
If( tid.greaterThanEqual( lightPaths ), () => {
|
|
62
|
+
|
|
63
|
+
Return();
|
|
64
|
+
|
|
65
|
+
} );
|
|
66
|
+
|
|
67
|
+
activeIndicesRW.element( tid ).assign( tid );
|
|
68
|
+
|
|
69
|
+
const pixel = lightPathPixel( tid, resolution ).toVar();
|
|
70
|
+
const rng = pcgHash( { state: getDecorrelatedSeed( { pixelCoord: pixel, rayIndex: int( 3 ), frame } ) } ).toVar();
|
|
71
|
+
|
|
72
|
+
const origin = vec3( 0.0 ).toVar();
|
|
73
|
+
const direction = vec3( 0.0, 1.0, 0.0 ).toVar();
|
|
74
|
+
const throughput = vec3( 0.0 ).toVar();
|
|
75
|
+
const dVCM = float( 0.0 ).toVar();
|
|
76
|
+
const dVC = float( 0.0 ).toVar();
|
|
77
|
+
const triangle = int( - 1 ).toVar();
|
|
78
|
+
const instanceLeaf = int( 0 ).toVar();
|
|
79
|
+
const startCos = float( 1.0 ).toVar();
|
|
80
|
+
const valid = tslBool( false ).toVar();
|
|
81
|
+
|
|
82
|
+
const xiSource = getRandomSample1D( pixel, int( 0 ), int( DIM_EMIT + 4 ), rng, resolution, frame ).toVar();
|
|
83
|
+
const source = int( 0 ).toVar();
|
|
84
|
+
const hi = int( bidirectional.sourceCount - 1 ).toVar();
|
|
85
|
+
Loop( source.lessThan( hi ), () => {
|
|
86
|
+
|
|
87
|
+
const mid = source.add( hi ).div( 2 ).toVar();
|
|
88
|
+
If( bidirectional.sourceCdf.element( mid ).lessThanEqual( xiSource ), () => {
|
|
89
|
+
|
|
90
|
+
source.assign( mid.add( 1 ) );
|
|
91
|
+
|
|
92
|
+
} ).Else( () => {
|
|
93
|
+
|
|
94
|
+
hi.assign( mid );
|
|
95
|
+
|
|
96
|
+
} );
|
|
97
|
+
|
|
98
|
+
} );
|
|
99
|
+
|
|
100
|
+
// From the scene's bounding disc facing `toLight`, for a light at infinity, where the guide learned light gets in;
|
|
101
|
+
// returns the start and its area density.
|
|
102
|
+
const guide = bidirectional.guideTexture;
|
|
103
|
+
const discOrigin = ( toLight ) => {
|
|
104
|
+
|
|
105
|
+
const start = sampleGuidedDisc( bidirectional, guide, toLight,
|
|
106
|
+
getRandomSample1D( pixel, int( 0 ), int( DIM_EMIT + 7 ), rng, resolution, frame ),
|
|
107
|
+
getRandomSample2D( pixel, int( 0 ), int( DIM_EMIT + 6 ), rng, resolution, frame ) ).toVar();
|
|
108
|
+
return { start, pdf: guidedDiscPdf( bidirectional, guide, toLight, start ) };
|
|
109
|
+
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
If( source.equal( int( SOURCE.SUN ) ), () => {
|
|
113
|
+
|
|
114
|
+
const toSun = sampleSunDisc( sunDirection, sunParams, getRandomSample2D( pixel, int( 0 ), int( DIM_EMIT + 5 ), rng, resolution, frame ) ).toVar();
|
|
115
|
+
const radiance = sunRadianceToward( toSun, sunDirection, sunRadiance, sunParams ).mul( environmentIntensity ).toVar();
|
|
116
|
+
const disc = discOrigin( toSun );
|
|
117
|
+
const emissionPdf = sunEmissionPdf( bidirectional, sunParams, disc.pdf ).toVar();
|
|
118
|
+
|
|
119
|
+
If( radiance.x.add( radiance.y ).add( radiance.z ).greaterThan( 0.0 ).and( emissionPdf.greaterThan( 0.0 ) ), () => {
|
|
120
|
+
|
|
121
|
+
origin.assign( disc.start );
|
|
122
|
+
direction.assign( toSun.negate() );
|
|
123
|
+
throughput.assign( radiance.div( emissionPdf ) );
|
|
124
|
+
// NEE draws this direction at 1 / solid angle, from any point; the distance is undone at the first hit.
|
|
125
|
+
dVCM.assign( mis( float( 1.0 ).div( sunParams.y.mul( emissionPdf ) ) ) );
|
|
126
|
+
dVC.assign( mis( float( 1.0 ).div( emissionPdf ) ) );
|
|
127
|
+
valid.assign( true );
|
|
128
|
+
|
|
129
|
+
} );
|
|
130
|
+
|
|
131
|
+
} ).ElseIf( source.equal( int( SOURCE.EMITTERS ) ), () => {
|
|
132
|
+
|
|
133
|
+
const pick = binarySearchCDF( lightBuffer, emissiveVec4Offset, emissiveTriangleCount,
|
|
134
|
+
getRandomSample1D( pixel, int( 0 ), int( DIM_EMIT ), rng, resolution, frame ) ).toVar();
|
|
135
|
+
const base = emissiveVec4Offset.add( pick.mul( int( 2 ) ) );
|
|
136
|
+
const entry = lightBuffer.element( base ).toVar(); // triangle, power, cdf, instance leaf
|
|
137
|
+
const emission = lightBuffer.element( base.add( int( 1 ) ) ).toVar(); // rgb, area
|
|
138
|
+
triangle.assign( int( entry.x ) );
|
|
139
|
+
instanceLeaf.assign( int( entry.w ) );
|
|
140
|
+
|
|
141
|
+
const tri = TriangleData.wrap( fetchTriangleData( triangle, triangleBuffer, bvhBuffer, instanceLeaf ) ).toVar();
|
|
142
|
+
const point = sampleTriangle( tri.v0, tri.v1, tri.v2, getRandomSample2D( pixel, int( 0 ), int( DIM_EMIT + 1 ), rng, resolution, frame ) ).toVar();
|
|
143
|
+
const windingN = normalize( cross( tri.v1.sub( tri.v0 ), tri.v2.sub( tri.v0 ) ) ).toVar();
|
|
144
|
+
|
|
145
|
+
const side = triangleSide( triangleRow( triangleBuffer, triangle, 4 ).z ).toVar();
|
|
146
|
+
const back = side.equal( int( 1 ) ).or( side.equal( int( 2 ) )
|
|
147
|
+
.and( getRandomSample1D( pixel, int( 0 ), int( DIM_EMIT + 3 ), rng, resolution, frame ).lessThan( 0.5 ) ) ).toVar();
|
|
148
|
+
const n = select( back, windingN.negate(), windingN ).toVar();
|
|
149
|
+
direction.assign( cosineWeightedSample( n, getRandomSample2D( pixel, int( 0 ), int( DIM_EMIT + 2 ), rng, resolution, frame ) ) );
|
|
150
|
+
const cosLight = dot( direction, n ).toVar();
|
|
151
|
+
|
|
152
|
+
const areaPdf = max( entry.y, 0.0 ).div( max( emissiveTotalPower, 1e-10 ) ).div( max( emission.w, 1e-20 ) ).mul(
|
|
153
|
+
select( side.equal( int( 2 ) ), float( 0.5 ), float( 1.0 ) ) ).mul( bidirectional.emitterPick ).toVar();
|
|
154
|
+
const emissionPdf = areaPdf.mul( cosLight ).div( Math.PI ).toVar();
|
|
155
|
+
|
|
156
|
+
If( cosLight.greaterThan( 1e-6 ).and( emissionPdf.greaterThan( 0.0 ) ), () => {
|
|
157
|
+
|
|
158
|
+
origin.assign( offsetRayOrigin( point, n ) );
|
|
159
|
+
// Le·cos / (pdfA·pdfW) = Le·π / pdfA.
|
|
160
|
+
throughput.assign( emission.xyz.mul( emissiveBoost ).mul( Math.PI ).div( areaPdf ) );
|
|
161
|
+
// NEE's density for this point depends on the first hit; Shade multiplies it into dVCM there.
|
|
162
|
+
dVCM.assign( mis( float( 1.0 ).div( emissionPdf ) ) );
|
|
163
|
+
dVC.assign( mis( cosLight.div( emissionPdf ) ) );
|
|
164
|
+
startCos.assign( cosLight );
|
|
165
|
+
valid.assign( true );
|
|
166
|
+
|
|
167
|
+
} );
|
|
168
|
+
|
|
169
|
+
} ).ElseIf( source.equal( int( SOURCE.ENVIRONMENT ) ), () => {
|
|
170
|
+
|
|
171
|
+
const drawn = sampleEnvironmentExact( envCDFTexture, envMatrix, envResolution,
|
|
172
|
+
getRandomSample2D( pixel, int( 0 ), int( DIM_EMIT + 5 ), rng, resolution, frame ) );
|
|
173
|
+
const toEnvironment = drawn.direction.toVar();
|
|
174
|
+
const directionPdf = drawn.pdf.toVar();
|
|
175
|
+
const radiance = sampleEnvironment( {
|
|
176
|
+
tex: envTexture, samp: sampler( envTexture ), direction: toEnvironment, environmentMatrix: envMatrix, environmentIntensity, enableEnvironmentLight: float( 1.0 ),
|
|
177
|
+
} ).xyz.toVar();
|
|
178
|
+
const disc = discOrigin( toEnvironment );
|
|
179
|
+
const emissionPdf = bidirectional.envPick.mul( directionPdf ).mul( disc.pdf ).toVar();
|
|
180
|
+
|
|
181
|
+
If( radiance.x.add( radiance.y ).add( radiance.z ).greaterThan( 0.0 ).and( emissionPdf.greaterThan( 0.0 ) ), () => {
|
|
182
|
+
|
|
183
|
+
origin.assign( disc.start );
|
|
184
|
+
direction.assign( toEnvironment.negate() );
|
|
185
|
+
throughput.assign( radiance.div( emissionPdf ) );
|
|
186
|
+
// NEE draws the direction from the same table, from any point.
|
|
187
|
+
dVCM.assign( mis( directionPdf.div( emissionPdf ) ) );
|
|
188
|
+
dVC.assign( mis( float( 1.0 ).div( emissionPdf ) ) );
|
|
189
|
+
triangle.assign( int( - 1 - SOURCE.ENVIRONMENT ) );
|
|
190
|
+
valid.assign( true );
|
|
191
|
+
|
|
192
|
+
} );
|
|
193
|
+
|
|
194
|
+
} ).ElseIf( source.greaterThanEqual( int( SOURCE.LAMPS ) ), () => {
|
|
195
|
+
|
|
196
|
+
// NEE's density depends on where the light lands; Shade multiplies it into dVCM there.
|
|
197
|
+
const pick = sourcePick( bidirectional, source ).toVar();
|
|
198
|
+
const type = sourceLampType( bidirectional, source ).toVar();
|
|
199
|
+
const index = sourceLampIndex( bidirectional, source, type ).toVar();
|
|
200
|
+
triangle.assign( int( - 1 ).sub( source ) );
|
|
201
|
+
const emissionPdf = float( 0.0 ).toVar();
|
|
202
|
+
const radiance = vec3( 0.0 ).toVar();
|
|
203
|
+
const xi = getRandomSample2D( pixel, int( 0 ), int( DIM_EMIT + 2 ), rng, resolution, frame ).toVar();
|
|
204
|
+
|
|
205
|
+
If( type.equal( int( LIGHT_TYPE_DIRECTIONAL ) ), () => {
|
|
206
|
+
|
|
207
|
+
const light = DirectionalLight.wrap( getDirectionalLight( directionalLightsBuffer, index ) );
|
|
208
|
+
const toLight = normalize( light.direction ).toVar();
|
|
209
|
+
radiance.assign( light.color.mul( light.intensity ) );
|
|
210
|
+
emissionPdf.assign( pick );
|
|
211
|
+
If( light.angle.greaterThan( 0.0 ), () => {
|
|
212
|
+
|
|
213
|
+
const solidAngle = directionalConeSolidAngle( light ).toVar();
|
|
214
|
+
toLight.assign( sampleCone( toLight, light.angle.mul( 0.5 ), getRandomSample2D( pixel, int( 0 ), int( DIM_EMIT + 5 ), rng, resolution, frame ) ) );
|
|
215
|
+
radiance.divAssign( solidAngle );
|
|
216
|
+
emissionPdf.divAssign( solidAngle );
|
|
217
|
+
|
|
218
|
+
} );
|
|
219
|
+
const disc = discOrigin( toLight );
|
|
220
|
+
emissionPdf.mulAssign( disc.pdf );
|
|
221
|
+
origin.assign( disc.start );
|
|
222
|
+
direction.assign( toLight.negate() );
|
|
223
|
+
|
|
224
|
+
} ).ElseIf( type.equal( int( LIGHT_TYPE_AREA ) ), () => {
|
|
225
|
+
|
|
226
|
+
const light = AreaLight.wrap( getAreaLight( areaLightsBuffer, index ) );
|
|
227
|
+
const disk = light.shape.greaterThan( 0.5 );
|
|
228
|
+
const p = getRandomSample2D( pixel, int( 0 ), int( DIM_EMIT + 1 ), rng, resolution, frame ).toVar();
|
|
229
|
+
const r = sqrt( p.x );
|
|
230
|
+
const phi = p.y.mul( 2 * Math.PI );
|
|
231
|
+
origin.assign( light.position.add( light.u.mul( select( disk, r.mul( cos( phi ) ), p.x.mul( 2.0 ).sub( 1.0 ) ) ) )
|
|
232
|
+
.add( light.v.mul( select( disk, r.mul( sin( phi ) ), p.y.mul( 2.0 ).sub( 1.0 ) ) ) ) );
|
|
233
|
+
direction.assign( cosineWeightedSample( light.normal, xi ) );
|
|
234
|
+
const cosLight = dot( direction, light.normal ).toVar();
|
|
235
|
+
radiance.assign( select( light.area.greaterThan( 0.0 ).and( cosLight.greaterThan( 1e-6 ) ),
|
|
236
|
+
areaLightRadiance( light ).mul( areaLightSpreadAttenuation( cosLight, light.spread ) ).mul( cosLight ), vec3( 0.0 ) ) );
|
|
237
|
+
emissionPdf.assign( pick.div( max( light.area, 1e-20 ) ).mul( cosLight ).div( Math.PI ) );
|
|
238
|
+
dVC.assign( mis( cosLight.div( max( emissionPdf, 1e-30 ) ) ) );
|
|
239
|
+
|
|
240
|
+
} ).ElseIf( type.equal( int( LIGHT_TYPE_POINT ) ), () => {
|
|
241
|
+
|
|
242
|
+
const light = PointLight.wrap( getPointLight( pointLightsBuffer, index ) );
|
|
243
|
+
origin.assign( light.position );
|
|
244
|
+
direction.assign( sampleCone( vec3( 0.0, 1.0, 0.0 ), float( Math.PI ), xi ) );
|
|
245
|
+
radiance.assign( light.color.mul( light.intensity ) );
|
|
246
|
+
emissionPdf.assign( pick.div( 4 * Math.PI ) );
|
|
247
|
+
|
|
248
|
+
} ).Else( () => {
|
|
249
|
+
|
|
250
|
+
const light = SpotLight.wrap( getSpotLight( spotLightsBuffer, index ) );
|
|
251
|
+
origin.assign( light.position );
|
|
252
|
+
direction.assign( sampleCone( light.direction, light.angle, xi ) );
|
|
253
|
+
const toLight = direction.negate();
|
|
254
|
+
radiance.assign( light.color.mul( light.intensity )
|
|
255
|
+
.mul( getSpotAttenuation( { coneCosine: cos( light.angle ), blend: light.penumbra, angleCosine: dot( direction, light.direction ) } ) )
|
|
256
|
+
.mul( sampleSpotGoboMask( light, toLight ) ).mul( sampleIESProfile( light, toLight ) ) );
|
|
257
|
+
emissionPdf.assign( pick.div( spotConeSolidAngle( light ) ) );
|
|
258
|
+
|
|
259
|
+
} );
|
|
260
|
+
|
|
261
|
+
If( radiance.x.add( radiance.y ).add( radiance.z ).greaterThan( 0.0 ).and( emissionPdf.greaterThan( 0.0 ) ), () => {
|
|
262
|
+
|
|
263
|
+
throughput.assign( radiance.div( emissionPdf ) );
|
|
264
|
+
dVCM.assign( mis( float( 1.0 ).div( emissionPdf ) ) );
|
|
265
|
+
valid.assign( true );
|
|
266
|
+
|
|
267
|
+
} );
|
|
268
|
+
|
|
269
|
+
} );
|
|
270
|
+
|
|
271
|
+
If( valid.not(), () => {
|
|
272
|
+
|
|
273
|
+
writeRayDirFlags( rayBufferRW, tid, direction, uint( 0 ) );
|
|
274
|
+
Return();
|
|
275
|
+
|
|
276
|
+
} );
|
|
277
|
+
|
|
278
|
+
const scale = max( max( max( throughput.x, throughput.y ), throughput.z ), 1e-20 ).toVar();
|
|
279
|
+
|
|
280
|
+
writeRayOriginMeta( rayBufferRW, tid, origin, int( 0 ), int( 0 ) );
|
|
281
|
+
writeRayDirFlags( rayBufferRW, tid, direction,
|
|
282
|
+
uint( RAY_FLAG.ACTIVE | RAY_FLAG.REDIRECTED | RAY_FLAG.LIGHT_PATH | RAY_FLAG.LIGHT_EMITTED ) );
|
|
283
|
+
writeRayThroughputPdf( rayBufferRW, tid, throughput.div( scale ), float( 0.0 ) );
|
|
284
|
+
writeRayRadiance( rayBufferRW, tid, vec4( vec3( 0.0 ), float( 1.0 ) ) );
|
|
285
|
+
writeMediumStack( rayBufferRW, tid, uint( 0 ), uint( transmissiveBounces ), float( 1.0 ), float( 1.0 ), float( 1.0 ) );
|
|
286
|
+
writeRngMis( hitBufferRW, tid, rng, dVCM, dVC );
|
|
287
|
+
writeLightOrigin( hitBufferRW, tid, triangle, instanceLeaf, startCos, scale );
|
|
288
|
+
|
|
289
|
+
} );
|
|
290
|
+
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export { WG_SIZE as LIGHT_GENERATE_WG_SIZE };
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where light from infinity gets into the scene, learned from camera paths. A camera path escaping at p in
|
|
3
|
+
* direction ω is where a light path along −ω would have done some good — through a window, past a door — so each
|
|
4
|
+
* escape counts one at p's place on the scene's bounding disc facing ω, per coarse direction. Lights at infinity
|
|
5
|
+
* (the sky, the sun, directional lamps) start their light paths from those counts, a share still drawn uniformly
|
|
6
|
+
* over the disc; every weight reads the same table, so the estimate stays unbiased whatever was learned.
|
|
7
|
+
*
|
|
8
|
+
* The counts sit in the counter buffer past COUNTER.GUIDE (Shade has no binding to spare); a kernel folds them
|
|
9
|
+
* into running sums every so often, which PathTracer copies into the guide texture: GUIDE_BINS rows of
|
|
10
|
+
* GUIDE_CELLS running sums ending at 1, then the row's count of escapes (0 = nothing learned: uniform).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
Fn, float, int, uint, vec2, vec3, ivec2, If, Loop, Return, instanceIndex, abs, floor, max, mix, select, normalize, cross,
|
|
15
|
+
dot, sqrt, cos, sin, atomicAdd, atomicLoad, atomicStore,
|
|
16
|
+
} from 'three/tsl';
|
|
17
|
+
|
|
18
|
+
import { COUNTER, GUIDE_COUNTER_WORDS } from '../Processor/QueueManager.js';
|
|
19
|
+
|
|
20
|
+
export const GUIDE_BIN_SIDE = 4;
|
|
21
|
+
export const GUIDE_BINS = GUIDE_BIN_SIDE * GUIDE_BIN_SIDE;
|
|
22
|
+
export const GUIDE_CELL_SIDE = 64;
|
|
23
|
+
export const GUIDE_CELLS = GUIDE_CELL_SIDE * GUIDE_CELL_SIDE;
|
|
24
|
+
export const GUIDE_TEXTURE_WIDTH = GUIDE_CELLS + 1;
|
|
25
|
+
if ( GUIDE_BINS * GUIDE_CELLS !== GUIDE_COUNTER_WORDS ) throw new Error( 'LightGuide: the counter buffer is sized for another guide' );
|
|
26
|
+
/** Floats per row of the build buffer: the texture's row, padded to the 256 bytes a buffer→texture copy needs. */
|
|
27
|
+
export const GUIDE_ROW_STRIDE = Math.ceil( GUIDE_TEXTURE_WIDTH * 4 / 256 ) * 64;
|
|
28
|
+
/** Share of light paths still drawn uniformly over the disc, so no start is ever ruled out. */
|
|
29
|
+
export const GUIDE_UNIFORM_SHARE = 0.2;
|
|
30
|
+
|
|
31
|
+
// The disc facing `toLight`: its plane's axes, as the light path's start is built on them.
|
|
32
|
+
const discFrame = ( toLight ) => {
|
|
33
|
+
|
|
34
|
+
const u = normalize( cross( select( abs( toLight.x ).greaterThan( 0.9 ), vec3( 0, 1, 0 ), vec3( 1, 0, 0 ) ), toLight ) ).toVar();
|
|
35
|
+
return { u, v: cross( toLight, u ).toVar() };
|
|
36
|
+
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Coarse direction: an octahedral GUIDE_BIN_SIDE² grid.
|
|
40
|
+
const guideBin = ( dir ) => {
|
|
41
|
+
|
|
42
|
+
const d = dir.div( max( abs( dir.x ).add( abs( dir.y ) ).add( abs( dir.z ) ), 1e-20 ) ).toVar();
|
|
43
|
+
const p = vec2( d.x, d.z ).toVar();
|
|
44
|
+
If( d.y.lessThan( 0.0 ), () => {
|
|
45
|
+
|
|
46
|
+
p.assign( vec2(
|
|
47
|
+
float( 1.0 ).sub( abs( d.z ) ).mul( select( d.x.greaterThanEqual( 0.0 ), 1.0, - 1.0 ) ),
|
|
48
|
+
float( 1.0 ).sub( abs( d.x ) ).mul( select( d.z.greaterThanEqual( 0.0 ), 1.0, - 1.0 ) ),
|
|
49
|
+
) );
|
|
50
|
+
|
|
51
|
+
} );
|
|
52
|
+
const q = floor( p.mul( 0.5 ).add( 0.5 ).mul( GUIDE_BIN_SIDE ) ).clamp( 0.0, GUIDE_BIN_SIDE - 1 );
|
|
53
|
+
return int( q.y ).mul( GUIDE_BIN_SIDE ).add( int( q.x ) );
|
|
54
|
+
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// p's place on the disc facing `toLight`, in units of its radius: where the light path through p along −toLight starts.
|
|
58
|
+
const discCoords = ( bdpt, toLight, p ) => {
|
|
59
|
+
|
|
60
|
+
const { u, v } = discFrame( toLight );
|
|
61
|
+
const rel = p.sub( bdpt.sceneCenter );
|
|
62
|
+
return vec2( dot( rel, u ), dot( rel, v ) ).div( bdpt.sceneRadius );
|
|
63
|
+
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// The guide's cell at disc coordinates `st`, or −1 outside the square it covers.
|
|
67
|
+
const guideCell = ( st ) => {
|
|
68
|
+
|
|
69
|
+
const g = st.mul( 0.5 ).add( 0.5 ).mul( GUIDE_CELL_SIDE ).toVar();
|
|
70
|
+
const inside = g.x.greaterThanEqual( 0.0 ).and( g.y.greaterThanEqual( 0.0 ) ).and( g.x.lessThan( GUIDE_CELL_SIDE ) ).and( g.y.lessThan( GUIDE_CELL_SIDE ) );
|
|
71
|
+
return select( inside, int( g.y ).mul( GUIDE_CELL_SIDE ).add( int( g.x ) ), int( - 1 ) );
|
|
72
|
+
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const learned = ( bdpt, guide, bin ) => bdpt.guide.greaterThan( uint( 0 ) ).and( guide.load( ivec2( int( GUIDE_CELLS ), bin ) ).x.greaterThan( 0.0 ) );
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Area density, over the disc facing `toLight`, of the light path whose start projects to `p` along −toLight —
|
|
79
|
+
* what `sceneDiscPdf` was before the guide.
|
|
80
|
+
*/
|
|
81
|
+
export const guidedDiscPdf = ( bdpt, guide, toLight, p ) => {
|
|
82
|
+
|
|
83
|
+
const st = discCoords( bdpt, toLight, p ).toVar();
|
|
84
|
+
const radius2 = bdpt.sceneRadius.mul( bdpt.sceneRadius ).toVar();
|
|
85
|
+
const uniformPdf = select( dot( st, st ).lessThanEqual( 1.0 ), float( 1.0 ).div( max( radius2.mul( Math.PI ), 1e-30 ) ), float( 0.0 ) ).toVar();
|
|
86
|
+
const bin = guideBin( toLight ).toVar();
|
|
87
|
+
const cell = guideCell( st ).toVar();
|
|
88
|
+
const at = ( c ) => guide.load( ivec2( max( c, int( 0 ) ), bin ) ).x;
|
|
89
|
+
const share = select( cell.greaterThanEqual( int( 0 ) ), at( cell ).sub( select( cell.greaterThan( int( 0 ) ), at( cell.sub( int( 1 ) ) ), float( 0.0 ) ) ), float( 0.0 ) );
|
|
90
|
+
// A cell is ( 2R / GUIDE_CELL_SIDE )².
|
|
91
|
+
const cellPdf = share.mul( GUIDE_CELLS / 4 ).div( max( radius2, 1e-30 ) );
|
|
92
|
+
return select( learned( bdpt, guide, bin ), mix( cellPdf, uniformPdf, GUIDE_UNIFORM_SHARE ), uniformPdf );
|
|
93
|
+
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A light path's start on the disc facing `toLight`: from a learned cell with chance 1 − GUIDE_UNIFORM_SHARE
|
|
98
|
+
* (`choice` < that, rescaled into the cell pick), else uniformly over the disc. `xi` places it within either.
|
|
99
|
+
*/
|
|
100
|
+
export const sampleGuidedDisc = ( bdpt, guide, toLight, choice, xi ) => {
|
|
101
|
+
|
|
102
|
+
const { u, v } = discFrame( toLight );
|
|
103
|
+
const st = vec2( 0.0 ).toVar();
|
|
104
|
+
const bin = guideBin( toLight ).toVar();
|
|
105
|
+
If( learned( bdpt, guide, bin ).and( choice.lessThan( 1 - GUIDE_UNIFORM_SHARE ) ), () => {
|
|
106
|
+
|
|
107
|
+
const target = choice.div( 1 - GUIDE_UNIFORM_SHARE ).toVar();
|
|
108
|
+
const lo = int( 0 ).toVar();
|
|
109
|
+
const hi = int( GUIDE_CELLS - 1 ).toVar();
|
|
110
|
+
Loop( lo.lessThan( hi ), () => {
|
|
111
|
+
|
|
112
|
+
const mid = lo.add( hi ).div( 2 ).toVar();
|
|
113
|
+
If( guide.load( ivec2( mid, bin ) ).x.lessThanEqual( target ), () => {
|
|
114
|
+
|
|
115
|
+
lo.assign( mid.add( 1 ) );
|
|
116
|
+
|
|
117
|
+
} ).Else( () => {
|
|
118
|
+
|
|
119
|
+
hi.assign( mid );
|
|
120
|
+
|
|
121
|
+
} );
|
|
122
|
+
|
|
123
|
+
} );
|
|
124
|
+
const cell = vec2( float( lo.mod( int( GUIDE_CELL_SIDE ) ) ), float( lo.div( int( GUIDE_CELL_SIDE ) ) ) );
|
|
125
|
+
st.assign( cell.add( xi ).div( GUIDE_CELL_SIDE ).mul( 2.0 ).sub( 1.0 ) );
|
|
126
|
+
|
|
127
|
+
} ).Else( () => {
|
|
128
|
+
|
|
129
|
+
const r = sqrt( xi.x );
|
|
130
|
+
const phi = xi.y.mul( 2 * Math.PI );
|
|
131
|
+
st.assign( vec2( r.mul( cos( phi ) ), r.mul( sin( phi ) ) ) );
|
|
132
|
+
|
|
133
|
+
} );
|
|
134
|
+
return bdpt.sceneCenter.add( toLight.mul( bdpt.sceneRadius ) ).add( u.mul( st.x ).add( v.mul( st.y ) ).mul( bdpt.sceneRadius ) );
|
|
135
|
+
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/** A camera path escaping at `p` toward `toLight`: one count for light paths that would have reached p. */
|
|
139
|
+
export const recordEscape = ( counters, bdpt, toLight, p ) => {
|
|
140
|
+
|
|
141
|
+
const cell = guideCell( discCoords( bdpt, toLight, p ) ).toVar();
|
|
142
|
+
If( bdpt.guideLearning.greaterThan( uint( 0 ) ).and( cell.greaterThanEqual( int( 0 ) ) ), () => {
|
|
143
|
+
|
|
144
|
+
atomicAdd( counters.element( uint( COUNTER.GUIDE ).add( uint( guideBin( toLight ).mul( int( GUIDE_CELLS ) ).add( cell ) ) ) ), uint( 1 ) );
|
|
145
|
+
|
|
146
|
+
} );
|
|
147
|
+
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/** Folds the counts into the build buffer's rows (GUIDE_ROW_STRIDE floats each), one thread a direction bin. */
|
|
151
|
+
export function buildGuideKernel( { counters, out } ) {
|
|
152
|
+
|
|
153
|
+
return Fn( () => {
|
|
154
|
+
|
|
155
|
+
const bin = instanceIndex;
|
|
156
|
+
If( bin.greaterThanEqual( uint( GUIDE_BINS ) ), () => {
|
|
157
|
+
|
|
158
|
+
Return();
|
|
159
|
+
|
|
160
|
+
} );
|
|
161
|
+
|
|
162
|
+
const base = uint( COUNTER.GUIDE ).add( bin.mul( uint( GUIDE_CELLS ) ) ).toVar();
|
|
163
|
+
const total = float( 0.0 ).toVar();
|
|
164
|
+
Loop( { start: int( 0 ), end: int( GUIDE_CELLS ), type: 'int', condition: '<' }, ( { i } ) => {
|
|
165
|
+
|
|
166
|
+
total.addAssign( float( atomicLoad( counters.element( base.add( uint( i ) ) ) ) ) );
|
|
167
|
+
|
|
168
|
+
} );
|
|
169
|
+
|
|
170
|
+
const row = bin.mul( uint( GUIDE_ROW_STRIDE ) ).toVar();
|
|
171
|
+
const running = float( 0.0 ).toVar();
|
|
172
|
+
Loop( { start: int( 0 ), end: int( GUIDE_CELLS ), type: 'int', condition: '<' }, ( { i } ) => {
|
|
173
|
+
|
|
174
|
+
running.addAssign( float( atomicLoad( counters.element( base.add( uint( i ) ) ) ) ) );
|
|
175
|
+
out.element( row.add( uint( i ) ) ).assign( select( total.greaterThan( 0.0 ),
|
|
176
|
+
select( i.equal( int( GUIDE_CELLS - 1 ) ), float( 1.0 ), running.div( total ) ), float( 0.0 ) ) );
|
|
177
|
+
|
|
178
|
+
} );
|
|
179
|
+
out.element( row.add( uint( GUIDE_CELLS ) ) ).assign( total );
|
|
180
|
+
|
|
181
|
+
} )().compute( GUIDE_BINS, [ GUIDE_BINS ] );
|
|
182
|
+
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Forgets every count: what camera paths saw before a reset says nothing about the scene after it. */
|
|
186
|
+
export function buildGuideClearKernel( { counters } ) {
|
|
187
|
+
|
|
188
|
+
return Fn( () => {
|
|
189
|
+
|
|
190
|
+
If( instanceIndex.lessThan( uint( GUIDE_COUNTER_WORDS ) ), () => {
|
|
191
|
+
|
|
192
|
+
atomicStore( counters.element( uint( COUNTER.GUIDE ).add( instanceIndex ) ), uint( 0 ) );
|
|
193
|
+
|
|
194
|
+
} );
|
|
195
|
+
|
|
196
|
+
} )().compute( GUIDE_COUNTER_WORDS, [ 256 ] );
|
|
197
|
+
|
|
198
|
+
}
|