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