rayzee 9.4.1 → 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 +226 -25
- 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 -44125
- 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 +23 -14
- 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/SimpleSky.js +1 -62
- 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 +94 -91
- 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/ShadeKernel.js
CHANGED
|
@@ -7,35 +7,46 @@
|
|
|
7
7
|
import {
|
|
8
8
|
Fn, float, vec2, vec3, vec4, int, uint,
|
|
9
9
|
bool as tslBool,
|
|
10
|
-
If, Loop, normalize, max, min, exp, log, clamp, dot, length, select, smoothstep, acos,
|
|
10
|
+
If, Loop, normalize, max, min, exp, log, clamp, dot, length, select, smoothstep, acos, abs, cross,
|
|
11
11
|
instanceIndex,
|
|
12
12
|
sampler,
|
|
13
13
|
atomicLoad, atomicStore,
|
|
14
14
|
Return,
|
|
15
15
|
} from 'three/tsl';
|
|
16
16
|
|
|
17
|
-
import { sampleEnvironment,
|
|
18
|
-
import { getMaterial, powerHeuristic, balanceHeuristic, classifyMaterial, REC709_LUMINANCE_COEFFICIENTS, PI_INV, EPSILON, diffuseGroundMaterial, offsetRayOrigin, SHADOW_END, triangleRow, MIN_ROUGHNESS } from './Common.js';
|
|
17
|
+
import { sampleEnvironment, groundProjectedEnvDir, sampleEnvironmentExact, environmentPdfExact } from './Environment.js';
|
|
18
|
+
import { getMaterial, powerHeuristic, balanceHeuristic, classifyMaterial, REC709_LUMINANCE_COEFFICIENTS, PI_INV, EPSILON, diffuseGroundMaterial, offsetRayOrigin, SHADOW_END, triangleRow, MIN_ROUGHNESS, MIN_PDF } from './Common.js';
|
|
19
19
|
import { cosineWeightedSample } from './MaterialSampling.js';
|
|
20
20
|
import { sampleAllMaterialTextures, processAnisotropyMap, applyExtensionMaps, getTransformedUV, triangleUVTangent } from './TextureSampling.js';
|
|
21
21
|
import { evaluateMaterialResponse } from './MaterialEvaluation.js';
|
|
22
|
-
import {
|
|
23
|
-
|
|
22
|
+
import {
|
|
23
|
+
calculateDirectLightingUnified, calculateDirectLightingThroughSurfaces, calculateMaterialPDF,
|
|
24
|
+
sampleDirectionalLight, sampleRectAreaLight, samplePointLightWithAttenuation, sampleSpotLightWithRadius,
|
|
25
|
+
areaLightRadiance, areaLightSpreadAttenuation,
|
|
26
|
+
} from './LightsSampling.js';
|
|
27
|
+
import { traceShadowRay, traceShadowRayRefractiveOpaque, estimateLightImportance } from './LightsDirect.js';
|
|
24
28
|
import { shadowTerminatorOrigin } from './ShadowTerminator.js';
|
|
25
|
-
import { unpackHitFacet } from './HitFacet.js';
|
|
29
|
+
import { hitFacet, unpackHitFacet, windingNormal } from './HitFacet.js';
|
|
26
30
|
import { traverseBVHShadow, triangleSide, sideAccepts } from './BVHTraversal.js';
|
|
27
31
|
import { handleMaterialTransparency, MaterialInteractionResult } from './MaterialTransmission.js';
|
|
28
32
|
import { sampleChromaticCollision, sampleHenyeyGreenstein, subsurfaceCoefficients, CollisionSample, MediumCoeffs } from './Subsurface.js';
|
|
33
|
+
import { ALL_MATERIAL_LAYERS } from './SceneResources.js';
|
|
29
34
|
import { calculateIndirectLighting } from './LightsIndirect.js';
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
import {
|
|
36
|
+
IndirectLightingResult, sampleCone, LightSample, DirectionalLight, AreaLight, PointLight, SpotLight,
|
|
37
|
+
getDirectionalLight, getAreaLight, getPointLight, getSpotLight, intersectAreaLight, getDistanceAttenuation, sampleDirectionalGoboMask,
|
|
38
|
+
LIGHT_TYPE_DIRECTIONAL, LIGHT_TYPE_AREA, LIGHT_TYPE_POINT, LIGHT_TYPE_SPOT,
|
|
39
|
+
} from './LightsCore.js';
|
|
40
|
+
import { sunRadianceToward, sampleSunDisc } from './Sun.js';
|
|
41
|
+
import { regularizePathContribution, generateSampledDirection, computeNDCDepth, handleRussianRoulette, DELTA_PDF } from './PathTracerCore.js';
|
|
33
42
|
import { evaluateSpecularDFG, baseFresnelParams } from './MaterialProperties.js';
|
|
34
|
-
import { NRD_HIT_DIST_A, NRD_HIT_DIST_B } from '../EngineDefaults.js';
|
|
35
43
|
import { sampleClearcoat, ClearcoatResult } from './Clearcoat.js';
|
|
36
44
|
import { refineDisplacedIntersection, DisplacementResult } from './Displacement.js';
|
|
37
|
-
import {
|
|
38
|
-
|
|
45
|
+
import {
|
|
46
|
+
calculateEmissiveTriangleContribution, calculateEmissiveLightPdf, EmissiveSample, EmissiveSampleIndexed,
|
|
47
|
+
sampleEmissiveTriangleIndexed, fetchTriangleData, TriangleData,
|
|
48
|
+
} from './EmissiveSampling.js';
|
|
49
|
+
import { sampleLightBVHTriangle, sampleLightBVHTriangleIndexed, calculateLightBVHPdf } from './LightBVHSampling.js';
|
|
39
50
|
import {
|
|
40
51
|
Ray,
|
|
41
52
|
HitInfo,
|
|
@@ -65,6 +76,7 @@ import {
|
|
|
65
76
|
readRayRadiance,
|
|
66
77
|
readFeatureThroughput, writeFeatureThroughput,
|
|
67
78
|
readRngState, writeRngState,
|
|
79
|
+
readMisState, writeRngMis, pendingVertex, cachedVertex, writeVertexRecord, writeLightPathLength, readLightOrigin,
|
|
68
80
|
} from '../Processor/PackedRayBuffer.js';
|
|
69
81
|
|
|
70
82
|
const WG_SIZE = 256;
|
|
@@ -88,7 +100,7 @@ export function buildShadeKernel( params ) {
|
|
|
88
100
|
enableEnvironmentLight,
|
|
89
101
|
groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight, groundProjectionLevel,
|
|
90
102
|
enableGroundCatcher, groundCatcherHeight,
|
|
91
|
-
envTotalSum,
|
|
103
|
+
envTotalSum, envResolution,
|
|
92
104
|
directionalLightsBuffer, numDirectionalLights,
|
|
93
105
|
areaLightsBuffer, numAreaLights,
|
|
94
106
|
pointLightsBuffer, numPointLights,
|
|
@@ -110,15 +122,33 @@ export function buildShadeKernel( params ) {
|
|
|
110
122
|
emissiveBoost, enableEmissiveTriangleSampling,
|
|
111
123
|
lightBVHNodeCount, reverseMapVec4Offset,
|
|
112
124
|
maxRayCount,
|
|
113
|
-
// Aux G-buffer (normal/depth/albedo + surface ID) feeds only the denoiser/OIDN MRT.
|
|
114
|
-
// live uniform (1 = denoiser on)
|
|
115
|
-
|
|
125
|
+
// Aux G-buffer (normal/depth/albedo + surface ID) feeds only the denoiser/OIDN MRT. Compiled in only when
|
|
126
|
+
// requested (PathTracer.requestOutput( 'gBuffer' )); then a live uniform (1 = denoiser on) skips the writes
|
|
127
|
+
// while nothing consumes them.
|
|
128
|
+
auxGBufferEnabled, gBuffer = true,
|
|
116
129
|
hasSun, sunDirection, sunRadiance, sunParams,
|
|
130
|
+
// Bidirectional: light subpaths shade here too; camera vertices leave connections for ConnectKernel.
|
|
131
|
+
bidirectional: bdpt = null,
|
|
132
|
+
// The hit-distance output, when a stage asked for it (PathTracer.requestOutput).
|
|
133
|
+
hitDistanceEncode = null,
|
|
134
|
+
// The material layers some material uses (MaterialDataManager.materialLayers); the rest compile out. Bidirectional
|
|
135
|
+
// always leaves diffuse transmission out: its connections and light tracing do not cross surfaces yet.
|
|
136
|
+
materialLayers: layers = ALL_MATERIAL_LAYERS,
|
|
117
137
|
} = params;
|
|
138
|
+
const diffuseTransmission = layers.diffuseTransmission;
|
|
118
139
|
|
|
119
|
-
|
|
140
|
+
// The bidirectional shading functions come with the integrator's uniforms (BidirectionalIntegrator), only used where bdpt is.
|
|
141
|
+
const {
|
|
142
|
+
mis, misOnHit, misOnSpecular, misOnScatter, misPartial, misWeight, emitterSideProbability, emitterAreaPdf, lightEndCosine, mergeVmAt, strategyWeight, sunEmissionPdf, STRATEGY, LIGHT_PIXEL_ROW_OFFSET, sourcePick, lampSource, sourceLampType, sourceLampIndex, SOURCE, guidedDiscPdf, recordEscape, LampPick, pickLamp, lampPickPdf, lampCount, spotConeSolidAngle, directionalConeSolidAngle, areaLightDirectPdfW,
|
|
143
|
+
} = bdpt?.lib ?? {};
|
|
144
|
+
|
|
145
|
+
const auxOn = gBuffer ? auxGBufferEnabled.greaterThan( uint( 0 ) ) : null;
|
|
120
146
|
|
|
121
147
|
const useEmissiveNEE = lightBuffer !== undefined;
|
|
148
|
+
const lamps = {
|
|
149
|
+
directional: directionalLightsBuffer, numDirectional: numDirectionalLights, area: areaLightsBuffer, numArea: numAreaLights,
|
|
150
|
+
point: pointLightsBuffer, numPoint: numPointLights, spot: spotLightsBuffer, numSpot: numSpotLights,
|
|
151
|
+
};
|
|
122
152
|
|
|
123
153
|
// Stochastic cone-jitter blur of an env backdrop lookup. Plain JS inliner (NOT a Fn — an rng Fn-param
|
|
124
154
|
// would freeze; see TSL pitfalls) so it mutates the caller's rngState .toVar() directly. Shared by the
|
|
@@ -150,6 +180,29 @@ export function buildShadeKernel( params ) {
|
|
|
150
180
|
|
|
151
181
|
};
|
|
152
182
|
|
|
183
|
+
// Emissive NEE's solid-angle density for a triangle's point, from whichever sampler it runs.
|
|
184
|
+
const emissiveNEEPdf = ( triangle, dist, toLight, from, instanceLeaf ) => {
|
|
185
|
+
|
|
186
|
+
const pdf = float( 0.0 ).toVar();
|
|
187
|
+
If( lightBVHNodeCount.greaterThan( int( 0 ) ), () => {
|
|
188
|
+
|
|
189
|
+
pdf.assign( calculateLightBVHPdf(
|
|
190
|
+
triangle, dist, toLight, from,
|
|
191
|
+
lightBuffer, emissiveVec4Offset, reverseMapVec4Offset, triangleBuffer, bvhBuffer,
|
|
192
|
+
) );
|
|
193
|
+
|
|
194
|
+
} ).Else( () => {
|
|
195
|
+
|
|
196
|
+
pdf.assign( calculateEmissiveLightPdf(
|
|
197
|
+
triangle, dist, toLight, from,
|
|
198
|
+
triangleBuffer, materialBuffer, emissiveTotalPower, bvhBuffer, instanceLeaf,
|
|
199
|
+
) );
|
|
200
|
+
|
|
201
|
+
} );
|
|
202
|
+
return pdf;
|
|
203
|
+
|
|
204
|
+
};
|
|
205
|
+
|
|
153
206
|
const computeFn = Fn( () => {
|
|
154
207
|
|
|
155
208
|
const threadIdx = instanceIndex;
|
|
@@ -186,6 +239,11 @@ export function buildShadeKernel( params ) {
|
|
|
186
239
|
|
|
187
240
|
} );
|
|
188
241
|
|
|
242
|
+
// A light subpath gathers nothing; camera-only work is gated on it.
|
|
243
|
+
const isLight = bdpt ? flags.bitAnd( uint( RAY_FLAG.LIGHT_PATH ) ).notEqual( uint( 0 ) ).toVar() : null;
|
|
244
|
+
const cameraOnly = ( cond ) => ( bdpt ? cond.and( isLight.not() ) : cond );
|
|
245
|
+
const aux = auxOn ? cameraOnly( auxOn ) : null;
|
|
246
|
+
|
|
189
247
|
// Backdrop-view = the ray still travels the original camera direction (only alpha/transparent passthrough
|
|
190
248
|
// since the camera, REDIRECTED still clear). Captured at ARRIVAL (before the opaque/redirect bitOr below)
|
|
191
249
|
// so it stays valid for both the miss branch and the emissive-hit scale. This — not bounceIndex==0 — is
|
|
@@ -200,6 +258,11 @@ export function buildShadeKernel( params ) {
|
|
|
200
258
|
// One ray per pixel: rayID is the pixel index.
|
|
201
259
|
const pixelIndex = rayID;
|
|
202
260
|
const rngState = readRngState( hitBufferRW, rayID ).toVar();
|
|
261
|
+
const subpathState = bdpt ? readMisState( hitBufferRW, rayID ) : null;
|
|
262
|
+
const subpath = bdpt ? { dVCM: subpathState.dVCM.toVar(), dVC: subpathState.dVC.toVar(), vm: null } : null;
|
|
263
|
+
const persistRng = () => ( bdpt
|
|
264
|
+
? writeRngMis( hitBufferRW, rayID, rngState, subpath.dVCM, subpath.dVC )
|
|
265
|
+
: writeRngState( hitBufferRW, rayID, rngState ) );
|
|
203
266
|
|
|
204
267
|
// Samples key on the GLOBAL pixel. pixelIndex is the LOCAL path slot; the global pixel
|
|
205
268
|
// = chunkRowBase·W + localSlot, so sequences stay aligned across row-band chunks
|
|
@@ -207,9 +270,10 @@ export function buildShadeKernel( params ) {
|
|
|
207
270
|
// Hoisted here rather than at the BSDF draw because the ground catcher runs NEE far earlier.
|
|
208
271
|
const _resX = int( resolution.x ).toVar();
|
|
209
272
|
const _globalPixel = int( pixelIndex ).add( chunkRowBase.mul( _resX ) );
|
|
273
|
+
const _pixelRow = float( _globalPixel.div( _resX ) ).add( 0.5 );
|
|
210
274
|
const _pixelCoord = vec2(
|
|
211
275
|
float( _globalPixel.mod( _resX ) ).add( 0.5 ),
|
|
212
|
-
|
|
276
|
+
bdpt ? _pixelRow.add( select( isLight, float( LIGHT_PIXEL_ROW_OFFSET ), float( 0.0 ) ) ) : _pixelRow,
|
|
213
277
|
);
|
|
214
278
|
// This bounce's block of sampler dimensions — see the budget table in Random.js.
|
|
215
279
|
const dimBase = int( currentBounce ).mul( int( SAMPLER_DIMS_PER_BOUNCE ) );
|
|
@@ -217,7 +281,7 @@ export function buildShadeKernel( params ) {
|
|
|
217
281
|
// DDFA see-through aux tint carried across smooth glass/mirror; committed into the OIDN/ASVGF
|
|
218
282
|
// albedo guide at the first diffuse-enough surface (or env). Mutated locally, persisted on every
|
|
219
283
|
// deferring continue (a missed persist = stale tint). 1 = untinted (direct hit → today's albedo).
|
|
220
|
-
const featCarry = readFeatureThroughput( rayBufferRW, rayID ).toVar();
|
|
284
|
+
const featCarry = aux ? readFeatureThroughput( rayBufferRW, rayID ).toVar() : null;
|
|
221
285
|
|
|
222
286
|
const hitDist = readHitDistance( hitBufferRW, rayID ).toVar();
|
|
223
287
|
const hitNormal = readHitNormal( hitBufferRW, rayID ).toVar();
|
|
@@ -234,14 +298,13 @@ export function buildShadeKernel( params ) {
|
|
|
234
298
|
const sssSteps = readSssSteps( rayBufferRW, rayID ).toVar();
|
|
235
299
|
const transparentCount = readTransparentCount( rayBufferRW, rayID ).toVar();
|
|
236
300
|
|
|
237
|
-
//
|
|
301
|
+
// Hit distance: first segment after the primary scatter, plus any alpha-skip run so a cutout hole
|
|
238
302
|
// doesn't shorten it. Unconditional at depth 1, so the post-skip segment wins.
|
|
239
|
-
If(
|
|
303
|
+
if ( hitDistanceEncode && aux ) If( aux.and( cameraDepth.equal( 1 ) ), () => {
|
|
240
304
|
|
|
241
305
|
const scatterViewZ = cameraViewMatrix.mul( vec4( origin, 1.0 ) ).z.abs();
|
|
242
|
-
const norm = scatterViewZ.mul( NRD_HIT_DIST_B ).add( NRD_HIT_DIST_A );
|
|
243
306
|
const total = readMisRayT( rayBufferRW, rayID ).add( min( hitDist, float( 1e6 ) ) );
|
|
244
|
-
writeGBufferHitDist( gBufferRW, pixelIndex,
|
|
307
|
+
writeGBufferHitDist( gBufferRW, pixelIndex, hitDistanceEncode( total, scatterViewZ ) );
|
|
245
308
|
|
|
246
309
|
} );
|
|
247
310
|
|
|
@@ -252,7 +315,7 @@ export function buildShadeKernel( params ) {
|
|
|
252
315
|
// ratio in alpha (rgb = 0) so it composites as bg·ratio over a transparent background.
|
|
253
316
|
// Shadows are caught by the EXISTING NEE shadow ray into real geometry — the plane itself
|
|
254
317
|
// never enters the BVH. Secondary bounces ignore it entirely.
|
|
255
|
-
If( enableGroundCatcher.and( bounceIndex.equal( 0 ) ), () => {
|
|
318
|
+
If( cameraOnly( enableGroundCatcher.and( bounceIndex.equal( 0 ) ) ), () => {
|
|
256
319
|
|
|
257
320
|
const dirY = direction.y.toVar();
|
|
258
321
|
If( dirY.abs().greaterThan( float( EPSILON ) ), () => {
|
|
@@ -286,7 +349,7 @@ export function buildShadeKernel( params ) {
|
|
|
286
349
|
bvhBuffer, triangleBuffer, materialBuffer,
|
|
287
350
|
envTexture, environmentIntensity, envMatrix,
|
|
288
351
|
envCDFTexture,
|
|
289
|
-
envTotalSum,
|
|
352
|
+
envTotalSum, envResolution,
|
|
290
353
|
enableEnvironmentLight,
|
|
291
354
|
tslBool( true ), // wantUnoccluded
|
|
292
355
|
vec3( 0.0 ), planeN, float( 0.0 ),
|
|
@@ -351,7 +414,7 @@ export function buildShadeKernel( params ) {
|
|
|
351
414
|
// The catcher is a real ground surface for the denoiser — write the plane's normal/depth
|
|
352
415
|
// + a neutral albedo (black albedo would break OIDN demodulation) and mark the pixel a
|
|
353
416
|
// valid surface, so OIDN/ASVGF don't smear the caught shadow as a background miss.
|
|
354
|
-
If(
|
|
417
|
+
if ( aux ) If( aux, () => {
|
|
355
418
|
|
|
356
419
|
const planeDepth = computeNDCDepth( { worldPos: planePoint, cameraProjectionMatrix, cameraViewMatrix } );
|
|
357
420
|
writeGBuffer( gBufferRW, pixelIndex, planeN, planeDepth, vec3( 1.0 ) );
|
|
@@ -361,7 +424,7 @@ export function buildShadeKernel( params ) {
|
|
|
361
424
|
writeRayRadiance( rayBufferRW, rayID, vec4( outRgb, outAlpha ) );
|
|
362
425
|
// DDFA: the plane's aux (written above) is a committed surface — lock it (cosmetic; ray dies here).
|
|
363
426
|
writeRayDirFlags( rayBufferRW, rayID, direction, flags.bitOr( uint( RAY_FLAG.AUX_LOCKED ) ).bitAnd( uint( ~ RAY_FLAG.ACTIVE ) ) );
|
|
364
|
-
|
|
427
|
+
persistRng();
|
|
365
428
|
Return();
|
|
366
429
|
|
|
367
430
|
} );
|
|
@@ -372,6 +435,13 @@ export function buildShadeKernel( params ) {
|
|
|
372
435
|
|
|
373
436
|
If( hitDist.greaterThan( MISS_DIST ), () => {
|
|
374
437
|
|
|
438
|
+
if ( bdpt ) If( isLight, () => {
|
|
439
|
+
|
|
440
|
+
writeRayDirFlags( rayBufferRW, rayID, direction, flags.bitAnd( uint( ~ RAY_FLAG.ACTIVE ) ) );
|
|
441
|
+
Return();
|
|
442
|
+
|
|
443
|
+
} );
|
|
444
|
+
|
|
375
445
|
// Background and environment-lighting are decoupled (independent axes):
|
|
376
446
|
// • Visible backdrop: a PRIMARY ray draws the env image only when showBackground — regardless
|
|
377
447
|
// of enableEnvironmentLight (so you can show the HDRI without it lighting the scene).
|
|
@@ -381,12 +451,19 @@ export function buildShadeKernel( params ) {
|
|
|
381
451
|
// is the direct backdrop", so env seen through alpha-cutout foliage holes is treated identically to
|
|
382
452
|
// the open sky (blur, intensity, show/hide, color-mode, ground projection all match).
|
|
383
453
|
// isBackdropView was captured at arrival (above) so it survives the REDIRECTED bitOr on opaque hits.
|
|
454
|
+
// A camera path out to the sky: where light from infinity gets in (LightGuide.js).
|
|
455
|
+
if ( bdpt ) If( isBackdropView.not().and( isLight.not() ), () => {
|
|
456
|
+
|
|
457
|
+
recordEscape( counters, bdpt, direction, origin );
|
|
458
|
+
|
|
459
|
+
} );
|
|
460
|
+
|
|
384
461
|
const wantBackdrop = isBackdropView.and( showBackground ); // draw env image as backdrop
|
|
385
462
|
const wantEnvLight = isBackdropView.not().and( enableEnvironmentLight ); // env as light on redirected bounces
|
|
386
463
|
|
|
387
464
|
// DDFA: env colour a redirected ray escaping to the environment sees (e.g. smooth glass over
|
|
388
465
|
// sky). Committed (tinted by featCarry) into the aux G-buffer before the miss Return below.
|
|
389
|
-
const escapedAuxAlbedo = vec3( 0.0 ).toVar();
|
|
466
|
+
const escapedAuxAlbedo = aux ? vec3( 0.0 ).toVar() : null;
|
|
390
467
|
|
|
391
468
|
If( wantBackdrop.or( wantEnvLight ), () => {
|
|
392
469
|
|
|
@@ -428,19 +505,27 @@ export function buildShadeKernel( params ) {
|
|
|
428
505
|
|
|
429
506
|
// DDFA: remember the (clamped) env colour this escaped ray sees, for the redirected-escape
|
|
430
507
|
// aux commit below (tinted by the accumulated see-through throughput).
|
|
431
|
-
escapedAuxAlbedo.assign( clamp( envColor, vec3( 0.0 ), vec3( 1.0 ) ) );
|
|
508
|
+
if ( aux ) escapedAuxAlbedo.assign( clamp( envColor, vec3( 0.0 ), vec3( 1.0 ) ) );
|
|
432
509
|
|
|
433
510
|
// MIS weight for implicit env hit — prevents double-counting with NEE
|
|
434
511
|
const envMisWeight = float( 1.0 ).toVar();
|
|
435
|
-
If( isBackdropView.not(), () => {
|
|
512
|
+
if ( bdpt ) If( isBackdropView.not(), () => {
|
|
513
|
+
|
|
514
|
+
// Georgiev (43) for the environment at infinity, from the exact table its NEE and light paths draw from.
|
|
515
|
+
const pdfW = select( bdpt.envTable.greaterThan( uint( 0 ) ), environmentPdfExact( envCDFTexture, envMatrix, envResolution, direction ), float( 0.0 ) ).toVar();
|
|
516
|
+
const directPdfW = select( flags.bitAnd( uint( RAY_FLAG.SUN_NEE ) ).notEqual( uint( 0 ) ), pdfW, float( 0.0 ) );
|
|
517
|
+
const emissionPdfW = select( bdpt.lightPaths.greaterThan( uint( 0 ) ),
|
|
518
|
+
bdpt.envPick.mul( pdfW ).mul( guidedDiscPdf( bdpt, bdpt.guideTexture, direction, origin ) ), float( 0.0 ) );
|
|
519
|
+
envMisWeight.assign( strategyWeight( bdpt.strategyView, STRATEGY.HIT,
|
|
520
|
+
misWeight( float( 0.0 ), mis( directPdfW ).mul( subpath.dVCM ).add( mis( emissionPdfW ).mul( subpath.dVC ) ) ) ) );
|
|
521
|
+
|
|
522
|
+
} );
|
|
523
|
+
else If( isBackdropView.not(), () => {
|
|
436
524
|
|
|
437
525
|
const prevBouncePdf = readRayPdf( rayBufferRW, rayID );
|
|
438
|
-
If( prevBouncePdf.greaterThan( 0.0 ), () => {
|
|
526
|
+
If( prevBouncePdf.greaterThan( 0.0 ).and( envTotalSum.greaterThan( 0.0 ) ), () => {
|
|
439
527
|
|
|
440
|
-
const
|
|
441
|
-
envTexture, direction, envMatrix, envTotalSum, envCompensationDelta, envResolution,
|
|
442
|
-
);
|
|
443
|
-
const envPdf = envEval.w;
|
|
528
|
+
const envPdf = environmentPdfExact( envCDFTexture, envMatrix, envResolution, direction ).toVar();
|
|
444
529
|
If( envPdf.greaterThan( 0.0 ), () => {
|
|
445
530
|
|
|
446
531
|
envMisWeight.assign( balanceHeuristic( { pdf1: prevBouncePdf, pdf2: envPdf } ) ); // megakernel parity (PathTracerCore.js:774): env NEE also uses balance
|
|
@@ -498,11 +583,29 @@ export function buildShadeKernel( params ) {
|
|
|
498
583
|
If( sunL.x.add( sunL.y ).add( sunL.z ).greaterThan( 0.0 ), () => {
|
|
499
584
|
|
|
500
585
|
const sunW = float( 1.0 ).toVar();
|
|
501
|
-
|
|
586
|
+
if ( bdpt ) {
|
|
502
587
|
|
|
503
|
-
|
|
588
|
+
// Georgiev (43) for a light at infinity: no distance, and NEE's density is solid angle.
|
|
589
|
+
If( isBackdropView.not(), () => {
|
|
590
|
+
|
|
591
|
+
const directPdfW = select( flags.bitAnd( uint( RAY_FLAG.SUN_NEE ) ).notEqual( uint( 0 ) ), float( 1.0 ).div( sunParams.y ), float( 0.0 ) );
|
|
592
|
+
const emissionPdfW = select( bdpt.lightPaths.greaterThan( uint( 0 ) ),
|
|
593
|
+
sunEmissionPdf( bdpt, sunParams, guidedDiscPdf( bdpt, bdpt.guideTexture, direction, origin ) ), float( 0.0 ) );
|
|
594
|
+
sunW.assign( strategyWeight( bdpt.strategyView, STRATEGY.HIT,
|
|
595
|
+
misWeight( float( 0.0 ), mis( directPdfW ).mul( subpath.dVCM ).add( mis( emissionPdfW ).mul( subpath.dVC ) ) ) ) );
|
|
596
|
+
|
|
597
|
+
} );
|
|
598
|
+
|
|
599
|
+
} else {
|
|
600
|
+
|
|
601
|
+
If( isBackdropView.not().and( flags.bitAnd( uint( RAY_FLAG.SUN_NEE ) ).notEqual( uint( 0 ) ) ), () => {
|
|
602
|
+
|
|
603
|
+
sunW.assign( powerHeuristic( { pdf1: readRayPdf( rayBufferRW, rayID ), pdf2: float( 1.0 ).div( sunParams.y ) } ) );
|
|
604
|
+
|
|
605
|
+
} );
|
|
606
|
+
|
|
607
|
+
}
|
|
504
608
|
|
|
505
|
-
} );
|
|
506
609
|
const vertex = max( bounceIndex.sub( 1 ), int( 0 ) );
|
|
507
610
|
const sunScale = select( isBackdropView, backgroundIntensity,
|
|
508
611
|
sunW.mul( select( vertex.greaterThan( int( 0 ) ), globalIlluminationIntensity, float( 1.0 ) ) ) );
|
|
@@ -553,7 +656,7 @@ export function buildShadeKernel( params ) {
|
|
|
553
656
|
// per pixel → OIDN sees structure tracking the refracted view); depth kept at the primary hit.
|
|
554
657
|
// AUX_LOCKED-clear alone means "never committed" — the old black-probe is redundant. A direct
|
|
555
658
|
// backdrop (REDIRECTED clear) keeps Generate's black/far default (regression-safe sky guide).
|
|
556
|
-
If(
|
|
659
|
+
if ( aux ) If( aux
|
|
557
660
|
.and( flags.bitAnd( uint( RAY_FLAG.AUX_LOCKED ) ).equal( uint( 0 ) ) )
|
|
558
661
|
.and( flags.bitAnd( uint( RAY_FLAG.REDIRECTED ) ).notEqual( uint( 0 ) ) ), () => {
|
|
559
662
|
|
|
@@ -568,8 +671,110 @@ export function buildShadeKernel( params ) {
|
|
|
568
671
|
|
|
569
672
|
} );
|
|
570
673
|
|
|
674
|
+
// On the triangle's plane: Extend stores the distance to it (ExtendKernel).
|
|
571
675
|
const hitPoint = origin.add( direction.mul( hitDist ) ).toVar();
|
|
676
|
+
// Vertex merging: a merge here, weighed in every sum this vertex ends or scatters.
|
|
677
|
+
if ( bdpt?.merging ) subpath.vm = mergeVmAt( bdpt, hitPoint ).toVar();
|
|
572
678
|
const N = normalize( hitNormal ).toVar();
|
|
679
|
+
// The hit record's facet is 11-bit; light tracing's and connections' grazing geometry terms need it exact.
|
|
680
|
+
const exactFacetN = bdpt ? hitFacet( {
|
|
681
|
+
triangleBuffer, bvhBuffer, triIdx: int( hitTriIdx ), instanceLeaf: hitInstance, hitPoint,
|
|
682
|
+
smoothNormal: N, viewDir: direction.negate(), didHit: tslBool( true ), liftEnabled: tslBool( false ),
|
|
683
|
+
} ).faceN : null;
|
|
684
|
+
|
|
685
|
+
// Where a lamp's light path first lands: its falloff and a directional gobo apply here.
|
|
686
|
+
const lampLanding = bdpt ? { source: int( - 1 ).toVar(), dist: float( 0.0 ).toVar() } : null;
|
|
687
|
+
const landLampPath = ( dist ) => {
|
|
688
|
+
|
|
689
|
+
const start = readLightOrigin( hitBufferRW, rayID );
|
|
690
|
+
const source = int( - 1 ).sub( start.triangle ).toVar();
|
|
691
|
+
If( start.triangle.lessThan( int( 0 ) ).and( source.greaterThanEqual( int( SOURCE.LAMPS ) ) ), () => {
|
|
692
|
+
|
|
693
|
+
const type = sourceLampType( bdpt, source ).toVar();
|
|
694
|
+
const index = sourceLampIndex( bdpt, source, type ).toVar();
|
|
695
|
+
If( type.equal( int( LIGHT_TYPE_DIRECTIONAL ) ), () => {
|
|
696
|
+
|
|
697
|
+
throughput.mulAssign( sampleDirectionalGoboMask( DirectionalLight.wrap( getDirectionalLight( directionalLightsBuffer, index ) ), hitPoint ) );
|
|
698
|
+
|
|
699
|
+
} ).ElseIf( type.equal( int( LIGHT_TYPE_POINT ) ), () => {
|
|
700
|
+
|
|
701
|
+
const light = PointLight.wrap( getPointLight( pointLightsBuffer, index ) );
|
|
702
|
+
throughput.mulAssign( getDistanceAttenuation( { lightDistance: dist, cutoffDistance: light.distance, decayExponent: light.decay } ).mul( dist.mul( dist ) ) );
|
|
703
|
+
|
|
704
|
+
} ).ElseIf( type.equal( int( LIGHT_TYPE_SPOT ) ), () => {
|
|
705
|
+
|
|
706
|
+
const light = SpotLight.wrap( getSpotLight( spotLightsBuffer, index ) );
|
|
707
|
+
throughput.mulAssign( getDistanceAttenuation( { lightDistance: dist, cutoffDistance: light.distance, decayExponent: light.decay } ).mul( dist.mul( dist ) ) );
|
|
708
|
+
|
|
709
|
+
} );
|
|
710
|
+
lampLanding.source.assign( source );
|
|
711
|
+
lampLanding.dist.assign( dist );
|
|
712
|
+
|
|
713
|
+
} );
|
|
714
|
+
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
// Georgiev (43) for the rect light a continuation reaches. Lamps are not geometry, so the new ray is tested
|
|
718
|
+
// here, after any scatter, with glass blocking as it does NEE's. `nearPick` gives NEE's pick of it here.
|
|
719
|
+
const bidirectionalAreaHit = ( from, dir, nearPick ) => {
|
|
720
|
+
|
|
721
|
+
const nearestT = float( 1e30 ).toVar();
|
|
722
|
+
const nearest = int( - 1 ).toVar();
|
|
723
|
+
Loop( { start: int( 0 ), end: numAreaLights, type: 'int', condition: '<' }, ( { i } ) => {
|
|
724
|
+
|
|
725
|
+
const light = AreaLight.wrap( getAreaLight( areaLightsBuffer, i ) );
|
|
726
|
+
If( light.intensity.greaterThan( 0.0 ), () => {
|
|
727
|
+
|
|
728
|
+
const t = intersectAreaLight( light, hitPoint, dir ).toVar();
|
|
729
|
+
If( t.greaterThan( 0.0 ).and( t.lessThan( nearestT ) ), () => {
|
|
730
|
+
|
|
731
|
+
nearestT.assign( t );
|
|
732
|
+
nearest.assign( i );
|
|
733
|
+
|
|
734
|
+
} );
|
|
735
|
+
|
|
736
|
+
} );
|
|
737
|
+
|
|
738
|
+
} );
|
|
739
|
+
|
|
740
|
+
If( nearest.greaterThanEqual( int( 0 ) ), () => {
|
|
741
|
+
|
|
742
|
+
const light = AreaLight.wrap( getAreaLight( areaLightsBuffer, nearest ) );
|
|
743
|
+
const cosLight = dot( dir, light.normal ).negate().toVar();
|
|
744
|
+
const toLight = hitPoint.add( dir.mul( nearestT ) ).sub( from ).toVar();
|
|
745
|
+
const len = length( toLight ).toVar();
|
|
746
|
+
const visibility = traceShadowRayRefractiveOpaque(
|
|
747
|
+
from, toLight.div( len ), len.mul( SHADOW_END ), traverseBVHShadow, bvhBuffer, triangleBuffer, materialBuffer,
|
|
748
|
+
).toVar();
|
|
749
|
+
|
|
750
|
+
If( visibility.greaterThan( 0.0 ).and( cosLight.greaterThan( 0.0 ) ), () => {
|
|
751
|
+
|
|
752
|
+
const arrived = { dVCM: subpath.dVCM.toVar(), dVC: subpath.dVC.toVar() };
|
|
753
|
+
misOnHit( arrived, nearestT, cosLight );
|
|
754
|
+
const neePdfA = nearPick
|
|
755
|
+
? nearPick( light ).mul( areaLightDirectPdfW( light, hitPoint, nearestT, cosLight ) ).mul( cosLight ).div( max( nearestT.mul( nearestT ), 1e-30 ) )
|
|
756
|
+
: float( 0.0 );
|
|
757
|
+
const emissionPdf = select( bdpt.lightPaths.greaterThan( uint( 0 ) ),
|
|
758
|
+
sourcePick( bdpt, lampSource( bdpt, int( LIGHT_TYPE_AREA ), nearest ) ).div( max( light.area, 1e-20 ) ).mul( cosLight ).div( Math.PI ), float( 0.0 ) );
|
|
759
|
+
const weight = strategyWeight( bdpt.strategyView, STRATEGY.HIT,
|
|
760
|
+
misWeight( float( 0.0 ), mis( neePdfA ).mul( arrived.dVCM ).add( mis( emissionPdf ).mul( arrived.dVC ) ) ) );
|
|
761
|
+
|
|
762
|
+
currentRadiance.assign( vec4(
|
|
763
|
+
currentRadiance.xyz.add(
|
|
764
|
+
regularizePathContribution(
|
|
765
|
+
areaLightRadiance( light ).mul( areaLightSpreadAttenuation( cosLight, light.spread ) ).mul( throughput ).mul( visibility ).mul( weight )
|
|
766
|
+
.mul( select( bounceIndex.greaterThan( 0 ), globalIlluminationIntensity, float( 1.0 ) ) ),
|
|
767
|
+
float( bounceIndex ), fireflyThreshold, int( accumFrame ),
|
|
768
|
+
),
|
|
769
|
+
),
|
|
770
|
+
currentRadiance.w,
|
|
771
|
+
) );
|
|
772
|
+
|
|
773
|
+
} );
|
|
774
|
+
|
|
775
|
+
} );
|
|
776
|
+
|
|
777
|
+
};
|
|
573
778
|
|
|
574
779
|
// medium stack read once here; reused by the transparency block below
|
|
575
780
|
const medStack = readMediumStack( rayBufferRW, rayID );
|
|
@@ -585,23 +790,33 @@ export function buildShadeKernel( params ) {
|
|
|
585
790
|
If( mediumStackDepth.greaterThan( 0 ), () => {
|
|
586
791
|
|
|
587
792
|
const mSigmaA = readMediumSigmaA( rayBufferRW, rayID ).toVar();
|
|
588
|
-
const
|
|
589
|
-
const mSigmaS = sssMed.sigmaS.toVar();
|
|
590
|
-
const mG = sssMed.g.toVar();
|
|
591
|
-
|
|
592
|
-
If( max( max( mSigmaS.x, mSigmaS.y ), mSigmaS.z ).lessThanEqual( 0.0 ), () => {
|
|
793
|
+
const absorb = () => {
|
|
593
794
|
|
|
594
795
|
// glass: Beer-Lambert absorption
|
|
595
796
|
const beer = exp( mSigmaA.mul( hitDist ).negate() ).toVar();
|
|
596
797
|
throughput.mulAssign( beer );
|
|
597
798
|
// DDFA: colored-glass volume tints the deferred aux guide by the same absorption.
|
|
598
|
-
If(
|
|
799
|
+
if ( aux ) If( aux.and( flags.bitAnd( uint( RAY_FLAG.AUX_LOCKED ) ).equal( uint( 0 ) ) ), () => {
|
|
599
800
|
|
|
600
801
|
featCarry.mulAssign( beer );
|
|
601
802
|
|
|
602
803
|
} );
|
|
603
804
|
|
|
604
|
-
}
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
// Without subsurface materials every medium is glass (scattering 0).
|
|
808
|
+
if ( ! layers.subsurface ) {
|
|
809
|
+
|
|
810
|
+
absorb();
|
|
811
|
+
return;
|
|
812
|
+
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const sssMed = readSSSMedium( rayBufferRW, rayID );
|
|
816
|
+
const mSigmaS = sssMed.sigmaS.toVar();
|
|
817
|
+
const mG = sssMed.g.toVar();
|
|
818
|
+
|
|
819
|
+
If( max( max( mSigmaS.x, mSigmaS.y ), mSigmaS.z ).lessThanEqual( 0.0 ), absorb ).Else( () => {
|
|
605
820
|
|
|
606
821
|
// subsurface: chromatic collision-distance sampling
|
|
607
822
|
const mSigmaT = mSigmaA.add( mSigmaS );
|
|
@@ -628,7 +843,7 @@ export function buildShadeKernel( params ) {
|
|
|
628
843
|
|
|
629
844
|
writeRayRadiance( rayBufferRW, rayID, currentRadiance );
|
|
630
845
|
writeRayDirFlags( rayBufferRW, rayID, direction, flags.bitAnd( uint( ~ RAY_FLAG.ACTIVE ) ) );
|
|
631
|
-
|
|
846
|
+
persistRng();
|
|
632
847
|
Return();
|
|
633
848
|
|
|
634
849
|
} );
|
|
@@ -645,7 +860,7 @@ export function buildShadeKernel( params ) {
|
|
|
645
860
|
// fires the next hit's env/emissive MIS, down-weighting SSS-then-env/emitter views.
|
|
646
861
|
writeRayThroughputPdf( rayBufferRW, rayID, throughput, readRayPdf( rayBufferRW, rayID ) );
|
|
647
862
|
writeRayRadiance( rayBufferRW, rayID, currentRadiance );
|
|
648
|
-
|
|
863
|
+
persistRng();
|
|
649
864
|
Return();
|
|
650
865
|
|
|
651
866
|
} );
|
|
@@ -659,6 +874,33 @@ export function buildShadeKernel( params ) {
|
|
|
659
874
|
const material = RayTracingMaterial.wrap(
|
|
660
875
|
getMaterial( int( hitMatIdx ), materialBuffer )
|
|
661
876
|
).toVar();
|
|
877
|
+
if ( ! diffuseTransmission || bdpt ) {
|
|
878
|
+
|
|
879
|
+
material.diffuseTransmission.assign( 0.0 );
|
|
880
|
+
material.diffuseTransmissionMapIndex.assign( int( - 1 ) );
|
|
881
|
+
material.diffuseTransmissionColorMapIndex.assign( int( - 1 ) );
|
|
882
|
+
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// A layer no material has is 0 in every material; saying so lets the compiler fold what still reads it.
|
|
886
|
+
for ( const layer of [ 'clearcoat', 'sheen', 'iridescence', 'anisotropy', 'subsurface', 'dispersion' ] ) {
|
|
887
|
+
|
|
888
|
+
if ( ! layers[ layer ] ) material[ layer ].assign( 0.0 );
|
|
889
|
+
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// The segment past the last bounce is traced only for the light it hits: an opaque surface that does not glow
|
|
893
|
+
// ends it before any texture is read (emission is factor × map, so a zero factor is zero).
|
|
894
|
+
If( flags.bitAnd( uint( RAY_FLAG.EMISSION_ONLY ) ).notEqual( uint( 0 ) )
|
|
895
|
+
.and( material.alphaMode.equal( int( 0 ) ) ).and( material.transmission.lessThanEqual( 0.0 ) ).and( material.subsurface.lessThanEqual( 0.0 ) )
|
|
896
|
+
.and( max( max( material.emissive.x, material.emissive.y ), material.emissive.z ).mul( material.emissiveIntensity ).lessThanEqual( 0.0 ) ), () => {
|
|
897
|
+
|
|
898
|
+
writeRayRadiance( rayBufferRW, rayID, currentRadiance );
|
|
899
|
+
writeRayDirFlags( rayBufferRW, rayID, direction, flags.bitAnd( uint( ~ RAY_FLAG.ACTIVE ) ) );
|
|
900
|
+
persistRng();
|
|
901
|
+
Return();
|
|
902
|
+
|
|
903
|
+
} );
|
|
662
904
|
|
|
663
905
|
// displacement: analytical ray-height marching refines hitPoint/UV/normal; no-op without a map
|
|
664
906
|
const samplingUV = hitUV.toVar();
|
|
@@ -715,7 +957,7 @@ export function buildShadeKernel( params ) {
|
|
|
715
957
|
const extUV = getTransformedUV( { uv: samplingUV, transform: material.albedoTransform } ).toVar();
|
|
716
958
|
|
|
717
959
|
// Fold the anisotropy texture (if any) into the scalar anisotropy/rotation used by the BRDF
|
|
718
|
-
If( material.anisotropyMapIndex.greaterThanEqual( int( 0 ) ), () => {
|
|
960
|
+
if ( layers.anisotropy ) If( material.anisotropyMapIndex.greaterThanEqual( int( 0 ) ), () => {
|
|
719
961
|
|
|
720
962
|
const aniso = processAnisotropyMap( material, extUV ).toVar();
|
|
721
963
|
material.anisotropy.assign( aniso.x );
|
|
@@ -736,6 +978,8 @@ export function buildShadeKernel( params ) {
|
|
|
736
978
|
material.iridescenceThicknessRange.assign( vec2( material.iridescenceThicknessRange.x, extMaps.iridescenceThickness ) );
|
|
737
979
|
material.specularIntensity.assign( extMaps.specularIntensity );
|
|
738
980
|
material.specularColor.assign( extMaps.specularColor );
|
|
981
|
+
material.diffuseTransmission.assign( extMaps.diffuseTransmission );
|
|
982
|
+
material.diffuseTransmissionColor.assign( extMaps.diffuseTransmissionColor );
|
|
739
983
|
|
|
740
984
|
// Below the floor the GGX peak is past f32, so a plain reflector reflects exactly: roughness 0
|
|
741
985
|
// is the delta-lobe marker the sampler and evaluator read. The other lobes keep the floor.
|
|
@@ -769,19 +1013,19 @@ export function buildShadeKernel( params ) {
|
|
|
769
1013
|
const metal = material.metalness.toVar();
|
|
770
1014
|
const trans = material.transmission.toVar();
|
|
771
1015
|
const roughFrac = smoothstep( float( 0.0 ), float( 0.15 ), rawRough ).toVar();
|
|
772
|
-
const auxCommit = clamp(
|
|
1016
|
+
const auxCommit = aux ? clamp(
|
|
773
1017
|
float( 1.0 ).sub( metal ).mul( float( 1.0 ).sub( trans ) )
|
|
774
1018
|
.add( roughFrac.mul( metal.mul( float( 1.0 ).sub( trans ) ).add( trans ) ) ),
|
|
775
1019
|
0.0, 1.0,
|
|
776
|
-
).greaterThanEqual( float( 0.25 ) ).toVar();
|
|
777
|
-
const featPrefix = featCarry.toVar();
|
|
1020
|
+
).greaterThanEqual( float( 0.25 ) ).toVar() : null;
|
|
1021
|
+
const featPrefix = aux ? featCarry.toVar() : null;
|
|
778
1022
|
|
|
779
1023
|
// DDFA terminal fallback: a ray dying while still deferring (e.g. a mirror maze) commits its last
|
|
780
1024
|
// surface instead of leaving a black guide OIDN would demod-amplify.
|
|
781
1025
|
// Plain JS inliner, not an Fn — it closes over albedo/featPrefix/flags .toVar()s.
|
|
782
1026
|
const commitDeferredAux = ( normal ) => {
|
|
783
1027
|
|
|
784
|
-
If(
|
|
1028
|
+
if ( aux ) If( aux.and( flags.bitAnd( uint( RAY_FLAG.AUX_LOCKED ) ).equal( uint( 0 ) ) ), () => {
|
|
785
1029
|
|
|
786
1030
|
const primaryDepth = gbDecodeNormalDepth( readGBuffer( gBufferRW, pixelIndex ) ).w;
|
|
787
1031
|
writeGBuffer( gBufferRW, pixelIndex, normal, primaryDepth, clamp( albedo.mul( featPrefix ), vec3( 0.0 ), vec3( 1.0 ) ) );
|
|
@@ -795,14 +1039,14 @@ export function buildShadeKernel( params ) {
|
|
|
795
1039
|
|
|
796
1040
|
writeRayRadiance( rayBufferRW, rayID, currentRadiance );
|
|
797
1041
|
writeRayDirFlags( rayBufferRW, rayID, direction, flags.bitAnd( uint( ~ RAY_FLAG.ACTIVE ) ) );
|
|
798
|
-
|
|
1042
|
+
persistRng();
|
|
799
1043
|
|
|
800
1044
|
};
|
|
801
1045
|
|
|
802
1046
|
// first-hit MRT data (bounce 0 only): write the primary DEPTH now with the default normal/albedo.
|
|
803
1047
|
// The real normal/albedo are committed by the DDFA decision blocks below (which re-pack this depth);
|
|
804
1048
|
// they may defer through smooth glass/mirror and commit at the first diffuse-enough surface or the env.
|
|
805
|
-
If( bounceIndex.equal( 0 ).and(
|
|
1049
|
+
if ( aux ) If( bounceIndex.equal( 0 ).and( aux ), () => {
|
|
806
1050
|
|
|
807
1051
|
const linearDepth = computeNDCDepth( {
|
|
808
1052
|
worldPos: hitPoint,
|
|
@@ -847,14 +1091,14 @@ export function buildShadeKernel( params ) {
|
|
|
847
1091
|
// ─── DDFA aux decision at a transmissive / alpha / SSS interaction. Runs for BOTH the continuing and
|
|
848
1092
|
// the BLEND fall-through paths, so it sits before If(continueRay). If/ElseIf chaining so BLEND wins
|
|
849
1093
|
// over the transmission branch (fixes the BLEND-glass per-frame flicker + the BLEND+transmission case). ───
|
|
850
|
-
If(
|
|
1094
|
+
if ( aux ) If( aux.and( flags.bitAnd( uint( RAY_FLAG.AUX_LOCKED ) ).equal( uint( 0 ) ) ), () => {
|
|
851
1095
|
|
|
852
1096
|
const primaryDepth = gbDecodeNormalDepth( readGBuffer( gBufferRW, pixelIndex ) ).w;
|
|
853
1097
|
// N here is the mapped normal — faceforward it (a ray exiting glass sees a back-facing N).
|
|
854
1098
|
const Nff = select( dot( N, direction.negate() ).lessThan( 0.0 ), N.negate(), N ).toVar();
|
|
855
1099
|
const auxAlbedo = clamp( albedo.mul( featPrefix ), vec3( 0.0 ), vec3( 1.0 ) ).toVar();
|
|
856
1100
|
|
|
857
|
-
If( material.alphaMode.equal( int( 2 ) ), () => {
|
|
1101
|
+
let auxCases = If( material.alphaMode.equal( int( 2 ) ), () => {
|
|
858
1102
|
|
|
859
1103
|
// BLEND: deterministic own-surface commit on BOTH stochastic skip + shade frames (no flicker).
|
|
860
1104
|
writeGBuffer( gBufferRW, pixelIndex, Nff, primaryDepth, auxAlbedo );
|
|
@@ -864,13 +1108,15 @@ export function buildShadeKernel( params ) {
|
|
|
864
1108
|
|
|
865
1109
|
// MASK cutout passthrough: keep deferring, tint unchanged (backdrop through the hole commits later).
|
|
866
1110
|
|
|
867
|
-
} )
|
|
1111
|
+
} );
|
|
1112
|
+
if ( layers.subsurface ) auxCases = auxCases.ElseIf( interaction.isSubsurface, () => {
|
|
868
1113
|
|
|
869
1114
|
// SSS boundary: force-commit the skin surface as diffuse.
|
|
870
1115
|
writeGBuffer( gBufferRW, pixelIndex, Nff, primaryDepth, auxAlbedo );
|
|
871
1116
|
flags.assign( flags.bitOr( uint( RAY_FLAG.AUX_LOCKED ) ) );
|
|
872
1117
|
|
|
873
|
-
} )
|
|
1118
|
+
} );
|
|
1119
|
+
auxCases.ElseIf( interaction.isTransmissive, () => {
|
|
874
1120
|
|
|
875
1121
|
If( auxCommit.or( material.dispersion.greaterThan( 0.0 ) ), () => {
|
|
876
1122
|
|
|
@@ -941,7 +1187,7 @@ export function buildShadeKernel( params ) {
|
|
|
941
1187
|
} );
|
|
942
1188
|
|
|
943
1189
|
// subsurface boundary: push the scattering medium on enter, pop on exit; free bounce
|
|
944
|
-
If( interaction.isSubsurface.and( interaction.didReflect.not() ), () => {
|
|
1190
|
+
if ( layers.subsurface ) If( interaction.isSubsurface.and( interaction.didReflect.not() ), () => {
|
|
945
1191
|
|
|
946
1192
|
If( interaction.entering, () => {
|
|
947
1193
|
|
|
@@ -994,6 +1240,15 @@ export function buildShadeKernel( params ) {
|
|
|
994
1240
|
|
|
995
1241
|
throughput.mulAssign( interaction.throughput );
|
|
996
1242
|
|
|
1243
|
+
// Importance, unlike radiance, is not rescaled by refraction.
|
|
1244
|
+
if ( bdpt ) If( isLight.and( interaction.isTransmissive.or( interaction.isSubsurface ) ).and( interaction.didReflect.not() ), () => {
|
|
1245
|
+
|
|
1246
|
+
const n1 = select( interaction.entering, currentMediumIOR, material.ior );
|
|
1247
|
+
const n2 = select( interaction.entering, material.ior, previousMediumIOR );
|
|
1248
|
+
throughput.mulAssign( n2.mul( n2 ).div( max( n1.mul( n1 ), EPSILON ) ) );
|
|
1249
|
+
|
|
1250
|
+
} );
|
|
1251
|
+
|
|
997
1252
|
// Off the side of the facet the new ray leaves on: reflection stays, transmission and alpha skip cross.
|
|
998
1253
|
const Ng = unpackHitFacet( readHitFacet( hitBufferRW, rayID ) ).faceN;
|
|
999
1254
|
const crossesFacet = dot( Ng, interaction.direction ).lessThan( 0.0 ).toVar();
|
|
@@ -1010,6 +1265,25 @@ export function buildShadeKernel( params ) {
|
|
|
1010
1265
|
flags.assign( flags.sub( flags.bitAnd( uint( RAY_FLAG.UNDER_SURFACE ) ) ).bitOr( uint( RAY_FLAG.REDIRECTED ) )
|
|
1011
1266
|
.bitOr( select( underSurface, uint( RAY_FLAG.UNDER_SURFACE ), uint( 0 ) ) ) );
|
|
1012
1267
|
|
|
1268
|
+
// Not connectible; an alpha skip is no vertex at all.
|
|
1269
|
+
if ( bdpt ) {
|
|
1270
|
+
|
|
1271
|
+
misOnHit( subpath, hitDist.add( readMisRayT( rayBufferRW, rayID ) ), abs( dot( exactFacetN, direction ) ) );
|
|
1272
|
+
misOnSpecular( subpath, abs( dot( exactFacetN, interaction.direction ) ) );
|
|
1273
|
+
If( flags.bitAnd( uint( RAY_FLAG.LIGHT_EMITTED ) ).notEqual( uint( 0 ) ), () => {
|
|
1274
|
+
|
|
1275
|
+
landLampPath( hitDist.add( readMisRayT( rayBufferRW, rayID ) ) );
|
|
1276
|
+
|
|
1277
|
+
} );
|
|
1278
|
+
flags.assign( flags.sub( flags.bitAnd( uint( RAY_FLAG.LIGHT_EMITTED ) ) ) );
|
|
1279
|
+
If( isLight.not().and( numAreaLights.greaterThan( int( 0 ) ) ), () => {
|
|
1280
|
+
|
|
1281
|
+
bidirectionalAreaHit( newOrigin, interaction.direction, null );
|
|
1282
|
+
|
|
1283
|
+
} );
|
|
1284
|
+
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1013
1287
|
} ).Else( () => {
|
|
1014
1288
|
|
|
1015
1289
|
// Alpha passthrough: charge the guard. N is faceforwarded for the aux commit (a ray
|
|
@@ -1037,12 +1311,12 @@ export function buildShadeKernel( params ) {
|
|
|
1037
1311
|
writeMediumStack( rayBufferRW, rayID, uint( mediumStackDepth ), uint( transTraversals ), mediumStack_ior_1, mediumStack_ior_2, mediumStack_ior_3, uint( pathWavelength.add( 0.5 ) ) );
|
|
1038
1312
|
// DDFA: persist the (possibly tinted) see-through throughput for the next bounce. MUST run after
|
|
1039
1313
|
// the writeMediumSigmaA above — both RMW slot 5 (sigmaA.xyz / featTP.w). Gated AUX_LOCKED-clear.
|
|
1040
|
-
If(
|
|
1314
|
+
if ( aux ) If( aux.and( flags.bitAnd( uint( RAY_FLAG.AUX_LOCKED ) ).equal( uint( 0 ) ) ), () => {
|
|
1041
1315
|
|
|
1042
1316
|
writeFeatureThroughput( rayBufferRW, rayID, featCarry );
|
|
1043
1317
|
|
|
1044
1318
|
} );
|
|
1045
|
-
|
|
1319
|
+
persistRng();
|
|
1046
1320
|
Return();
|
|
1047
1321
|
|
|
1048
1322
|
} );
|
|
@@ -1055,18 +1329,59 @@ export function buildShadeKernel( params ) {
|
|
|
1055
1329
|
// so any later env-escape is redirected light, not the direct backdrop). Single positive bitOr.
|
|
1056
1330
|
flags.assign( flags.bitOr( uint( RAY_FLAG.HAS_HIT_OPAQUE | RAY_FLAG.REDIRECTED ) ) );
|
|
1057
1331
|
|
|
1332
|
+
if ( bdpt ) {
|
|
1333
|
+
|
|
1334
|
+
const arrivalDist = hitDist.add( readMisRayT( rayBufferRW, rayID ) ).toVar();
|
|
1335
|
+
|
|
1336
|
+
// A light path's NEE density needs its first vertex.
|
|
1337
|
+
If( flags.bitAnd( uint( RAY_FLAG.LIGHT_EMITTED ) ).notEqual( uint( 0 ) ), () => {
|
|
1338
|
+
|
|
1339
|
+
const start = readLightOrigin( hitBufferRW, rayID );
|
|
1340
|
+
const neePdfA = float( 0.0 ).toVar();
|
|
1341
|
+
If( start.triangle.lessThan( int( 0 ) ), () => {
|
|
1342
|
+
|
|
1343
|
+
// A light at infinity has no distance to square. The sun's NEE density is in already; a lamp's
|
|
1344
|
+
// depends on this vertex's material, below.
|
|
1345
|
+
const source = int( - 1 ).sub( start.triangle );
|
|
1346
|
+
neePdfA.assign( select( source.lessThan( int( SOURCE.LAMPS ) ).or( sourceLampType( bdpt, source ).equal( int( LIGHT_TYPE_DIRECTIONAL ) ) ),
|
|
1347
|
+
float( 1.0 ).div( max( arrivalDist.mul( arrivalDist ), 1e-30 ) ), float( 1.0 ) ) );
|
|
1348
|
+
landLampPath( arrivalDist );
|
|
1349
|
+
|
|
1350
|
+
} ).Else( () => {
|
|
1351
|
+
|
|
1352
|
+
// NEE draws a triangle on every side it emits from, as the light path left it.
|
|
1353
|
+
if ( useEmissiveNEE ) If( enableEmissiveTriangleSampling.equal( int( 1 ) )
|
|
1354
|
+
.and( emissiveTriangleCount.greaterThan( int( 0 ) ) ), () => {
|
|
1355
|
+
|
|
1356
|
+
neePdfA.assign( emissiveNEEPdf( start.triangle, arrivalDist, direction.negate(), hitPoint, start.instanceLeaf )
|
|
1357
|
+
.mul( abs( start.cosLight ) ).div( arrivalDist.mul( arrivalDist ) ) );
|
|
1358
|
+
|
|
1359
|
+
} );
|
|
1360
|
+
|
|
1361
|
+
} );
|
|
1362
|
+
subpath.dVCM.mulAssign( mis( neePdfA ) );
|
|
1363
|
+
flags.assign( flags.sub( flags.bitAnd( uint( RAY_FLAG.LIGHT_EMITTED ) ) ) );
|
|
1364
|
+
|
|
1365
|
+
} );
|
|
1366
|
+
|
|
1367
|
+
misOnHit( subpath, arrivalDist, abs( dot( exactFacetN, direction ) ) );
|
|
1368
|
+
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1058
1371
|
const emissive = matSamples.emissive.toVar();
|
|
1059
|
-
// NEE samples only a single-sided emitter's lit side, so from behind (bounce rays) it is a dark occluder.
|
|
1060
|
-
|
|
1372
|
+
// NEE samples only a single-sided emitter's lit side, so from behind (bounce rays) it is a dark occluder. The side
|
|
1373
|
+
// is the facet's, as NEE's: the interpolated normal turns away near a coarse mesh's silhouette.
|
|
1374
|
+
If( cameraOnly( isBackdropView.not().and( length( emissive ).greaterThan( 0.0 ) ) ), () => {
|
|
1061
1375
|
|
|
1062
|
-
If( sideAccepts( triangleSide( triangleRow( triangleBuffer, int( hitTriIdx ), 4 ).z ),
|
|
1376
|
+
If( sideAccepts( triangleSide( triangleRow( triangleBuffer, int( hitTriIdx ), 4 ).z ),
|
|
1377
|
+
dot( direction, windingNormal( triangleBuffer, bvhBuffer, int( hitTriIdx ), hitInstance ) ) ).not(), () => {
|
|
1063
1378
|
|
|
1064
1379
|
emissive.assign( vec3( 0.0 ) );
|
|
1065
1380
|
|
|
1066
1381
|
} );
|
|
1067
1382
|
|
|
1068
1383
|
} );
|
|
1069
|
-
If( length( emissive ).greaterThan( 0.0 ), () => {
|
|
1384
|
+
If( cameraOnly( length( emissive ).greaterThan( 0.0 ) ), () => {
|
|
1070
1385
|
|
|
1071
1386
|
// Key on backdrop-view (not bounceIndex>0) so an emitter seen DIRECTLY through an alpha-cutout hole
|
|
1072
1387
|
// renders at full intensity (1.0) like a direct view, consistent with env-through-hole — instead of
|
|
@@ -1078,7 +1393,34 @@ export function buildShadeKernel( params ) {
|
|
|
1078
1393
|
// two estimators. Without it emissive geometry / area lights double-count (~2x bright + noisier).
|
|
1079
1394
|
// Primary hits keep weight 1.0 (the wavefront's bounce-0 stored pdf is the Generate init, not a NEE pdf).
|
|
1080
1395
|
const emissiveMISWeight = float( 1.0 ).toVar();
|
|
1081
|
-
if (
|
|
1396
|
+
if ( bdpt ) {
|
|
1397
|
+
|
|
1398
|
+
// Georgiev (43): NEE from the previous vertex, and every light subpath strategy.
|
|
1399
|
+
If( isBackdropView.not(), () => {
|
|
1400
|
+
|
|
1401
|
+
const misT = readMisRayT( rayBufferRW, rayID ).toVar();
|
|
1402
|
+
const misDist = hitDist.add( misT ).toVar();
|
|
1403
|
+
const tri = TriangleData.wrap( fetchTriangleData( int( hitTriIdx ), triangleBuffer, bvhBuffer, hitInstance ) );
|
|
1404
|
+
const cosToward = dot( direction.negate(), normalize( cross( tri.v1.sub( tri.v0 ), tri.v2.sub( tri.v0 ) ) ) ).toVar();
|
|
1405
|
+
const areaPdf = emitterAreaPdf( material.emissive.mul( material.emissiveIntensity ), emissiveTotalPower ).toVar();
|
|
1406
|
+
const emissionPdf = select( bdpt.lightPaths.greaterThan( uint( 0 ) ),
|
|
1407
|
+
areaPdf.mul( float( 1.0 ).sub( bdpt.sunPick ) ).mul( emitterSideProbability( triangleSide( triangleRow( triangleBuffer, int( hitTriIdx ), 4 ).z ), cosToward ) )
|
|
1408
|
+
.mul( abs( cosToward ) ).div( Math.PI ),
|
|
1409
|
+
float( 0.0 ) );
|
|
1410
|
+
const neePdfA = float( 0.0 ).toVar();
|
|
1411
|
+
if ( useEmissiveNEE ) If( enableEmissiveTriangleSampling.equal( int( 1 ) ).and( emissiveTriangleCount.greaterThan( int( 0 ) ) )
|
|
1412
|
+
.and( areaPdf.greaterThan( 0.0 ) ), () => {
|
|
1413
|
+
|
|
1414
|
+
neePdfA.assign( emissiveNEEPdf( int( hitTriIdx ), misDist, direction, origin.sub( direction.mul( misT ) ), hitInstance )
|
|
1415
|
+
.mul( abs( cosToward ) ).div( misDist.mul( misDist ) ) );
|
|
1416
|
+
|
|
1417
|
+
} );
|
|
1418
|
+
emissiveMISWeight.assign( strategyWeight( bdpt.strategyView, STRATEGY.HIT,
|
|
1419
|
+
misWeight( float( 0.0 ), mis( neePdfA ).mul( subpath.dVCM ).add( mis( emissionPdf ).mul( subpath.dVC ) ) ) ) );
|
|
1420
|
+
|
|
1421
|
+
} );
|
|
1422
|
+
|
|
1423
|
+
} else if ( useEmissiveNEE ) {
|
|
1082
1424
|
|
|
1083
1425
|
If( enableEmissiveTriangleSampling.equal( int( 1 ) )
|
|
1084
1426
|
.and( emissiveTriangleCount.greaterThan( int( 0 ) ) )
|
|
@@ -1133,6 +1475,13 @@ export function buildShadeKernel( params ) {
|
|
|
1133
1475
|
|
|
1134
1476
|
} );
|
|
1135
1477
|
|
|
1478
|
+
If( flags.bitAnd( uint( RAY_FLAG.EMISSION_ONLY ) ).notEqual( uint( 0 ) ), () => {
|
|
1479
|
+
|
|
1480
|
+
terminatePath();
|
|
1481
|
+
Return();
|
|
1482
|
+
|
|
1483
|
+
} );
|
|
1484
|
+
|
|
1136
1485
|
// BRDF sample (needed by both direct + indirect)
|
|
1137
1486
|
const V = direction.negate().toVar();
|
|
1138
1487
|
// face-forwarded geometric normal: horizon guard for NEE and the bounce continuation
|
|
@@ -1146,6 +1495,13 @@ export function buildShadeKernel( params ) {
|
|
|
1146
1495
|
hitPoint, offsetNormal: facetN, lift: terminatorLift, faceN: facetN, smoothN: NgeoFF, L,
|
|
1147
1496
|
cutoff: shadowTerminatorOffset,
|
|
1148
1497
|
} );
|
|
1498
|
+
// Light from behind a surface that passes light through diffusely: its rays leave from the other side.
|
|
1499
|
+
// Compiled in only with the lobe (see `diffuseTransmission`).
|
|
1500
|
+
const transmitting = diffuseTransmission && ! bdpt;
|
|
1501
|
+
const transmitsDiffusely = transmitting ? material.diffuseTransmission.greaterThan( 0.0 ) : tslBool( false );
|
|
1502
|
+
const throughSurface = L => ( transmitting ? transmitsDiffusely.and( dot( N, L ).lessThan( 0.0 ) ).and( dot( L, NgeoFF ).lessThan( 0.0 ) ) : tslBool( false ) );
|
|
1503
|
+
const backOrigin = transmitting ? offsetRayOrigin( hitPoint, facetN.negate() ) : null;
|
|
1504
|
+
const originFor = ( L, front ) => ( transmitting ? select( throughSurface( L ), backOrigin, front ) : front );
|
|
1149
1505
|
|
|
1150
1506
|
// Two-sided shading: opaque path only (transmissive/SSS already continued). Decide the flip on the
|
|
1151
1507
|
// GEOMETRIC normal — an inward-normal / double-sided mesh (GLB/PBRT) faces away as a whole — so a
|
|
@@ -1164,7 +1520,7 @@ export function buildShadeKernel( params ) {
|
|
|
1164
1520
|
// ─── DDFA opaque aux decision: commit at the first diffuse-enough surface, defer through smooth
|
|
1165
1521
|
// mirror/metal so the guide describes what the mirror reflects, not the mirror itself. N is already
|
|
1166
1522
|
// viewer-facing (two-sided flip above); depth read back + re-packed (idempotent snorm — no drift). ───
|
|
1167
|
-
If(
|
|
1523
|
+
if ( aux ) If( aux.and( flags.bitAnd( uint( RAY_FLAG.AUX_LOCKED ) ).equal( uint( 0 ) ) ), () => {
|
|
1168
1524
|
|
|
1169
1525
|
const primaryDepth = gbDecodeNormalDepth( readGBuffer( gBufferRW, pixelIndex ) ).w;
|
|
1170
1526
|
If( auxCommit, () => {
|
|
@@ -1211,8 +1567,28 @@ export function buildShadeKernel( params ) {
|
|
|
1211
1567
|
const brdfPdf = float( 0.0 ).toVar();
|
|
1212
1568
|
const brdfIsTransmission = tslBool( false ).toVar();
|
|
1213
1569
|
const brdfColorWeight = vec3( 1.0 ).toVar();
|
|
1570
|
+
const brdfIsDiffuseTransmission = tslBool( false ).toVar();
|
|
1571
|
+
|
|
1572
|
+
const sampleLobes = () => {
|
|
1573
|
+
|
|
1574
|
+
const bs = DirectionSample.wrap( generateSampledDirection(
|
|
1575
|
+
V, N, material, xi, lobeXi, rngState,
|
|
1576
|
+
_pixelCoord, resolution, frame, dimBase,
|
|
1577
|
+
mc,
|
|
1578
|
+
false, emptyWeights,
|
|
1579
|
+
false, emptyCache,
|
|
1580
|
+
) );
|
|
1581
|
+
brdfDir.assign( bs.direction );
|
|
1582
|
+
brdfValue.assign( bs.value );
|
|
1583
|
+
brdfPdf.assign( bs.pdf );
|
|
1584
|
+
brdfIsTransmission.assign( bs.isTransmission );
|
|
1585
|
+
brdfColorWeight.assign( bs.colorWeight );
|
|
1586
|
+
brdfIsDiffuseTransmission.assign( bs.isDiffuseTransmission );
|
|
1214
1587
|
|
|
1215
|
-
|
|
1588
|
+
};
|
|
1589
|
+
|
|
1590
|
+
if ( ! layers.clearcoat ) sampleLobes();
|
|
1591
|
+
else If( material.clearcoat.greaterThan( 0.0 ), () => {
|
|
1216
1592
|
|
|
1217
1593
|
const ccRay = Ray( { origin, direction } );
|
|
1218
1594
|
const ccHit = HitInfo( {
|
|
@@ -1227,25 +1603,11 @@ export function buildShadeKernel( params ) {
|
|
|
1227
1603
|
brdfDir.assign( ccResult.L );
|
|
1228
1604
|
brdfValue.assign( ccResult.brdf );
|
|
1229
1605
|
brdfPdf.assign( ccResult.pdf );
|
|
1606
|
+
brdfIsDiffuseTransmission.assign( ccResult.isDiffuseTransmission );
|
|
1230
1607
|
|
|
1231
|
-
} ).Else(
|
|
1232
|
-
|
|
1233
|
-
const bs = DirectionSample.wrap( generateSampledDirection(
|
|
1234
|
-
V, N, material, xi, lobeXi, rngState,
|
|
1235
|
-
_pixelCoord, resolution, frame, dimBase,
|
|
1236
|
-
mc,
|
|
1237
|
-
false, emptyWeights,
|
|
1238
|
-
false, emptyCache,
|
|
1239
|
-
) );
|
|
1240
|
-
brdfDir.assign( bs.direction );
|
|
1241
|
-
brdfValue.assign( bs.value );
|
|
1242
|
-
brdfPdf.assign( bs.pdf );
|
|
1243
|
-
brdfIsTransmission.assign( bs.isTransmission );
|
|
1244
|
-
brdfColorWeight.assign( bs.colorWeight );
|
|
1245
|
-
|
|
1246
|
-
} );
|
|
1608
|
+
} ).Else( sampleLobes );
|
|
1247
1609
|
|
|
1248
|
-
const
|
|
1610
|
+
const directLighting = () => DirectLightingDual.wrap( ( transmitting ? calculateDirectLightingThroughSurfaces : calculateDirectLightingUnified )(
|
|
1249
1611
|
hitPoint, N, NgeoFF, material, V,
|
|
1250
1612
|
brdfDir, brdfPdf, brdfValue,
|
|
1251
1613
|
bounceIndex, rngState,
|
|
@@ -1257,18 +1619,20 @@ export function buildShadeKernel( params ) {
|
|
|
1257
1619
|
bvhBuffer, triangleBuffer, materialBuffer,
|
|
1258
1620
|
envTexture, environmentIntensity, envMatrix,
|
|
1259
1621
|
envCDFTexture,
|
|
1260
|
-
envTotalSum,
|
|
1622
|
+
envTotalSum, envResolution,
|
|
1261
1623
|
enableEnvironmentLight,
|
|
1262
1624
|
tslBool( false ), // wantUnoccluded: false on real surfaces — dead-codes the unoccluded sum
|
|
1263
1625
|
terminatorLift, facetN, shadowTerminatorOffset,
|
|
1264
1626
|
hasSun, sunDirection, sunRadiance, sunParams,
|
|
1265
|
-
) ).shadowed
|
|
1627
|
+
) ).shadowed;
|
|
1628
|
+
// Bidirectional mode samples every light itself, below.
|
|
1629
|
+
const directLight = bdpt ? null : directLighting().toVar();
|
|
1266
1630
|
|
|
1267
1631
|
const giScale = select( bounceIndex.greaterThan( 0 ), globalIlluminationIntensity, float( 1.0 ) );
|
|
1268
1632
|
// Per-term firefly suppression (megakernel parity: PathTracerCore.js:1164) — wrap the direct-light add
|
|
1269
1633
|
// like every other contribution (env/emissive-hit/emissive-NEE). This replaces the cumulative catch-all
|
|
1270
1634
|
// that re-suppressed already-wrapped terms + prior-bounce radiance — suppress(a+b) ≠ suppress(a)+suppress(b) (gap #13).
|
|
1271
|
-
currentRadiance.assign( vec4(
|
|
1635
|
+
if ( ! bdpt ) currentRadiance.assign( vec4(
|
|
1272
1636
|
currentRadiance.xyz.add(
|
|
1273
1637
|
regularizePathContribution(
|
|
1274
1638
|
throughput.mul( directLight ).mul( giScale ),
|
|
@@ -1278,8 +1642,319 @@ export function buildShadeKernel( params ) {
|
|
|
1278
1642
|
currentRadiance.w
|
|
1279
1643
|
) );
|
|
1280
1644
|
|
|
1645
|
+
// Georgiev (44)-(45). Glass blocks the shadow ray: light through it arrives on the light subpaths.
|
|
1646
|
+
const bidirectionalEmissiveNEE = () => {
|
|
1647
|
+
|
|
1648
|
+
const position = vec3( 0.0 ).toVar();
|
|
1649
|
+
const lightDir = vec3( 0.0 ).toVar();
|
|
1650
|
+
const emission = vec3( 0.0 ).toVar();
|
|
1651
|
+
const lightPdf = float( 0.0 ).toVar();
|
|
1652
|
+
const valid = tslBool( false ).toVar();
|
|
1653
|
+
const lightTriangle = int( 0 ).toVar();
|
|
1654
|
+
const windingN = vec3( 0.0 ).toVar();
|
|
1655
|
+
const take = ( sample ) => {
|
|
1656
|
+
|
|
1657
|
+
const e = EmissiveSampleIndexed.wrap( sample );
|
|
1658
|
+
position.assign( e.position );
|
|
1659
|
+
lightDir.assign( e.direction );
|
|
1660
|
+
emission.assign( e.emission );
|
|
1661
|
+
lightPdf.assign( e.pdf );
|
|
1662
|
+
valid.assign( e.valid );
|
|
1663
|
+
lightTriangle.assign( e.triangleIndex );
|
|
1664
|
+
windingN.assign( e.faceNormal );
|
|
1665
|
+
|
|
1666
|
+
};
|
|
1667
|
+
|
|
1668
|
+
If( lightBVHNodeCount.greaterThan( int( 0 ) ), () => {
|
|
1669
|
+
|
|
1670
|
+
take( sampleLightBVHTriangleIndexed(
|
|
1671
|
+
hitPoint, N, rngState, _pixelCoord, resolution, frame, dimBase,
|
|
1672
|
+
lightBuffer, lightBuffer, emissiveVec4Offset, triangleBuffer, bvhBuffer, tslBool( false ),
|
|
1673
|
+
) );
|
|
1674
|
+
|
|
1675
|
+
} ).Else( () => {
|
|
1676
|
+
|
|
1677
|
+
take( sampleEmissiveTriangleIndexed(
|
|
1678
|
+
hitPoint, N, rngState, _pixelCoord, resolution, frame, dimBase,
|
|
1679
|
+
lightBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower, triangleBuffer, bvhBuffer, tslBool( false ),
|
|
1680
|
+
) );
|
|
1681
|
+
|
|
1682
|
+
} );
|
|
1683
|
+
|
|
1684
|
+
const NoL = max( float( 0.0 ), dot( N, lightDir ) ).toVar();
|
|
1685
|
+
const side = triangleSide( triangleRow( triangleBuffer, lightTriangle, 4 ).z ).toVar();
|
|
1686
|
+
If( valid.and( lightPdf.greaterThan( 0.0 ) ).and( NoL.greaterThan( 0.0 ) ).and( dot( lightDir, NgeoFF ).greaterThan( 0.0 ) )
|
|
1687
|
+
.and( sideAccepts( side, dot( lightDir, windingN ) ) ), () => {
|
|
1688
|
+
|
|
1689
|
+
const rayOrigin = lightShadowOrigin( lightDir ).toVar();
|
|
1690
|
+
const toSample = position.sub( rayOrigin ).toVar();
|
|
1691
|
+
const shadowDist = length( toSample ).toVar();
|
|
1692
|
+
const visibility = traceShadowRayRefractiveOpaque(
|
|
1693
|
+
rayOrigin, toSample.div( shadowDist ), shadowDist.mul( SHADOW_END ),
|
|
1694
|
+
traverseBVHShadow, bvhBuffer, triangleBuffer, materialBuffer,
|
|
1695
|
+
).toVar();
|
|
1696
|
+
|
|
1697
|
+
If( visibility.greaterThan( 0.0 ), () => {
|
|
1698
|
+
|
|
1699
|
+
const brdfVal = evaluateMaterialResponse( V, lightDir, N, material );
|
|
1700
|
+
const forward = calculateMaterialPDF( V, lightDir, N, material );
|
|
1701
|
+
const reverse = calculateMaterialPDF( lightDir, V, N, material );
|
|
1702
|
+
// Emission density over NEE's, both seen from here; the light's cosine cancels.
|
|
1703
|
+
const emissionOverDirect = select( bdpt.lightPaths.greaterThan( uint( 0 ) ),
|
|
1704
|
+
emitterAreaPdf( emission, emissiveTotalPower, float( 1.0 ).sub( bdpt.sunPick ) )
|
|
1705
|
+
.mul( emitterSideProbability( side, dot( lightDir.negate(), windingN ) ) )
|
|
1706
|
+
.mul( abs( dot( lightDir, exactFacetN ) ) ).div( lightPdf.mul( Math.PI ) ),
|
|
1707
|
+
float( 0.0 ) );
|
|
1708
|
+
const wLight = mis( forward.div( lightPdf ) );
|
|
1709
|
+
const wCamera = misPartial( emissionOverDirect, subpath, reverse );
|
|
1710
|
+
|
|
1711
|
+
currentRadiance.assign( vec4(
|
|
1712
|
+
currentRadiance.xyz.add(
|
|
1713
|
+
regularizePathContribution(
|
|
1714
|
+
emission.mul( brdfVal ).mul( NoL ).div( lightPdf ).mul( visibility ).mul( emissiveBoost )
|
|
1715
|
+
.mul( strategyWeight( bdpt.strategyView, STRATEGY.NEE, misWeight( wLight, wCamera ) ) ).mul( throughput ).mul( giScale ),
|
|
1716
|
+
float( bounceIndex ), fireflyThreshold, int( accumFrame ),
|
|
1717
|
+
),
|
|
1718
|
+
),
|
|
1719
|
+
currentRadiance.w,
|
|
1720
|
+
) );
|
|
1721
|
+
|
|
1722
|
+
} );
|
|
1723
|
+
|
|
1724
|
+
} );
|
|
1725
|
+
|
|
1726
|
+
};
|
|
1727
|
+
|
|
1728
|
+
// Georgiev (44)-(45) for the sun; as for emitters, glass blocks the shadow ray.
|
|
1729
|
+
const bidirectionalSunNEE = () => {
|
|
1730
|
+
|
|
1731
|
+
const lightDir = sampleSunDisc( sunDirection, sunParams, getRandomSample2D( _pixelCoord, int( 0 ), dimBase.add( int( 9 ) ), rngState, resolution, frame ) ).toVar();
|
|
1732
|
+
const radiance = sunRadianceToward( lightDir, sunDirection, sunRadiance, sunParams ).mul( environmentIntensity ).toVar();
|
|
1733
|
+
const NoL = max( float( 0.0 ), dot( N, lightDir ) ).toVar();
|
|
1734
|
+
If( radiance.x.add( radiance.y ).add( radiance.z ).greaterThan( 0.0 ).and( NoL.greaterThan( 0.0 ) ).and( dot( lightDir, NgeoFF ).greaterThan( 0.0 ) ), () => {
|
|
1735
|
+
|
|
1736
|
+
const visibility = traceShadowRayRefractiveOpaque(
|
|
1737
|
+
lightShadowOrigin( lightDir ), lightDir, float( 1e20 ),
|
|
1738
|
+
traverseBVHShadow, bvhBuffer, triangleBuffer, materialBuffer,
|
|
1739
|
+
).toVar();
|
|
1740
|
+
|
|
1741
|
+
If( visibility.greaterThan( 0.0 ), () => {
|
|
1742
|
+
|
|
1743
|
+
const brdfVal = evaluateMaterialResponse( V, lightDir, N, material );
|
|
1744
|
+
const forward = calculateMaterialPDF( V, lightDir, N, material );
|
|
1745
|
+
const reverse = calculateMaterialPDF( lightDir, V, N, material );
|
|
1746
|
+
const emissionOverDirect = select( bdpt.lightPaths.greaterThan( uint( 0 ) ),
|
|
1747
|
+
sunEmissionPdf( bdpt, sunParams, guidedDiscPdf( bdpt, bdpt.guideTexture, lightDir, hitPoint ) ).mul( sunParams.y ).mul( abs( dot( lightDir, exactFacetN ) ) ), float( 0.0 ) );
|
|
1748
|
+
const weight = misWeight( mis( forward.mul( sunParams.y ) ), misPartial( emissionOverDirect, subpath, reverse ) );
|
|
1749
|
+
|
|
1750
|
+
currentRadiance.assign( vec4(
|
|
1751
|
+
currentRadiance.xyz.add(
|
|
1752
|
+
regularizePathContribution(
|
|
1753
|
+
radiance.mul( brdfVal ).mul( NoL ).mul( sunParams.y ).mul( visibility )
|
|
1754
|
+
.mul( strategyWeight( bdpt.strategyView, STRATEGY.NEE, weight ) ).mul( throughput ).mul( giScale ),
|
|
1755
|
+
float( bounceIndex ), fireflyThreshold, int( accumFrame ),
|
|
1756
|
+
),
|
|
1757
|
+
),
|
|
1758
|
+
currentRadiance.w,
|
|
1759
|
+
) );
|
|
1760
|
+
|
|
1761
|
+
} );
|
|
1762
|
+
|
|
1763
|
+
} );
|
|
1764
|
+
|
|
1765
|
+
};
|
|
1766
|
+
|
|
1767
|
+
if ( bdpt ) If( isLight.not().and( hasSun.greaterThan( int( 0 ) ) ).and( enableEnvironmentLight ), () => {
|
|
1768
|
+
|
|
1769
|
+
bidirectionalSunNEE();
|
|
1770
|
+
|
|
1771
|
+
} );
|
|
1772
|
+
|
|
1773
|
+
// Georgiev (44)-(45) for the environment, drawn exactly (environmentPdfExact); glass blocks it as it does the sun's.
|
|
1774
|
+
const bidirectionalEnvironmentNEE = () => {
|
|
1775
|
+
|
|
1776
|
+
const drawn = sampleEnvironmentExact( envCDFTexture, envMatrix, envResolution,
|
|
1777
|
+
getRandomSample2D( _pixelCoord, int( 0 ), dimBase.add( int( 1 ) ), rngState, resolution, frame ) );
|
|
1778
|
+
const lightDir = drawn.direction.toVar();
|
|
1779
|
+
const lightPdf = drawn.pdf.toVar();
|
|
1780
|
+
const radiance = sampleEnvironment( {
|
|
1781
|
+
tex: envTexture, samp: sampler( envTexture ), direction: lightDir, environmentMatrix: envMatrix, environmentIntensity, enableEnvironmentLight: float( 1.0 ),
|
|
1782
|
+
} ).xyz.toVar();
|
|
1783
|
+
const NoL = max( float( 0.0 ), dot( N, lightDir ) ).toVar();
|
|
1784
|
+
If( lightPdf.greaterThan( 0.0 ).and( radiance.x.add( radiance.y ).add( radiance.z ).greaterThan( 0.0 ) ).and( NoL.greaterThan( 0.0 ) )
|
|
1785
|
+
.and( dot( lightDir, NgeoFF ).greaterThan( 0.0 ) ), () => {
|
|
1786
|
+
|
|
1787
|
+
const visibility = traceShadowRayRefractiveOpaque(
|
|
1788
|
+
lightShadowOrigin( lightDir ), lightDir, float( 1e20 ), traverseBVHShadow, bvhBuffer, triangleBuffer, materialBuffer,
|
|
1789
|
+
).toVar();
|
|
1790
|
+
|
|
1791
|
+
If( visibility.greaterThan( 0.0 ), () => {
|
|
1792
|
+
|
|
1793
|
+
const brdfVal = evaluateMaterialResponse( V, lightDir, N, material );
|
|
1794
|
+
const forward = calculateMaterialPDF( V, lightDir, N, material );
|
|
1795
|
+
const reverse = calculateMaterialPDF( lightDir, V, N, material );
|
|
1796
|
+
const lead = select( bdpt.lightPaths.greaterThan( uint( 0 ) ),
|
|
1797
|
+
bdpt.envPick.mul( guidedDiscPdf( bdpt, bdpt.guideTexture, lightDir, hitPoint ) ).mul( abs( dot( lightDir, exactFacetN ) ) ), float( 0.0 ) );
|
|
1798
|
+
const weight = misWeight( mis( forward.div( lightPdf ) ), misPartial( lead, subpath, reverse ) );
|
|
1799
|
+
|
|
1800
|
+
currentRadiance.assign( vec4(
|
|
1801
|
+
currentRadiance.xyz.add(
|
|
1802
|
+
regularizePathContribution(
|
|
1803
|
+
radiance.mul( brdfVal ).mul( NoL ).div( lightPdf ).mul( visibility )
|
|
1804
|
+
.mul( strategyWeight( bdpt.strategyView, STRATEGY.NEE, weight ) ).mul( throughput ).mul( giScale ),
|
|
1805
|
+
float( bounceIndex ), fireflyThreshold, int( accumFrame ),
|
|
1806
|
+
),
|
|
1807
|
+
),
|
|
1808
|
+
currentRadiance.w,
|
|
1809
|
+
) );
|
|
1810
|
+
|
|
1811
|
+
} );
|
|
1812
|
+
|
|
1813
|
+
} );
|
|
1814
|
+
|
|
1815
|
+
};
|
|
1816
|
+
|
|
1817
|
+
if ( bdpt ) If( isLight.not().and( enableEnvironmentLight ).and( bdpt.envTable.greaterThan( uint( 0 ) ) ), () => {
|
|
1818
|
+
|
|
1819
|
+
bidirectionalEnvironmentNEE();
|
|
1820
|
+
|
|
1821
|
+
} );
|
|
1822
|
+
|
|
1823
|
+
// The lamps' reservoir total here, for the BSDF-hit partner at the continuation.
|
|
1824
|
+
const lampTotal = bdpt ? float( 0.0 ).toVar() : null;
|
|
1825
|
+
|
|
1826
|
+
// Georgiev (44)-(45) for a lamp, picked as the path tracer picks it. A point, a spot or a directional light no
|
|
1827
|
+
// camera path can hit; a rect light's partner is the continuation's hit (bidirectionalAreaHit).
|
|
1828
|
+
const bidirectionalLampNEE = () => {
|
|
1829
|
+
|
|
1830
|
+
const lightRandom = getRandomSample2D( _pixelCoord, int( 0 ), dimBase.add( int( 2 ) ), rngState, resolution, frame ).toVar();
|
|
1831
|
+
const pick = LampPick.wrap( pickLamp( lamps, hitPoint, N, material, lightRandom.x,
|
|
1832
|
+
( dim ) => getRandomSample1D( _pixelCoord, int( 0 ), dim, rngState, resolution, frame ) ) ).toVar();
|
|
1833
|
+
lampTotal.assign( pick.total );
|
|
1834
|
+
|
|
1835
|
+
const valid = tslBool( false ).toVar();
|
|
1836
|
+
const lightDir = vec3( 0.0, 1.0, 0.0 ).toVar();
|
|
1837
|
+
const emission = vec3( 0.0 ).toVar();
|
|
1838
|
+
const dist = float( 0.0 ).toVar();
|
|
1839
|
+
const lightPdf = float( 0.0 ).toVar();
|
|
1840
|
+
// Emission density over NEE's, but for the cosine here.
|
|
1841
|
+
const emissionOverDirect = float( 0.0 ).toVar();
|
|
1842
|
+
const take = ( sample ) => {
|
|
1843
|
+
|
|
1844
|
+
const ls = LightSample.wrap( sample );
|
|
1845
|
+
valid.assign( ls.valid );
|
|
1846
|
+
lightDir.assign( ls.direction );
|
|
1847
|
+
emission.assign( ls.emission );
|
|
1848
|
+
dist.assign( ls.distance );
|
|
1849
|
+
lightPdf.assign( ls.pdf );
|
|
1850
|
+
|
|
1851
|
+
};
|
|
1852
|
+
|
|
1853
|
+
const pE = sourcePick( bdpt, lampSource( bdpt, pick.kind, pick.index ) ).toVar();
|
|
1854
|
+
|
|
1855
|
+
If( pick.kind.equal( int( LIGHT_TYPE_DIRECTIONAL ) ), () => {
|
|
1856
|
+
|
|
1857
|
+
const light = DirectionalLight.wrap( getDirectionalLight( directionalLightsBuffer, pick.index ) );
|
|
1858
|
+
take( sampleDirectionalLight( light, hitPoint, pick.pdf, lightRandom.y,
|
|
1859
|
+
getRandomSample1D( _pixelCoord, int( 0 ), dimBase.add( int( 14 ) ), rngState, resolution, frame ) ) );
|
|
1860
|
+
emissionOverDirect.assign( pE.mul( guidedDiscPdf( bdpt, bdpt.guideTexture, lightDir, hitPoint ) ).div( max( pick.pdf, 1e-30 ) ) );
|
|
1861
|
+
|
|
1862
|
+
} ).ElseIf( pick.kind.equal( int( LIGHT_TYPE_AREA ) ), () => {
|
|
1863
|
+
|
|
1864
|
+
const light = AreaLight.wrap( getAreaLight( areaLightsBuffer, pick.index ) );
|
|
1865
|
+
take( sampleRectAreaLight( light, hitPoint,
|
|
1866
|
+
vec2( lightRandom.y, getRandomSample1D( _pixelCoord, int( 0 ), dimBase.add( int( 15 ) ), rngState, resolution, frame ) ), pick.pdf ) );
|
|
1867
|
+
emissionOverDirect.assign( pE.div( max( light.area.mul( Math.PI ).mul( lightPdf ), 1e-30 ) ) );
|
|
1868
|
+
|
|
1869
|
+
} ).ElseIf( pick.kind.equal( int( LIGHT_TYPE_POINT ) ), () => {
|
|
1870
|
+
|
|
1871
|
+
take( samplePointLightWithAttenuation( PointLight.wrap( getPointLight( pointLightsBuffer, pick.index ) ), hitPoint, pick.pdf ) );
|
|
1872
|
+
emissionOverDirect.assign( pE.div( max( float( 4 * Math.PI ).mul( pick.pdf ).mul( dist ).mul( dist ), 1e-30 ) ) );
|
|
1873
|
+
|
|
1874
|
+
} ).Else( () => {
|
|
1875
|
+
|
|
1876
|
+
const light = SpotLight.wrap( getSpotLight( spotLightsBuffer, pick.index ) );
|
|
1877
|
+
take( sampleSpotLightWithRadius( light, hitPoint, pick.pdf ) );
|
|
1878
|
+
emissionOverDirect.assign( pE.div( max( spotConeSolidAngle( light ).mul( pick.pdf ).mul( dist ).mul( dist ), 1e-30 ) ) );
|
|
1879
|
+
|
|
1880
|
+
} );
|
|
1881
|
+
|
|
1882
|
+
const NoL = max( float( 0.0 ), dot( N, lightDir ) ).toVar();
|
|
1883
|
+
If( valid.and( lightPdf.greaterThan( 0.0 ) ).and( NoL.greaterThan( 0.0 ) ).and( dot( lightDir, NgeoFF ).greaterThan( 0.0 ) ), () => {
|
|
1884
|
+
|
|
1885
|
+
const atInfinity = pick.kind.equal( int( LIGHT_TYPE_DIRECTIONAL ) );
|
|
1886
|
+
const shadowOrigin = lightShadowOrigin( lightDir ).toVar();
|
|
1887
|
+
const toSample = hitPoint.add( lightDir.mul( dist ) ).sub( shadowOrigin ).toVar();
|
|
1888
|
+
const shadowDist = length( toSample ).toVar();
|
|
1889
|
+
const visibility = traceShadowRayRefractiveOpaque(
|
|
1890
|
+
shadowOrigin, select( atInfinity, lightDir, toSample.div( shadowDist ) ), select( atInfinity, float( 1e20 ), shadowDist.mul( SHADOW_END ) ),
|
|
1891
|
+
traverseBVHShadow, bvhBuffer, triangleBuffer, materialBuffer,
|
|
1892
|
+
).toVar();
|
|
1893
|
+
|
|
1894
|
+
If( visibility.greaterThan( 0.0 ), () => {
|
|
1895
|
+
|
|
1896
|
+
const brdfVal = evaluateMaterialResponse( V, lightDir, N, material );
|
|
1897
|
+
const forward = calculateMaterialPDF( V, lightDir, N, material );
|
|
1898
|
+
const reverse = calculateMaterialPDF( lightDir, V, N, material );
|
|
1899
|
+
const lead = select( bdpt.lightPaths.greaterThan( uint( 0 ) ), emissionOverDirect.mul( abs( dot( lightDir, exactFacetN ) ) ), float( 0.0 ) );
|
|
1900
|
+
const wLight = select( pick.kind.equal( int( LIGHT_TYPE_AREA ) ), mis( forward.div( lightPdf ) ), float( 0.0 ) );
|
|
1901
|
+
|
|
1902
|
+
currentRadiance.assign( vec4(
|
|
1903
|
+
currentRadiance.xyz.add(
|
|
1904
|
+
regularizePathContribution(
|
|
1905
|
+
emission.mul( brdfVal ).mul( NoL ).div( lightPdf ).mul( visibility )
|
|
1906
|
+
.mul( strategyWeight( bdpt.strategyView, STRATEGY.NEE, misWeight( wLight, misPartial( lead, subpath, reverse ) ) ) ).mul( throughput ).mul( giScale ),
|
|
1907
|
+
float( bounceIndex ), fireflyThreshold, int( accumFrame ),
|
|
1908
|
+
),
|
|
1909
|
+
),
|
|
1910
|
+
currentRadiance.w,
|
|
1911
|
+
) );
|
|
1912
|
+
|
|
1913
|
+
} );
|
|
1914
|
+
|
|
1915
|
+
} );
|
|
1916
|
+
|
|
1917
|
+
};
|
|
1918
|
+
|
|
1919
|
+
if ( bdpt ) If( isLight.not().and( lampCount( lamps ).greaterThan( int( 0 ) ) ), () => {
|
|
1920
|
+
|
|
1921
|
+
bidirectionalLampNEE();
|
|
1922
|
+
|
|
1923
|
+
} );
|
|
1924
|
+
|
|
1925
|
+
// NEE's density for a lamp's light path depends on this vertex's material.
|
|
1926
|
+
if ( bdpt ) If( isLight.and( lampLanding.source.greaterThanEqual( int( SOURCE.LAMPS ) ) ), () => {
|
|
1927
|
+
|
|
1928
|
+
const type = sourceLampType( bdpt, lampLanding.source ).toVar();
|
|
1929
|
+
const index = sourceLampIndex( bdpt, lampLanding.source, type ).toVar();
|
|
1930
|
+
const d = lampLanding.dist;
|
|
1931
|
+
const direct = lampPickPdf( lamps, type, index, hitPoint, N, material ).toVar();
|
|
1932
|
+
If( type.equal( int( LIGHT_TYPE_DIRECTIONAL ) ), () => {
|
|
1933
|
+
|
|
1934
|
+
const light = DirectionalLight.wrap( getDirectionalLight( directionalLightsBuffer, index ) );
|
|
1935
|
+
direct.divAssign( select( light.angle.greaterThan( 0.0 ), directionalConeSolidAngle( light ), float( 1.0 ) ) );
|
|
1936
|
+
|
|
1937
|
+
} ).ElseIf( type.equal( int( LIGHT_TYPE_AREA ) ), () => {
|
|
1938
|
+
|
|
1939
|
+
const light = AreaLight.wrap( getAreaLight( areaLightsBuffer, index ) );
|
|
1940
|
+
const cosLight = dot( direction, light.normal ).toVar();
|
|
1941
|
+
direct.mulAssign( areaLightDirectPdfW( light, hitPoint, d, cosLight ).mul( cosLight ).div( max( d.mul( d ), 1e-30 ) ) );
|
|
1942
|
+
|
|
1943
|
+
} );
|
|
1944
|
+
subpath.dVCM.mulAssign( mis( direct ) );
|
|
1945
|
+
|
|
1946
|
+
} );
|
|
1947
|
+
|
|
1281
1948
|
// emissive triangle NEE: light-BVH fast path when available, flat-CDF fallback otherwise
|
|
1282
|
-
if ( useEmissiveNEE ) {
|
|
1949
|
+
if ( useEmissiveNEE && bdpt ) {
|
|
1950
|
+
|
|
1951
|
+
If( isLight.not().and( enableEmissiveTriangleSampling.equal( int( 1 ) ) ).and( emissiveTriangleCount.greaterThan( int( 0 ) ) ), () => {
|
|
1952
|
+
|
|
1953
|
+
bidirectionalEmissiveNEE();
|
|
1954
|
+
|
|
1955
|
+
} );
|
|
1956
|
+
|
|
1957
|
+
} else if ( useEmissiveNEE ) {
|
|
1283
1958
|
|
|
1284
1959
|
If(
|
|
1285
1960
|
enableEmissiveTriangleSampling.equal( int( 1 ) )
|
|
@@ -1306,18 +1981,22 @@ export function buildShadeKernel( params ) {
|
|
|
1306
1981
|
lightBuffer,
|
|
1307
1982
|
emissiveVec4Offset,
|
|
1308
1983
|
triangleBuffer, bvhBuffer,
|
|
1984
|
+
transmitsDiffusely,
|
|
1309
1985
|
) );
|
|
1310
1986
|
|
|
1311
1987
|
// No rough-diffuse secondary-bounce skip here: dropping NEE while the emissive-hit
|
|
1312
1988
|
// MIS still down-weights BSDF hits deletes emitter energy from deep GI.
|
|
1313
1989
|
If( emissiveSample.valid.and( emissiveSample.pdf.greaterThan( 0.0 ) ), () => {
|
|
1314
1990
|
|
|
1315
|
-
const
|
|
1991
|
+
const back = throughSurface( emissiveSample.direction ).toVar();
|
|
1992
|
+
const NoL = transmitting ? abs( dot( N, emissiveSample.direction ) ) : max( float( 0.0 ), dot( N, emissiveSample.direction ) );
|
|
1993
|
+
const inFront = NoL.greaterThan( 0.0 ).and( dot( N, emissiveSample.direction ).greaterThan( 0.0 ) )
|
|
1994
|
+
.and( dot( emissiveSample.direction, NgeoFF ).greaterThan( 0.0 ) );
|
|
1316
1995
|
|
|
1317
|
-
If(
|
|
1996
|
+
If( transmitting ? back.or( inFront ) : inFront, () => {
|
|
1318
1997
|
|
|
1319
1998
|
// Aimed at the sampled point and stopped a relative hair short, as for area lights.
|
|
1320
|
-
const rayOrigin = lightShadowOrigin( emissiveSample.direction ).toVar();
|
|
1999
|
+
const rayOrigin = originFor( emissiveSample.direction, lightShadowOrigin( emissiveSample.direction ) ).toVar();
|
|
1321
2000
|
const toSample = emissiveSample.position.sub( rayOrigin ).toVar();
|
|
1322
2001
|
const shadowDist = length( toSample ).toVar();
|
|
1323
2002
|
const visibility = traceShadowRayWrapped(
|
|
@@ -1365,7 +2044,7 @@ export function buildShadeKernel( params ) {
|
|
|
1365
2044
|
lightBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
1366
2045
|
triangleBuffer, bvhBuffer,
|
|
1367
2046
|
traceShadowRayWrapped,
|
|
1368
|
-
Fn( ( [ p, , L ] ) => lightShadowOrigin( L ).sub( p ) ),
|
|
2047
|
+
Fn( ( [ p, , L ] ) => originFor( L, lightShadowOrigin( L ) ).sub( p ) ),
|
|
1369
2048
|
);
|
|
1370
2049
|
|
|
1371
2050
|
currentRadiance.assign( vec4(
|
|
@@ -1389,10 +2068,46 @@ export function buildShadeKernel( params ) {
|
|
|
1389
2068
|
// per-term (env / emissive-hit / direct-light / emissive-NEE), matching the megakernel which never
|
|
1390
2069
|
// re-suppresses the running radiance.
|
|
1391
2070
|
|
|
2071
|
+
if ( bdpt ) {
|
|
2072
|
+
|
|
2073
|
+
// Light from below the shading normal: a camera path neither samples it nor lets NEE take it.
|
|
2074
|
+
If( isLight.and( dot( V, N ).lessThanEqual( 0.0 ) ), () => {
|
|
2075
|
+
|
|
2076
|
+
terminatePath();
|
|
2077
|
+
Return();
|
|
2078
|
+
|
|
2079
|
+
} );
|
|
2080
|
+
|
|
2081
|
+
If( isLight, () => {
|
|
2082
|
+
|
|
2083
|
+
// The path's vertex count rides in its first slot.
|
|
2084
|
+
const first = rayID.mul( bdpt.slotsPerPath ).toVar();
|
|
2085
|
+
writeVertexRecord( hitBufferRW, cachedVertex( first.add( uint( cameraDepth ) ) ), {
|
|
2086
|
+
position: hitPoint, tag: uint( 0 ), throughput: throughput.mul( readLightOrigin( hitBufferRW, rayID ).scale ),
|
|
2087
|
+
V, N, facetN: exactFacetN, materialIndex: hitMatIdx, uv: samplingUV, dVCM: subpath.dVCM, dVC: subpath.dVC,
|
|
2088
|
+
// 1: a camera on this side would cull the face, so light tracing must not show it.
|
|
2089
|
+
extra: select( sideAccepts( triangleSide( triangleRow( triangleBuffer, int( hitTriIdx ), 4 ).z ),
|
|
2090
|
+
dot( direction, windingNormal( triangleBuffer, bvhBuffer, int( hitTriIdx ), hitInstance ) ) ), uint( 0 ), uint( 1 ) ),
|
|
2091
|
+
} );
|
|
2092
|
+
writeLightPathLength( hitBufferRW, cachedVertex( first ), bdpt.lightTag, uint( cameraDepth ).add( uint( 1 ) ) );
|
|
2093
|
+
|
|
2094
|
+
} ).ElseIf( bdpt.lightPaths.greaterThan( uint( 0 ) ).and( bdpt.merging ? cameraDepth.lessThanEqual( maxBounceCount ) : cameraDepth.lessThan( maxBounceCount ) ), () => {
|
|
2095
|
+
|
|
2096
|
+
// For ConnectKernel (and MergeKernel); a light vertex must still fit in the bounce budget.
|
|
2097
|
+
writeVertexRecord( hitBufferRW, pendingVertex( rayID ), {
|
|
2098
|
+
position: hitPoint, tag: bdpt.passTag, throughput,
|
|
2099
|
+
V, N, facetN: exactFacetN, materialIndex: hitMatIdx, uv: samplingUV, dVCM: subpath.dVCM, dVC: subpath.dVC, extra: uint( cameraDepth ),
|
|
2100
|
+
} );
|
|
2101
|
+
|
|
2102
|
+
} );
|
|
2103
|
+
|
|
2104
|
+
}
|
|
2105
|
+
|
|
1392
2106
|
const indirectResult = IndirectLightingResult.wrap( calculateIndirectLighting(
|
|
1393
2107
|
N, material,
|
|
1394
2108
|
brdfDir, brdfPdf, brdfValue,
|
|
1395
2109
|
brdfIsTransmission, brdfColorWeight,
|
|
2110
|
+
brdfIsDiffuseTransmission,
|
|
1396
2111
|
rngState,
|
|
1397
2112
|
_pixelCoord, resolution, frame, dimBase,
|
|
1398
2113
|
) ).toVar();
|
|
@@ -1400,8 +2115,10 @@ export function buildShadeKernel( params ) {
|
|
|
1400
2115
|
const bounceDir = indirectResult.direction.toVar();
|
|
1401
2116
|
|
|
1402
2117
|
// Shading-normal leak guard: a normal-mapped lobe can sample below the geometric surface;
|
|
1403
|
-
// tracing that ray tunnels through single-sided shells onto whatever sits behind them.
|
|
1404
|
-
|
|
2118
|
+
// tracing that ray tunnels through single-sided shells onto whatever sits behind them. A diffuse
|
|
2119
|
+
// transmission draw is the mirror case: it must cross the surface.
|
|
2120
|
+
If( select( brdfIsDiffuseTransmission, dot( bounceDir, NgeoFF ).greaterThanEqual( 0.0 ),
|
|
2121
|
+
brdfIsTransmission.not().and( dot( bounceDir, NgeoFF ).lessThanEqual( 0.0 ) ) ), () => {
|
|
1405
2122
|
|
|
1406
2123
|
commitDeferredAux( N );
|
|
1407
2124
|
terminatePath();
|
|
@@ -1410,7 +2127,35 @@ export function buildShadeKernel( params ) {
|
|
|
1410
2127
|
} );
|
|
1411
2128
|
// combinedPdf is stored as next bounce's prevBouncePdf for NEE↔implicit-env MIS
|
|
1412
2129
|
const bouncePdf = max( indirectResult.combinedPdf, 0.001 ).toVar();
|
|
1413
|
-
|
|
2130
|
+
const specularScatter = bdpt ? brdfIsTransmission.or( brdfPdf.greaterThanEqual( DELTA_PDF * 0.5 ) ).toVar() : null;
|
|
2131
|
+
if ( bdpt ) {
|
|
2132
|
+
|
|
2133
|
+
If( isLight, () => {
|
|
2134
|
+
|
|
2135
|
+
// Light flows V → bounceDir; the BSDF is not reciprocal. Shading-normal correction: Veach 5.3.2.
|
|
2136
|
+
const correction = lightEndCosine( V, N, exactFacetN, bounceDir ).div( max( abs( dot( bounceDir, N ) ), 1e-4 ) ).toVar();
|
|
2137
|
+
If( specularScatter.or( brdfPdf.greaterThan( 0.0 ).not() ), () => {
|
|
2138
|
+
|
|
2139
|
+
throughput.mulAssign( indirectResult.throughput.mul( correction ) );
|
|
2140
|
+
|
|
2141
|
+
} ).Else( () => {
|
|
2142
|
+
|
|
2143
|
+
throughput.mulAssign( evaluateMaterialResponse( bounceDir, V, N, material )
|
|
2144
|
+
.mul( max( dot( N, bounceDir ), 0.0 ) ).div( max( brdfPdf, MIN_PDF ) ).mul( correction ) );
|
|
2145
|
+
|
|
2146
|
+
} );
|
|
2147
|
+
|
|
2148
|
+
} ).Else( () => {
|
|
2149
|
+
|
|
2150
|
+
throughput.mulAssign( indirectResult.throughput );
|
|
2151
|
+
|
|
2152
|
+
} );
|
|
2153
|
+
|
|
2154
|
+
} else {
|
|
2155
|
+
|
|
2156
|
+
throughput.mulAssign( indirectResult.throughput );
|
|
2157
|
+
|
|
2158
|
+
}
|
|
1414
2159
|
|
|
1415
2160
|
// Adaptive Russian roulette (gap #7) — material-importance + throughput + env-direction aware, replacing
|
|
1416
2161
|
// the flat clamp(maxThroughput,0.05,0.95). depth = bounceIndex (path length, per gap #4); rayDirection =
|
|
@@ -1434,17 +2179,50 @@ export function buildShadeKernel( params ) {
|
|
|
1434
2179
|
If( cameraDepth.greaterThanEqual( maxBounceCount ), () => {
|
|
1435
2180
|
|
|
1436
2181
|
commitDeferredAux( N );
|
|
1437
|
-
|
|
1438
|
-
|
|
2182
|
+
// One segment more, for the light it may hit: light sampled here keeps its BSDF-hit partner (and its MIS
|
|
2183
|
+
// weight), and bidirectional strategies all reach the longest paths.
|
|
2184
|
+
if ( bdpt ) If( isLight, () => {
|
|
2185
|
+
|
|
2186
|
+
terminatePath();
|
|
2187
|
+
Return();
|
|
2188
|
+
|
|
2189
|
+
} );
|
|
2190
|
+
flags.assign( flags.bitOr( uint( RAY_FLAG.EMISSION_ONLY | RAY_FLAG.AUX_LOCKED ) ) );
|
|
1439
2191
|
|
|
1440
2192
|
} );
|
|
1441
2193
|
|
|
2194
|
+
if ( bdpt ) {
|
|
2195
|
+
|
|
2196
|
+
const cosOut = abs( dot( bounceDir, exactFacetN ) );
|
|
2197
|
+
If( specularScatter, () => {
|
|
2198
|
+
|
|
2199
|
+
misOnSpecular( subpath, cosOut );
|
|
2200
|
+
|
|
2201
|
+
} ).Else( () => {
|
|
2202
|
+
|
|
2203
|
+
misOnScatter( subpath, cosOut, calculateMaterialPDF( V, bounceDir, N, material ), calculateMaterialPDF( bounceDir, V, N, material ) );
|
|
2204
|
+
|
|
2205
|
+
} );
|
|
2206
|
+
|
|
2207
|
+
}
|
|
2208
|
+
|
|
1442
2209
|
const underFacet = dot( facetN, bounceDir ).lessThan( 0.0 ).toVar();
|
|
1443
2210
|
const newOrigin = offsetRayOrigin( hitPoint, select( underFacet, facetN.negate(), facetN ) );
|
|
1444
2211
|
|
|
2212
|
+
if ( bdpt ) If( isLight.not().and( numAreaLights.greaterThan( int( 0 ) ) ), () => {
|
|
2213
|
+
|
|
2214
|
+
const connectible = specularScatter.not().and( dot( N, bounceDir ).greaterThan( 0.0 ) ).and( dot( bounceDir, NgeoFF ).greaterThan( 0.0 ) );
|
|
2215
|
+
bidirectionalAreaHit( newOrigin, bounceDir, ( light ) => select( connectible, select( lampTotal.greaterThan( 0.0 ),
|
|
2216
|
+
estimateLightImportance( light, hitPoint, N, material ).div( max( lampTotal, 1e-30 ) ),
|
|
2217
|
+
float( 1.0 ).div( max( float( lampCount( lamps ) ), 1.0 ) ) ), float( 0.0 ) ) );
|
|
2218
|
+
|
|
2219
|
+
} );
|
|
2220
|
+
|
|
1445
2221
|
// Whether this vertex's sun NEE could have drawn bounceDir, so a sun hit at the miss knows its MIS partner.
|
|
1446
|
-
|
|
1447
|
-
const
|
|
2222
|
+
// Through a diffusely transmitting surface it samples the far side too.
|
|
2223
|
+
const sunNEE = brdfIsTransmission.not().and( dot( N, bounceDir ).greaterThan( 0.0 ).or( brdfIsDiffuseTransmission ) );
|
|
2224
|
+
// Diffuse transmission crosses the surface on purpose; the next hit is an ordinary bounce's.
|
|
2225
|
+
const underSurface = brdfIsTransmission.not().and( brdfIsDiffuseTransmission.not() ).and( underFacet );
|
|
1448
2226
|
flags.assign( flags.sub( flags.bitAnd( uint( RAY_FLAG.SUN_NEE | RAY_FLAG.UNDER_SURFACE ) ) )
|
|
1449
2227
|
.bitOr( select( sunNEE, uint( RAY_FLAG.SUN_NEE ), uint( 0 ) ) )
|
|
1450
2228
|
.bitOr( select( underSurface, uint( RAY_FLAG.UNDER_SURFACE ), uint( 0 ) ) ) );
|
|
@@ -1456,12 +2234,12 @@ export function buildShadeKernel( params ) {
|
|
|
1456
2234
|
writeRayRadiance( rayBufferRW, rayID, currentRadiance );
|
|
1457
2235
|
writeMediumStack( rayBufferRW, rayID, uint( mediumStackDepth ), uint( transTraversals ), mediumStack_ior_1, mediumStack_ior_2, mediumStack_ior_3, uint( pathWavelength.add( 0.5 ) ) );
|
|
1458
2236
|
// DDFA: persist the (possibly mirror-tinted) see-through throughput for the next bounce.
|
|
1459
|
-
If(
|
|
2237
|
+
if ( aux ) If( aux.and( flags.bitAnd( uint( RAY_FLAG.AUX_LOCKED ) ).equal( uint( 0 ) ) ), () => {
|
|
1460
2238
|
|
|
1461
2239
|
writeFeatureThroughput( rayBufferRW, rayID, featCarry );
|
|
1462
2240
|
|
|
1463
2241
|
} );
|
|
1464
|
-
|
|
2242
|
+
persistRng();
|
|
1465
2243
|
|
|
1466
2244
|
} );
|
|
1467
2245
|
|