rayzee 10.0.0 → 11.0.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 +252 -44
- package/dist/ActiveColor-YvuQq44O.js +13 -0
- package/dist/ArchiveImporter-BRQvLggb.js +4300 -0
- package/dist/AssetConfig-DSEntE65.js +30 -0
- package/dist/BidirectionalIntegrator-9lZQxFVH.js +934 -0
- package/dist/BufferLayout-D_IBo2ci.js +150 -0
- package/dist/ColorManagement-oeEk7oSu.js +1488 -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-dDDGj9Av.js +97 -0
- package/dist/{NeuralRetouch-CYgkQY8Z.js → NeuralRetouch-BCXylThH.js} +8 -6
- package/dist/{NeuralSuperRes-PkgDLQcD.js → NeuralSuperRes-sZAeS142.js} +55 -120
- package/dist/PathTracer-CQPW06Mm.js +29971 -0
- package/dist/PhysicalSky-BvqCw_dy.js +1219 -0
- package/dist/RayzeeRenderer-COpwotpU.js +3551 -0
- package/dist/SolarLimb-CPaeZVwz.js +721 -0
- package/dist/ToneMapGPU-CjU3osDd.js +177 -0
- package/dist/ViewTransforms-CYN7bq2w.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 +2551 -44024
- package/dist/rayzee.umd.js +528 -487
- package/dist/sceneCachePolicy-BSSsKY98.js +443 -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/Color/ViewTransforms.js +5 -1
- package/src/EngineDefaults.js +62 -1029
- package/src/EngineEvents.js +2 -0
- package/src/EngineIssues.js +2 -0
- package/src/Headless.js +6 -5
- package/src/Passes/AIUpscaler.js +58 -141
- package/src/Passes/OIDNDenoiser.js +92 -65
- package/src/Passes/OIDNTemporalHistory.js +1 -1
- package/src/PathTracerApp.js +620 -4026
- 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 +327 -1763
- package/src/Processor/AtmosphereModel.js +1 -2
- package/src/Processor/BVHBuilder.js +14 -126
- package/src/Processor/BVHLeafFold.js +4 -6
- package/src/Processor/BVHRefitter.js +67 -46
- package/src/Processor/BufferLayout.js +291 -0
- package/src/Processor/EmissiveTriangleBuilder.js +1 -1
- 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 +15 -5
- package/src/Processor/HostMemory.js +7 -1
- package/src/Processor/IESParser.js +47 -35
- package/src/Processor/InstanceTable.js +1 -1
- package/src/Processor/KernelManager.js +35 -2
- package/src/Processor/MaterialPacking.js +10 -8
- 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 +39 -5
- package/src/Processor/ReinsertionOptimizer.js +2 -2
- package/src/Processor/SceneProcessor.js +53 -62
- package/src/Processor/ShaderBuilder.js +6 -16
- package/src/Processor/SimpleSky.js +1 -62
- package/src/Processor/SolarLimb.js +3 -0
- package/src/Processor/SplitBLAS.js +1 -1
- package/src/Processor/StorageTexturePool.js +22 -1
- package/src/Processor/TLASBuilder.js +1 -3
- package/src/Processor/TextureBuckets.js +237 -0
- package/src/Processor/TextureCreator.js +181 -8
- package/src/Processor/ToneMapGPU.js +64 -15
- package/src/Processor/Workers/BVHRefitWorker.js +5 -2
- 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/Workers/TexturesWorker.js +103 -18
- package/src/Processor/archiveFormats.js +7 -0
- package/src/Processor/utils.js +2 -1
- package/src/RayzeeRenderer.js +3899 -0
- package/src/RenderSettings.js +68 -62
- package/src/Stages/ASVGF.js +13 -14
- package/src/Stages/AutoExposure.js +17 -17
- package/src/Stages/BilateralFilter.js +2 -1
- package/src/Stages/Compositor.js +8 -23
- package/src/Stages/DenoiserSettings.js +182 -0
- package/src/Stages/EdgeFilter.js +2 -1
- package/src/Stages/MotionVector.js +1 -1
- package/src/Stages/NRD.js +11 -5
- package/src/Stages/NormalDepth.js +12 -8
- package/src/Stages/PathTracer.js +338 -43
- package/src/Stages/PathTracerStage.js +69 -66
- package/src/Stages/Variance.js +1 -1
- 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 +45 -9
- package/src/Storage/areas.js +4 -0
- package/src/TSL/BVHTraversal.js +50 -25
- 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 +20 -15
- 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 +65 -18
- 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/LightVisibility.js +111 -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 +239 -51
- 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 +5 -1
- package/src/TSL/SceneResources.js +51 -0
- package/src/TSL/ShadeKernel.js +964 -101
- package/src/TSL/Struct.js +10 -0
- package/src/TSL/Sun.js +1 -1
- package/src/TSL/TextureSampling.js +66 -61
- 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 +31 -0
- package/src/index.js +13 -23
- package/src/integrators/BidirectionalIntegrator.js +833 -0
- package/src/managers/AnimationManager.js +88 -6
- package/src/managers/CameraManager.js +103 -46
- package/src/managers/DenoisingManager.js +14 -10
- package/src/managers/EnvironmentManager.js +125 -103
- package/src/managers/GoboManager.js +9 -0
- package/src/managers/MaterialDataManager.js +48 -37
- package/src/managers/UniformManager.js +32 -10
- package/dist/ToneMapGPU-B6XIe3DU.js +0 -544
- package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
- package/src/Processor/TreeletOptimizer.js +0 -574
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LightSplatKernel.js — light tracing: every stored light vertex to the pinhole, into the pixel it
|
|
3
|
+
* projects to. Fixed-point u32 channels: WGSL has no float atomics, and integer sums are order-free.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Fn, float, vec3, vec4, int, uint, If, instanceIndex, atomicAdd, atomicLoad, atomicStore, Return,
|
|
8
|
+
dot, sqrt, max, min, floor, length, localId, workgroupId,
|
|
9
|
+
} from 'three/tsl';
|
|
10
|
+
|
|
11
|
+
import { evaluateMaterialResponse } from './MaterialEvaluation.js';
|
|
12
|
+
import { calculateMaterialPDF } from './LightsSampling.js';
|
|
13
|
+
import { traceShadowRayRefractiveOpaque } from './LightsDirect.js';
|
|
14
|
+
import { traverseBVHShadowCameraCulled } from './BVHTraversal.js';
|
|
15
|
+
import { offsetRayOrigin, SHADOW_END, sanitizeRGB } from './Common.js';
|
|
16
|
+
import { pcgHash } from './Random.js';
|
|
17
|
+
import { regularizePathContribution } from './PathTracerCore.js';
|
|
18
|
+
import { resolveSurfaceMaterial, lightEndCosine, misPartial, misWeight, strategyWeight, STRATEGY, SPLAT_SCALE, SPLAT_MAX } from './Bidirectional.js';
|
|
19
|
+
import {
|
|
20
|
+
cachedVertex, readVertexRecord, readLightPathLength, readRayRadiance, writeRayRadiance,
|
|
21
|
+
} from '../Processor/PackedRayBuffer.js';
|
|
22
|
+
|
|
23
|
+
const WG_SIZE = 256;
|
|
24
|
+
const RESOLVE_WG_SIZE = 16;
|
|
25
|
+
|
|
26
|
+
export function buildLightSplatKernel( params ) {
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
hitBufferRO, splatBuffer,
|
|
30
|
+
bvhBuffer, triangleBuffer, materialBuffer,
|
|
31
|
+
lightPaths, slotsPerPath, lightTag, strategyView,
|
|
32
|
+
cameraPosition, cameraForward, cameraViewMatrix, cameraProjectionMatrix, pixelArea,
|
|
33
|
+
renderWidth, renderHeight,
|
|
34
|
+
globalIlluminationIntensity, fireflyThreshold, accumFrame, frame,
|
|
35
|
+
mergeVm = null,
|
|
36
|
+
} = params;
|
|
37
|
+
|
|
38
|
+
return Fn( () => {
|
|
39
|
+
|
|
40
|
+
const slot = instanceIndex.toVar();
|
|
41
|
+
If( slot.greaterThanEqual( lightPaths.mul( slotsPerPath ) ), () => {
|
|
42
|
+
|
|
43
|
+
Return();
|
|
44
|
+
|
|
45
|
+
} );
|
|
46
|
+
|
|
47
|
+
const c = slot.mod( slotsPerPath ).toVar();
|
|
48
|
+
const stored = readLightPathLength( hitBufferRO, cachedVertex( slot.sub( c ) ), lightTag );
|
|
49
|
+
If( c.greaterThanEqual( stored ), () => {
|
|
50
|
+
|
|
51
|
+
Return();
|
|
52
|
+
|
|
53
|
+
} );
|
|
54
|
+
|
|
55
|
+
const record = readVertexRecord( hitBufferRO, cachedVertex( slot ) );
|
|
56
|
+
const v = { ...record, vm: mergeVm?.( record.position ) ?? null };
|
|
57
|
+
|
|
58
|
+
const toCamera = cameraPosition.sub( v.position ).toVar();
|
|
59
|
+
const dist2 = max( dot( toCamera, toCamera ), 1e-12 ).toVar();
|
|
60
|
+
const dir = toCamera.div( sqrt( dist2 ) ).toVar();
|
|
61
|
+
const cosFacet = dot( dir, v.facetN ).toVar();
|
|
62
|
+
const cosCamera = dot( dir, cameraForward ).negate().toVar();
|
|
63
|
+
// The camera is this vertex's viewer: like a camera path, only the facet decides which side it is on.
|
|
64
|
+
If( cosFacet.lessThanEqual( 0.0 ).or( cosCamera.lessThanEqual( 1e-6 ) ).or( v.extra.notEqual( uint( 0 ) ) ), () => {
|
|
65
|
+
|
|
66
|
+
Return();
|
|
67
|
+
|
|
68
|
+
} );
|
|
69
|
+
|
|
70
|
+
const clip = cameraProjectionMatrix.mul( cameraViewMatrix.mul( vec4( v.position, 1.0 ) ) ).toVar();
|
|
71
|
+
If( clip.w.lessThanEqual( 0.0 ), () => {
|
|
72
|
+
|
|
73
|
+
Return();
|
|
74
|
+
|
|
75
|
+
} );
|
|
76
|
+
|
|
77
|
+
const px = floor( clip.x.div( clip.w ).mul( 0.5 ).add( 0.5 ).mul( float( renderWidth ) ) ).toVar();
|
|
78
|
+
const py = floor( float( 0.5 ).sub( clip.y.div( clip.w ).mul( 0.5 ) ).mul( float( renderHeight ) ) ).toVar();
|
|
79
|
+
If( px.lessThan( 0.0 ).or( py.lessThan( 0.0 ) ).or( px.greaterThanEqual( float( renderWidth ) ) ).or( py.greaterThanEqual( float( renderHeight ) ) ), () => {
|
|
80
|
+
|
|
81
|
+
Return();
|
|
82
|
+
|
|
83
|
+
} );
|
|
84
|
+
|
|
85
|
+
const material = resolveSurfaceMaterial( v.materialIndex, v.uv, v.N, materialBuffer );
|
|
86
|
+
const f = evaluateMaterialResponse( dir, v.V, v.N, material ).toVar();
|
|
87
|
+
|
|
88
|
+
// The pinhole's solid-angle density for a uniform point in a pixel: 1 / (A_pixel cos³θ).
|
|
89
|
+
const cameraPdfW = float( 1.0 ).div( pixelArea.mul( cosCamera ).mul( cosCamera ).mul( cosCamera ) );
|
|
90
|
+
const toSurface = cameraPdfW.div( dist2 ).toVar();
|
|
91
|
+
|
|
92
|
+
// Georgiev (46); the camera side has no sum of its own.
|
|
93
|
+
const wLight = misPartial( toSurface.mul( cosFacet ).div( float( lightPaths ) ), v, calculateMaterialPDF( dir, v.V, v.N, material ) );
|
|
94
|
+
const bounces = c.add( uint( 1 ) );
|
|
95
|
+
const gi = float( 1.0 ).toVar();
|
|
96
|
+
If( bounces.greaterThan( uint( 1 ) ), () => {
|
|
97
|
+
|
|
98
|
+
gi.assign( globalIlluminationIntensity );
|
|
99
|
+
|
|
100
|
+
} );
|
|
101
|
+
|
|
102
|
+
const contribution = regularizePathContribution(
|
|
103
|
+
v.throughput.mul( f ).mul( lightEndCosine( v.V, v.N, v.facetN, dir ) )
|
|
104
|
+
.mul( toSurface.div( float( lightPaths ) ).mul( strategyWeight( strategyView, STRATEGY.LIGHT_TRACE, misWeight( wLight, float( 0.0 ) ) ) ).mul( gi ) ),
|
|
105
|
+
float( c ), fireflyThreshold, int( accumFrame ),
|
|
106
|
+
).toVar();
|
|
107
|
+
|
|
108
|
+
const origin = offsetRayOrigin( v.position, v.facetN ).toVar();
|
|
109
|
+
const segment = cameraPosition.sub( origin ).toVar();
|
|
110
|
+
const segmentLength = length( segment ).toVar();
|
|
111
|
+
// This segment stands in for the primary ray, which sees through the faces it culls.
|
|
112
|
+
const visibility = traceShadowRayRefractiveOpaque(
|
|
113
|
+
origin, segment.div( segmentLength ), segmentLength.mul( SHADOW_END ),
|
|
114
|
+
traverseBVHShadowCameraCulled, bvhBuffer, triangleBuffer, materialBuffer,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
If( visibility.greaterThan( 0.0 ), () => {
|
|
118
|
+
|
|
119
|
+
const value = min( sanitizeRGB( contribution.mul( visibility ) ), vec3( SPLAT_MAX ) ).mul( SPLAT_SCALE ).toVar();
|
|
120
|
+
const dither = float( pcgHash( { state: slot.bitXor( uint( frame ).mul( uint( 0x9E3779B9 ) ) ) } ).shiftRight( uint( 8 ) ) ).mul( 1.0 / 16777216.0 ).toVar();
|
|
121
|
+
const base = uint( py ).mul( uint( renderWidth ) ).add( uint( px ) ).mul( uint( 3 ) ).toVar();
|
|
122
|
+
atomicAdd( splatBuffer.element( base ), uint( floor( value.x.add( dither ) ) ) );
|
|
123
|
+
atomicAdd( splatBuffer.element( base.add( uint( 1 ) ) ), uint( floor( value.y.add( dither ) ) ) );
|
|
124
|
+
atomicAdd( splatBuffer.element( base.add( uint( 2 ) ) ), uint( floor( value.z.add( dither ) ) ) );
|
|
125
|
+
|
|
126
|
+
} );
|
|
127
|
+
|
|
128
|
+
} );
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function buildSplatResolveKernel( params ) {
|
|
133
|
+
|
|
134
|
+
const { rayBufferRW, splatBuffer, renderWidth, chunkRowBase, chunkRows } = params;
|
|
135
|
+
|
|
136
|
+
return Fn( () => {
|
|
137
|
+
|
|
138
|
+
const gx = int( workgroupId.x ).mul( RESOLVE_WG_SIZE ).add( int( localId.x ) );
|
|
139
|
+
const localGy = int( workgroupId.y ).mul( RESOLVE_WG_SIZE ).add( int( localId.y ) );
|
|
140
|
+
|
|
141
|
+
If( gx.lessThan( renderWidth ).and( localGy.lessThan( chunkRows ) ), () => {
|
|
142
|
+
|
|
143
|
+
const rayID = uint( localGy.mul( renderWidth ).add( gx ) );
|
|
144
|
+
const base = uint( localGy.add( chunkRowBase ).mul( renderWidth ).add( gx ) ).mul( uint( 3 ) ).toVar();
|
|
145
|
+
const r = atomicLoad( splatBuffer.element( base ) ).toVar();
|
|
146
|
+
const g = atomicLoad( splatBuffer.element( base.add( uint( 1 ) ) ) ).toVar();
|
|
147
|
+
const b = atomicLoad( splatBuffer.element( base.add( uint( 2 ) ) ) ).toVar();
|
|
148
|
+
|
|
149
|
+
If( r.bitOr( g ).bitOr( b ).notEqual( uint( 0 ) ), () => {
|
|
150
|
+
|
|
151
|
+
atomicStore( splatBuffer.element( base ), uint( 0 ) );
|
|
152
|
+
atomicStore( splatBuffer.element( base.add( uint( 1 ) ) ), uint( 0 ) );
|
|
153
|
+
atomicStore( splatBuffer.element( base.add( uint( 2 ) ) ), uint( 0 ) );
|
|
154
|
+
const radiance = readRayRadiance( rayBufferRW, rayID ).toVar();
|
|
155
|
+
writeRayRadiance( rayBufferRW, rayID, vec4( radiance.xyz.add( vec3( float( r ), float( g ), float( b ) ).div( SPLAT_SCALE ) ), radiance.w ) );
|
|
156
|
+
|
|
157
|
+
} );
|
|
158
|
+
|
|
159
|
+
} );
|
|
160
|
+
|
|
161
|
+
} );
|
|
162
|
+
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export { WG_SIZE as LIGHT_SPLAT_WG_SIZE, RESOLVE_WG_SIZE as SPLAT_RESOLVE_WG_SIZE };
|