rayzee 10.0.0 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +252 -44
- package/dist/ActiveColor-YvuQq44O.js +13 -0
- package/dist/ArchiveImporter-BRQvLggb.js +4300 -0
- package/dist/AssetConfig-DSEntE65.js +30 -0
- package/dist/BidirectionalIntegrator-9lZQxFVH.js +934 -0
- package/dist/BufferLayout-D_IBo2ci.js +150 -0
- package/dist/ColorManagement-oeEk7oSu.js +1488 -0
- package/dist/EngineEvents-Ci8dVWCP.js +73 -0
- package/dist/EngineIssues-CIZnXHsF.js +72 -0
- package/dist/FileFormats-sPGJeCRc.js +102 -0
- package/dist/Logger-dDDGj9Av.js +97 -0
- package/dist/{NeuralRetouch-CYgkQY8Z.js → NeuralRetouch-BCXylThH.js} +8 -6
- package/dist/{NeuralSuperRes-PkgDLQcD.js → NeuralSuperRes-sZAeS142.js} +55 -120
- package/dist/PathTracer-CQPW06Mm.js +29971 -0
- package/dist/PhysicalSky-BvqCw_dy.js +1219 -0
- package/dist/RayzeeRenderer-COpwotpU.js +3551 -0
- package/dist/SolarLimb-CPaeZVwz.js +721 -0
- package/dist/ToneMapGPU-CjU3osDd.js +177 -0
- package/dist/ViewTransforms-CYN7bq2w.js +255 -0
- package/dist/WorkingMatrix-C6y233hT.js +27 -0
- package/dist/ZipReader-gDSWr0I5.js +98 -0
- package/dist/addons/archives.es.js +3 -0
- package/dist/addons/bidirectional.es.js +2 -0
- package/dist/addons/color.es.js +3 -0
- package/dist/addons/formats.es.js +2 -0
- package/dist/addons/physical-sky.es.js +2 -0
- package/dist/addons/storage.es.js +3 -0
- package/dist/archiveFormats-BZw7TiDp.js +75 -0
- package/dist/identity-DrQsGT2o.js +39 -0
- package/dist/openStorage-DwcBRUj7.js +820 -0
- package/dist/rayzee-core.es.js +12 -0
- package/dist/rayzee.es.js +2551 -44024
- package/dist/rayzee.umd.js +528 -487
- package/dist/sceneCachePolicy-BSSsKY98.js +443 -0
- package/dist/shared-BGBcSxBQ.js +17 -0
- package/package.json +23 -2
- package/src/Color/ActiveColor.js +34 -0
- package/src/Color/BasicColor.js +147 -0
- package/src/Color/ColorManagement.js +7 -31
- package/src/Color/ViewTransforms.js +5 -1
- package/src/EngineDefaults.js +62 -1029
- package/src/EngineEvents.js +2 -0
- package/src/EngineIssues.js +2 -0
- package/src/Headless.js +6 -5
- package/src/Passes/AIUpscaler.js +58 -141
- package/src/Passes/OIDNDenoiser.js +92 -65
- package/src/Passes/OIDNTemporalHistory.js +1 -1
- package/src/PathTracerApp.js +620 -4026
- package/src/Pipeline/PipelineContext.js +0 -4
- package/src/Platform.js +36 -0
- package/src/Processor/ArchiveCache.js +1 -1
- package/src/Processor/ArchiveImporter.js +1259 -0
- package/src/Processor/AssetLoader.js +327 -1763
- package/src/Processor/AtmosphereModel.js +1 -2
- package/src/Processor/BVHBuilder.js +14 -126
- package/src/Processor/BVHLeafFold.js +4 -6
- package/src/Processor/BVHRefitter.js +67 -46
- package/src/Processor/BufferLayout.js +291 -0
- package/src/Processor/EmissiveTriangleBuilder.js +1 -1
- package/src/Processor/EnvironmentExactTable.js +187 -0
- package/src/Processor/EquirectHDRInfo.js +30 -223
- package/src/Processor/FileFormats.js +116 -0
- package/src/Processor/GLTFDecoders.js +85 -0
- package/src/Processor/GLTFDiffuseTransmission.js +63 -0
- package/src/Processor/GeometryExtractor.js +15 -5
- package/src/Processor/HostMemory.js +7 -1
- package/src/Processor/IESParser.js +47 -35
- package/src/Processor/InstanceTable.js +1 -1
- package/src/Processor/KernelManager.js +35 -2
- package/src/Processor/MaterialPacking.js +10 -8
- package/src/Processor/PBRT/PBRTMaterials.js +129 -41
- package/src/Processor/PBRT/PBRTParser.js +30 -17
- package/src/Processor/PBRT/PBRTSceneBuilder.js +548 -37
- package/src/Processor/PBRT/PBRTTextureBake.js +154 -0
- package/src/Processor/PBRT/PFM.js +59 -0
- package/src/Processor/PackedRayBuffer.js +116 -9
- package/src/Processor/ParallelBVHBuilder.js +3 -6
- package/src/Processor/PhysicalSky.js +5 -4
- package/src/Processor/PlatformImageLoader.js +50 -6
- package/src/Processor/QueueManager.js +39 -5
- package/src/Processor/ReinsertionOptimizer.js +2 -2
- package/src/Processor/SceneProcessor.js +53 -62
- package/src/Processor/ShaderBuilder.js +6 -16
- package/src/Processor/SimpleSky.js +1 -62
- package/src/Processor/SolarLimb.js +3 -0
- package/src/Processor/SplitBLAS.js +1 -1
- package/src/Processor/StorageTexturePool.js +22 -1
- package/src/Processor/TLASBuilder.js +1 -3
- package/src/Processor/TextureBuckets.js +237 -0
- package/src/Processor/TextureCreator.js +181 -8
- package/src/Processor/ToneMapGPU.js +64 -15
- package/src/Processor/Workers/BVHRefitWorker.js +5 -2
- package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
- package/src/Processor/Workers/BVHWorker.js +5 -18
- package/src/Processor/Workers/CDFWorker.js +6 -183
- package/src/Processor/Workers/TexturesWorker.js +103 -18
- package/src/Processor/archiveFormats.js +7 -0
- package/src/Processor/utils.js +2 -1
- package/src/RayzeeRenderer.js +3899 -0
- package/src/RenderSettings.js +68 -62
- package/src/Stages/ASVGF.js +13 -14
- package/src/Stages/AutoExposure.js +17 -17
- package/src/Stages/BilateralFilter.js +2 -1
- package/src/Stages/Compositor.js +8 -23
- package/src/Stages/DenoiserSettings.js +182 -0
- package/src/Stages/EdgeFilter.js +2 -1
- package/src/Stages/MotionVector.js +1 -1
- package/src/Stages/NRD.js +11 -5
- package/src/Stages/NormalDepth.js +12 -8
- package/src/Stages/PathTracer.js +338 -43
- package/src/Stages/PathTracerStage.js +69 -66
- package/src/Stages/Variance.js +1 -1
- package/src/Storage/BLASCache.js +1 -1
- package/src/Storage/CDFCache.js +18 -17
- package/src/Storage/DownloadCache.js +1 -1
- package/src/Storage/SceneGraphCodec.js +46 -3
- package/src/Storage/SpillStore.js +1 -1
- package/src/Storage/StorageManager.js +45 -9
- package/src/Storage/areas.js +4 -0
- package/src/TSL/BVHTraversal.js +50 -25
- package/src/TSL/Bidirectional.js +204 -0
- package/src/TSL/BidirectionalLamps.js +132 -0
- package/src/TSL/Clearcoat.js +15 -6
- package/src/TSL/Common.js +20 -15
- package/src/TSL/ConnectKernel.js +147 -0
- package/src/TSL/Debugger.js +1 -1
- package/src/TSL/Displacement.js +4 -3
- package/src/TSL/EmissiveSampling.js +65 -18
- package/src/TSL/Environment.js +77 -106
- package/src/TSL/EnvironmentCDF.js +85 -39
- package/src/TSL/ExtendKernel.js +8 -3
- package/src/TSL/FinalWriteKernel.js +4 -4
- package/src/TSL/GenerateKernel.js +18 -4
- package/src/TSL/HitFacet.js +35 -5
- package/src/TSL/LightBVHSampling.js +40 -9
- package/src/TSL/LightGenerateKernel.js +293 -0
- package/src/TSL/LightGuide.js +198 -0
- package/src/TSL/LightSplatKernel.js +165 -0
- package/src/TSL/LightVisibility.js +111 -0
- package/src/TSL/LightsCore.js +13 -38
- package/src/TSL/LightsDirect.js +28 -29
- package/src/TSL/LightsIndirect.js +3 -1
- package/src/TSL/LightsSampling.js +239 -51
- package/src/TSL/MaterialEvaluation.js +113 -31
- package/src/TSL/MaterialProperties.js +44 -17
- package/src/TSL/MaterialTransmission.js +52 -47
- package/src/TSL/MergeKernel.js +218 -0
- package/src/TSL/PathTracerCore.js +65 -29
- package/src/TSL/Random.js +5 -1
- package/src/TSL/SceneResources.js +51 -0
- package/src/TSL/ShadeKernel.js +964 -101
- package/src/TSL/Struct.js +10 -0
- package/src/TSL/Sun.js +1 -1
- package/src/TSL/TextureSampling.js +66 -61
- package/src/addons/archives.js +14 -0
- package/src/addons/bidirectional.js +13 -0
- package/src/addons/color.js +32 -0
- package/src/addons/formats.js +16 -0
- package/src/addons/physicalSky.js +14 -0
- package/src/addons/storage.js +19 -0
- package/src/core.js +31 -0
- package/src/index.js +13 -23
- package/src/integrators/BidirectionalIntegrator.js +833 -0
- package/src/managers/AnimationManager.js +88 -6
- package/src/managers/CameraManager.js +103 -46
- package/src/managers/DenoisingManager.js +14 -10
- package/src/managers/EnvironmentManager.js +125 -103
- package/src/managers/GoboManager.js +9 -0
- package/src/managers/MaterialDataManager.js +48 -37
- package/src/managers/UniformManager.js +32 -10
- package/dist/ToneMapGPU-B6XIe3DU.js +0 -544
- package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
- package/src/Processor/TreeletOptimizer.js +0 -574
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConnectKernel.js — connects each camera vertex Shade left pending to one vertex of a random light
|
|
3
|
+
* path, weighted by how many of that path's vertices fit the bounce budget (Davidovič et al. 2014).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Fn, float, vec2, vec4, int, uint, If, instanceIndex, atomicLoad, Return, dot, sqrt, min, max, length,
|
|
8
|
+
} from 'three/tsl';
|
|
9
|
+
|
|
10
|
+
import { getRandomSample1D, pcgHash, SAMPLER_DIMS_PER_BOUNCE } from './Random.js';
|
|
11
|
+
import { evaluateMaterialResponse } from './MaterialEvaluation.js';
|
|
12
|
+
import { calculateMaterialPDF } from './LightsSampling.js';
|
|
13
|
+
import { traceShadowRayRefractiveOpaque } from './LightsDirect.js';
|
|
14
|
+
import { traverseBVHShadow } from './BVHTraversal.js';
|
|
15
|
+
import { offsetRayOrigin, SHADOW_END, luminance } from './Common.js';
|
|
16
|
+
import { regularizePathContribution } from './PathTracerCore.js';
|
|
17
|
+
import {
|
|
18
|
+
resolveSurfaceMaterial, lightEndCosine, misPartial, misWeight, strategyWeight, STRATEGY, DIM_CONNECT_PATH, DIM_CONNECT_VERTEX,
|
|
19
|
+
} from './Bidirectional.js';
|
|
20
|
+
import { COUNTER } from '../Processor/QueueManager.js';
|
|
21
|
+
import {
|
|
22
|
+
readRayRadiance, writeRayRadiance, readRngState,
|
|
23
|
+
pendingVertex, cachedVertex, readVertexTag, readVertexRecord, readLightPathLength,
|
|
24
|
+
} from '../Processor/PackedRayBuffer.js';
|
|
25
|
+
|
|
26
|
+
const WG_SIZE = 256;
|
|
27
|
+
|
|
28
|
+
export function buildConnectKernel( params ) {
|
|
29
|
+
|
|
30
|
+
const {
|
|
31
|
+
rayBufferRW, hitBufferRO, activeIndicesRO, counters,
|
|
32
|
+
bvhBuffer, triangleBuffer, materialBuffer,
|
|
33
|
+
resolution, frame, accumFrame, currentBounce, chunkRowBase,
|
|
34
|
+
lightPaths, slotsPerPath, lightTag, passTag, strategyView,
|
|
35
|
+
maxBounceCount, globalIlluminationIntensity, fireflyThreshold,
|
|
36
|
+
// Vertex merging's η² at a point (Bidirectional.js mergeVmAt), or null without it.
|
|
37
|
+
mergeVm = null,
|
|
38
|
+
} = params;
|
|
39
|
+
|
|
40
|
+
return Fn( () => {
|
|
41
|
+
|
|
42
|
+
const tid = instanceIndex;
|
|
43
|
+
If( tid.greaterThanEqual( atomicLoad( counters.element( uint( COUNTER.ENTERING_COUNT ) ) ) ), () => {
|
|
44
|
+
|
|
45
|
+
Return();
|
|
46
|
+
|
|
47
|
+
} );
|
|
48
|
+
|
|
49
|
+
const rayID = activeIndicesRO.element( tid ).toVar();
|
|
50
|
+
const pending = pendingVertex( rayID );
|
|
51
|
+
If( readVertexTag( hitBufferRO, pending ).notEqual( passTag ), () => {
|
|
52
|
+
|
|
53
|
+
Return();
|
|
54
|
+
|
|
55
|
+
} );
|
|
56
|
+
|
|
57
|
+
const camRecord = readVertexRecord( hitBufferRO, pending );
|
|
58
|
+
const cam = { ...camRecord, vm: mergeVm?.( camRecord.position ) ?? null };
|
|
59
|
+
const cameraVertices = int( cam.extra ).add( int( 1 ) ).toVar();
|
|
60
|
+
|
|
61
|
+
const resX = int( resolution.x );
|
|
62
|
+
const globalPixel = int( rayID ).add( chunkRowBase.mul( resX ) );
|
|
63
|
+
const pixel = vec2( float( globalPixel.mod( resX ) ).add( 0.5 ), float( globalPixel.div( resX ) ).add( 0.5 ) ).toVar();
|
|
64
|
+
const dimBase = int( currentBounce ).mul( int( SAMPLER_DIMS_PER_BOUNCE ) );
|
|
65
|
+
// Only PCG reads it; hashed so the path's own stream, already saved by Shade, is not replayed.
|
|
66
|
+
const rng = pcgHash( { state: readRngState( hitBufferRO, rayID ).bitXor( uint( 0x9E3779B9 ) ) } ).toVar();
|
|
67
|
+
const uPath = getRandomSample1D( pixel, int( 0 ), dimBase.add( int( DIM_CONNECT_PATH ) ), rng, resolution, frame ).toVar();
|
|
68
|
+
const uVertex = getRandomSample1D( pixel, int( 0 ), dimBase.add( int( DIM_CONNECT_VERTEX ) ), rng, resolution, frame ).toVar();
|
|
69
|
+
|
|
70
|
+
const path = min( uint( float( lightPaths ).mul( uPath ) ), lightPaths.sub( uint( 1 ) ) ).toVar();
|
|
71
|
+
const first = path.mul( slotsPerPath ).toVar();
|
|
72
|
+
const stored = int( readLightPathLength( hitBufferRO, cachedVertex( first ), lightTag ) );
|
|
73
|
+
const usable = min( stored, int( maxBounceCount ).add( int( 1 ) ).sub( cameraVertices ) ).toVar();
|
|
74
|
+
If( usable.lessThan( int( 1 ) ), () => {
|
|
75
|
+
|
|
76
|
+
Return();
|
|
77
|
+
|
|
78
|
+
} );
|
|
79
|
+
|
|
80
|
+
const c = min( int( float( usable ).mul( uVertex ) ), usable.sub( int( 1 ) ) ).toVar();
|
|
81
|
+
const lightRecord = readVertexRecord( hitBufferRO, cachedVertex( first.add( uint( c ) ) ) );
|
|
82
|
+
const light = { ...lightRecord, vm: mergeVm?.( lightRecord.position ) ?? null };
|
|
83
|
+
|
|
84
|
+
const toLight = light.position.sub( cam.position ).toVar();
|
|
85
|
+
const dist2 = max( dot( toLight, toLight ), 1e-12 ).toVar();
|
|
86
|
+
const dir = toLight.div( sqrt( dist2 ) ).toVar();
|
|
87
|
+
|
|
88
|
+
const cosCamera = dot( dir, cam.N ).toVar();
|
|
89
|
+
const cosCameraFacet = dot( dir, cam.facetN ).toVar();
|
|
90
|
+
const cosLightFacet = dot( dir, light.facetN ).negate().toVar();
|
|
91
|
+
// The camera end tests as NEE does; at the light end the camera vertex is the viewer, which only the facet bounds.
|
|
92
|
+
If( cosCamera.lessThanEqual( 0.0 ).or( cosCameraFacet.lessThanEqual( 0.0 ) ).or( cosLightFacet.lessThanEqual( 0.0 ) ), () => {
|
|
93
|
+
|
|
94
|
+
Return();
|
|
95
|
+
|
|
96
|
+
} );
|
|
97
|
+
|
|
98
|
+
const cameraMaterial = resolveSurfaceMaterial( cam.materialIndex, cam.uv, cam.N, materialBuffer );
|
|
99
|
+
const lightMaterial = resolveSurfaceMaterial( light.materialIndex, light.uv, light.N, materialBuffer );
|
|
100
|
+
const toCamera = dir.negate();
|
|
101
|
+
|
|
102
|
+
const fCamera = evaluateMaterialResponse( cam.V, dir, cam.N, cameraMaterial ).toVar();
|
|
103
|
+
const fLight = evaluateMaterialResponse( toCamera, light.V, light.N, lightMaterial ).toVar();
|
|
104
|
+
const geometry = cosCamera.mul( lightEndCosine( light.V, light.N, light.facetN, toCamera ) ).div( dist2 );
|
|
105
|
+
|
|
106
|
+
const cameraForward = calculateMaterialPDF( cam.V, dir, cam.N, cameraMaterial );
|
|
107
|
+
const cameraReverse = calculateMaterialPDF( dir, cam.V, cam.N, cameraMaterial );
|
|
108
|
+
const lightForward = calculateMaterialPDF( light.V, toCamera, light.N, lightMaterial );
|
|
109
|
+
const lightReverse = calculateMaterialPDF( toCamera, light.V, light.N, lightMaterial );
|
|
110
|
+
|
|
111
|
+
// Georgiev (40)-(41): each side's sum, from the other side's density of reaching it.
|
|
112
|
+
const wLight = misPartial( cameraForward.mul( cosLightFacet ).div( dist2 ), light, lightReverse );
|
|
113
|
+
const wCamera = misPartial( lightForward.mul( cosCameraFacet ).div( dist2 ), cam, cameraReverse );
|
|
114
|
+
|
|
115
|
+
const scattering = cameraVertices.add( c ).add( int( 1 ) );
|
|
116
|
+
// Alone, a path of k scattering vertices is reached by k − 1 connections.
|
|
117
|
+
const weight = strategyWeight( strategyView, STRATEGY.CONNECT, misWeight( wLight, wCamera ), float( 1.0 ).div( float( scattering.sub( int( 1 ) ) ) ) );
|
|
118
|
+
const contribution = regularizePathContribution(
|
|
119
|
+
cam.throughput.mul( fCamera ).mul( fLight ).mul( light.throughput )
|
|
120
|
+
.mul( geometry.mul( weight ).mul( float( usable ) ).mul( globalIlluminationIntensity ) ),
|
|
121
|
+
float( scattering.sub( int( 1 ) ) ), fireflyThreshold, int( accumFrame ),
|
|
122
|
+
).toVar();
|
|
123
|
+
|
|
124
|
+
If( luminance( contribution ).greaterThan( 0.0 ), () => {
|
|
125
|
+
|
|
126
|
+
const origin = offsetRayOrigin( cam.position, cam.facetN ).toVar();
|
|
127
|
+
const segment = offsetRayOrigin( light.position, light.facetN ).sub( origin ).toVar();
|
|
128
|
+
const segmentLength = length( segment ).toVar();
|
|
129
|
+
const visibility = traceShadowRayRefractiveOpaque(
|
|
130
|
+
origin, segment.div( segmentLength ), segmentLength.mul( SHADOW_END ),
|
|
131
|
+
traverseBVHShadow, bvhBuffer, triangleBuffer, materialBuffer,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
If( visibility.greaterThan( 0.0 ), () => {
|
|
135
|
+
|
|
136
|
+
const radiance = readRayRadiance( rayBufferRW, rayID ).toVar();
|
|
137
|
+
writeRayRadiance( rayBufferRW, rayID, vec4( radiance.xyz.add( contribution.mul( visibility ) ), radiance.w ) );
|
|
138
|
+
|
|
139
|
+
} );
|
|
140
|
+
|
|
141
|
+
} );
|
|
142
|
+
|
|
143
|
+
} );
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export { WG_SIZE as CONNECT_WG_SIZE };
|
package/src/TSL/Debugger.js
CHANGED
|
@@ -72,7 +72,7 @@ export const TraceDebugMode = Fn( ( [
|
|
|
72
72
|
visMode, debugVisScale,
|
|
73
73
|
// Screen info
|
|
74
74
|
pixelCoord, resolution,
|
|
75
|
-
// Material textures
|
|
75
|
+
// Material textures come from the kernel's SceneResources.
|
|
76
76
|
// Camera matrices (for depth debug mode)
|
|
77
77
|
cameraProjectionMatrix, cameraViewMatrix,
|
|
78
78
|
// Frame counter (for stochastic debug modes)
|
package/src/TSL/Displacement.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { sceneResources } from './SceneResources.js';
|
|
1
2
|
import { Fn, float, vec2, int, If, Loop, abs, normalize, dot, max, uintBitsToFloat } from 'three/tsl';
|
|
2
3
|
|
|
3
4
|
import { struct } from './patches.js';
|
|
4
5
|
import { triangleRow, instanceRows, instanceDirToWorld } from './Common.js';
|
|
5
|
-
import { sampleDisplacementMap, bucketTexelSize
|
|
6
|
+
import { sampleDisplacementMap, bucketTexelSize } from './TextureSampling.js';
|
|
6
7
|
|
|
7
8
|
// Ray-displacement intersection configuration
|
|
8
9
|
const MAX_MARCH_STEPS = 32;
|
|
@@ -32,7 +33,7 @@ export const DisplacementResult = struct( {
|
|
|
32
33
|
*/
|
|
33
34
|
export const refineDisplacedIntersection = Fn( ( [
|
|
34
35
|
ray, hitInfo, triangleBuffer, material, bounceIndex, bvhBuffer, instanceLeaf
|
|
35
|
-
] ) => {
|
|
36
|
+
], builder ) => {
|
|
36
37
|
|
|
37
38
|
const resultHitPoint = hitInfo.hitPoint.toVar();
|
|
38
39
|
const resultUV = hitInfo.uv.toVar();
|
|
@@ -205,7 +206,7 @@ export const refineDisplacedIntersection = Fn( ( [
|
|
|
205
206
|
|
|
206
207
|
// Compute displaced normal from height-field gradients using UV tangent vectors.
|
|
207
208
|
// Finite-difference step = one texel of the SELECTED bucket array's real dimensions.
|
|
208
|
-
const texel = bucketTexelSize(
|
|
209
|
+
const texel = bucketTexelSize( sceneResources( builder ).linearBuckets, material.displacementMapIndex ).toVar();
|
|
209
210
|
const hC = finalHeight;
|
|
210
211
|
const hU = sampleDisplacementMap(
|
|
211
212
|
material.displacementMapIndex, finalUV.add( vec2( texel.x, 0.0 ) ), material.displacementTransform,
|
|
@@ -32,11 +32,13 @@ import {
|
|
|
32
32
|
computeDotProductsAniso, instanceRows, instanceNormalToWorld, instancePointToWorld,
|
|
33
33
|
unpackTriangleNormal, triangleRow, SHADOW_END
|
|
34
34
|
} from './Common.js';
|
|
35
|
-
import { TRI_MATERIAL_MASK } from '../
|
|
35
|
+
import { TRI_MATERIAL_MASK } from '../Processor/BufferLayout.js';
|
|
36
36
|
import { getRandomSample1D, getRandomSample2D } from './Random.js';
|
|
37
37
|
import { calculateMaterialPDFFromDots } from './LightsSampling.js';
|
|
38
|
-
import { evaluateMaterialResponseFromDots } from './MaterialEvaluation.js';
|
|
38
|
+
import { evaluateMaterialResponseFromDots, evaluateDiffuseTransmission } from './MaterialEvaluation.js';
|
|
39
|
+
import { diffuseTransmissionWeight, diffuseTransmissionPdf } from './MaterialProperties.js';
|
|
39
40
|
import { DotProducts } from './Struct.js';
|
|
41
|
+
import { triangleSide, sideAccepts } from './BVHTraversal.js';
|
|
40
42
|
|
|
41
43
|
// ================================================================================
|
|
42
44
|
// STRUCTS
|
|
@@ -54,6 +56,21 @@ export const EmissiveSample = struct( {
|
|
|
54
56
|
valid: 'bool', // Whether sample is valid
|
|
55
57
|
} );
|
|
56
58
|
|
|
59
|
+
// EmissiveSample plus the triangle it landed on, for the bidirectional MIS weight.
|
|
60
|
+
export const EmissiveSampleIndexed = struct( {
|
|
61
|
+
position: 'vec3',
|
|
62
|
+
normal: 'vec3',
|
|
63
|
+
emission: 'vec3',
|
|
64
|
+
direction: 'vec3',
|
|
65
|
+
distance: 'float',
|
|
66
|
+
pdf: 'float',
|
|
67
|
+
area: 'float',
|
|
68
|
+
cosThetaLight: 'float',
|
|
69
|
+
valid: 'bool',
|
|
70
|
+
triangleIndex: 'int',
|
|
71
|
+
faceNormal: 'vec3', // winding normal
|
|
72
|
+
} );
|
|
73
|
+
|
|
57
74
|
export const EmissiveContributionResult = struct( {
|
|
58
75
|
contribution: 'vec3',
|
|
59
76
|
hasEmissive: 'bool',
|
|
@@ -285,6 +302,7 @@ export const TriangleData = struct( {
|
|
|
285
302
|
v0: 'vec3', v1: 'vec3', v2: 'vec3',
|
|
286
303
|
n0: 'vec3', n1: 'vec3', n2: 'vec3',
|
|
287
304
|
materialIndex: 'int',
|
|
305
|
+
side: 'int', // 0 front, 1 back, 2 double (BVHTraversal.js triangleSide)
|
|
288
306
|
} );
|
|
289
307
|
|
|
290
308
|
// Fetch triangle vertices from storage buffer
|
|
@@ -323,6 +341,7 @@ export const fetchTriangleData = Fn( ( [ triangleIndex, triangleBuffer, bvhBuffe
|
|
|
323
341
|
return TriangleData( {
|
|
324
342
|
v0, v1, v2, n0, n1, n2,
|
|
325
343
|
materialIndex: int( uvMat.z.bitAnd( uint( TRI_MATERIAL_MASK ) ) ),
|
|
344
|
+
side: triangleSide( uvMat.z ),
|
|
326
345
|
} );
|
|
327
346
|
|
|
328
347
|
} );
|
|
@@ -359,14 +378,16 @@ export const calculateEmissiveLightPdf = Fn( ( [
|
|
|
359
378
|
|
|
360
379
|
// Area: PDF = (power/totalPower) / area, converted to solid angle
|
|
361
380
|
const geoNormal = normalize( cross( triData.v1.sub( triData.v0 ), triData.v2.sub( triData.v0 ) ) );
|
|
362
|
-
const cosLight = max( dot( rayDir
|
|
381
|
+
const cosLight = max( abs( dot( rayDir, geoNormal ) ), 0.001 );
|
|
363
382
|
const distSq = hitDistance.mul( hitDistance );
|
|
364
383
|
const pdfArea = selectionPdf.div( area );
|
|
365
384
|
result.assign( pdfArea.mul( distSq ).div( cosLight ) );
|
|
366
385
|
|
|
367
386
|
} );
|
|
368
387
|
|
|
369
|
-
|
|
388
|
+
// The sampler draws only the sides the triangle emits from (a unit normal: sideAccepts has a threshold).
|
|
389
|
+
const winding = normalize( cross( triData.v1.sub( triData.v0 ), triData.v2.sub( triData.v0 ) ) );
|
|
390
|
+
return select( sideAccepts( triData.side, dot( rayDir, winding ) ), max( result, MIN_PDF ), float( 0.0 ) );
|
|
370
391
|
|
|
371
392
|
} );
|
|
372
393
|
|
|
@@ -378,7 +399,7 @@ export const calculateEmissiveLightPdf = Fn( ( [
|
|
|
378
399
|
// CDF values are stored in the .b channel of the emissive buffer.
|
|
379
400
|
// `emissiveOffset` is the vec4-element offset into the packed light buffer
|
|
380
401
|
// where emissive entries start (0 if using a non-packed buffer).
|
|
381
|
-
const binarySearchCDF = Fn( ( [ emissiveTriangleBuffer, emissiveOffset, emissiveTriangleCount, rand ] ) => {
|
|
402
|
+
export const binarySearchCDF = Fn( ( [ emissiveTriangleBuffer, emissiveOffset, emissiveTriangleCount, rand ] ) => {
|
|
382
403
|
|
|
383
404
|
const lo = int( 0 ).toVar();
|
|
384
405
|
const hi = emissiveTriangleCount.sub( 1 ).toVar();
|
|
@@ -407,15 +428,17 @@ const binarySearchCDF = Fn( ( [ emissiveTriangleBuffer, emissiveOffset, emissive
|
|
|
407
428
|
// Sample from emissive triangle index using CDF importance sampling.
|
|
408
429
|
// `emissiveTriangleBuffer` may be the shared packed light buffer; `emissiveVec4Offset`
|
|
409
430
|
// gives the vec4 offset where emissive entries begin.
|
|
410
|
-
|
|
431
|
+
const makeSampleEmissiveTriangle = ( indexed ) => Fn( ( [
|
|
411
432
|
hitPoint, surfaceNormal,
|
|
412
433
|
rngState,
|
|
413
434
|
pixelCoord, resolution, frame, dimBase,
|
|
414
435
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
415
436
|
triangleBuffer, bvhBuffer,
|
|
437
|
+
// The surface passes light through diffusely: a point behind it is as good as one in front.
|
|
438
|
+
throughSurface,
|
|
416
439
|
] ) => {
|
|
417
440
|
|
|
418
|
-
const
|
|
441
|
+
const fields = {
|
|
419
442
|
position: vec3( 0.0 ),
|
|
420
443
|
normal: vec3( 0.0 ),
|
|
421
444
|
emission: vec3( 0.0 ),
|
|
@@ -425,7 +448,10 @@ export const sampleEmissiveTriangle = Fn( ( [
|
|
|
425
448
|
area: float( 0.0 ),
|
|
426
449
|
cosThetaLight: float( 0.0 ),
|
|
427
450
|
valid: false,
|
|
428
|
-
}
|
|
451
|
+
};
|
|
452
|
+
const result = ( indexed
|
|
453
|
+
? EmissiveSampleIndexed( { ...fields, triangleIndex: int( - 1 ), faceNormal: vec3( 0.0 ) } )
|
|
454
|
+
: EmissiveSample( fields ) ).toVar();
|
|
429
455
|
|
|
430
456
|
// Check if we have emissive triangles
|
|
431
457
|
If( emissiveTriangleCount.greaterThan( int( 0 ) ), () => {
|
|
@@ -469,9 +495,9 @@ export const sampleEmissiveTriangle = Fn( ( [
|
|
|
469
495
|
const samplePos = sphResult.position;
|
|
470
496
|
|
|
471
497
|
const surfaceFacing = dot( dir, surfaceNormal );
|
|
472
|
-
const emissiveFacing = dot( dir, geoNormal
|
|
498
|
+
const emissiveFacing = abs( dot( dir, geoNormal ) );
|
|
473
499
|
|
|
474
|
-
If( surfaceFacing.greaterThan( 0.0 ).
|
|
500
|
+
If( surfaceFacing.greaterThan( 0.0 ).or( throughSurface ).and( sideAccepts( triData.side, dot( dir, geoNormal ) ) ), () => {
|
|
475
501
|
|
|
476
502
|
// Interpolate normal at sampled point via barycentric coords
|
|
477
503
|
const bary = barycentricFromPoint( samplePos, triData.v0, triData.v1, triData.v2 );
|
|
@@ -495,6 +521,12 @@ export const sampleEmissiveTriangle = Fn( ( [
|
|
|
495
521
|
result.area.assign( area );
|
|
496
522
|
result.cosThetaLight.assign( emissiveFacing );
|
|
497
523
|
result.valid.assign( true );
|
|
524
|
+
if ( indexed ) {
|
|
525
|
+
|
|
526
|
+
result.triangleIndex.assign( triangleIndex );
|
|
527
|
+
result.faceNormal.assign( geoNormal );
|
|
528
|
+
|
|
529
|
+
}
|
|
498
530
|
|
|
499
531
|
} );
|
|
500
532
|
|
|
@@ -512,9 +544,11 @@ export const sampleEmissiveTriangle = Fn( ( [
|
|
|
512
544
|
const dir = toEmissive.div( dist );
|
|
513
545
|
|
|
514
546
|
const surfaceFacing = dot( dir, surfaceNormal );
|
|
515
|
-
|
|
547
|
+
// The facet's cosine, not the shading normal's: it is what turns an area density into a solid-angle one.
|
|
548
|
+
const emissiveFacing = abs( dot( dir, geoNormal ) );
|
|
516
549
|
|
|
517
|
-
|
|
550
|
+
// Edge-on (a two-sided emitter passes its side test there) the density is infinite and the light zero.
|
|
551
|
+
If( surfaceFacing.greaterThan( 0.0 ).or( throughSurface ).and( sideAccepts( triData.side, dot( dir, geoNormal ) ) ).and( emissiveFacing.greaterThan( 0.0 ) ), () => {
|
|
518
552
|
|
|
519
553
|
// PDF: CDF selection (power/totalPower) * uniform area (1/area)
|
|
520
554
|
// Converted to solid angle: pdfArea * distSq / cosLight
|
|
@@ -530,6 +564,12 @@ export const sampleEmissiveTriangle = Fn( ( [
|
|
|
530
564
|
result.area.assign( area );
|
|
531
565
|
result.cosThetaLight.assign( emissiveFacing );
|
|
532
566
|
result.valid.assign( true );
|
|
567
|
+
if ( indexed ) {
|
|
568
|
+
|
|
569
|
+
result.triangleIndex.assign( triangleIndex );
|
|
570
|
+
result.faceNormal.assign( geoNormal );
|
|
571
|
+
|
|
572
|
+
}
|
|
533
573
|
|
|
534
574
|
} );
|
|
535
575
|
|
|
@@ -541,6 +581,9 @@ export const sampleEmissiveTriangle = Fn( ( [
|
|
|
541
581
|
|
|
542
582
|
} );
|
|
543
583
|
|
|
584
|
+
export const sampleEmissiveTriangle = /*@__PURE__*/ makeSampleEmissiveTriangle( false );
|
|
585
|
+
export const sampleEmissiveTriangleIndexed = /*@__PURE__*/ makeSampleEmissiveTriangle( true );
|
|
586
|
+
|
|
544
587
|
// ================================================================================
|
|
545
588
|
// EMISSIVE TRIANGLE DIRECT LIGHTING CONTRIBUTION
|
|
546
589
|
// ================================================================================
|
|
@@ -573,11 +616,12 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
|
573
616
|
// down-weights BSDF hits deletes emitter energy from deep GI.
|
|
574
617
|
|
|
575
618
|
// Sample emissive triangle (CDF importance-weighted)
|
|
619
|
+
const transmits = material.diffuseTransmission.greaterThan( 0.0 );
|
|
576
620
|
const emissiveSample = EmissiveSample.wrap( sampleEmissiveTriangle(
|
|
577
621
|
hitPoint, normal, rngState,
|
|
578
622
|
pixelCoord, resolution, frame, dimBase,
|
|
579
623
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
580
|
-
triangleBuffer, bvhBuffer,
|
|
624
|
+
triangleBuffer, bvhBuffer, transmits,
|
|
581
625
|
) );
|
|
582
626
|
|
|
583
627
|
If( emissiveSample.valid.and( emissiveSample.pdf.greaterThan( 0.0 ) ), () => {
|
|
@@ -586,10 +630,12 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
|
586
630
|
result.emissionOnly.assign( emissiveSample.emission );
|
|
587
631
|
result.distance.assign( emissiveSample.distance );
|
|
588
632
|
|
|
589
|
-
// Check geometric validity
|
|
590
|
-
const
|
|
633
|
+
// Check geometric validity: in front, or behind a surface that passes light through diffusely
|
|
634
|
+
const back = transmits.and( dot( normal, emissiveSample.direction ).lessThan( 0.0 ) )
|
|
635
|
+
.and( dot( emissiveSample.direction, geomNormal ).lessThan( 0.0 ) ).toVar();
|
|
636
|
+
const NoL = abs( dot( normal, emissiveSample.direction ) );
|
|
591
637
|
|
|
592
|
-
If( NoL.greaterThan( 0.0 ).and( dot( emissiveSample.direction, geomNormal ).greaterThan( 0.0 ) ), () => {
|
|
638
|
+
If( back.or( NoL.greaterThan( 0.0 ).and( dot( emissiveSample.direction, geomNormal ).greaterThan( 0.0 ) ) ), () => {
|
|
593
639
|
|
|
594
640
|
// Aimed at the sampled point and stopped a relative hair short, as for area lights.
|
|
595
641
|
const rayOrigin = hitPoint.add( calculateRayOffsetFn( hitPoint, geomNormal, emissiveSample.direction ) );
|
|
@@ -602,8 +648,9 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
|
602
648
|
// Share H + dot products between BRDF eval and PDF (computeDotProducts
|
|
603
649
|
// would otherwise run twice with identical inputs).
|
|
604
650
|
const dots = DotProducts.wrap( computeDotProductsAniso( normal, viewDir, emissiveSample.direction, material ) );
|
|
605
|
-
const brdfValue = evaluateMaterialResponseFromDots( material, dots );
|
|
606
|
-
const brdfPdf =
|
|
651
|
+
const brdfValue = select( back, evaluateDiffuseTransmission( material, max( dot( normal, viewDir ), 0.001 ) ), evaluateMaterialResponseFromDots( material, dots ) );
|
|
652
|
+
const brdfPdf = select( back,
|
|
653
|
+
diffuseTransmissionPdf( diffuseTransmissionWeight( material ), NoL ), calculateMaterialPDFFromDots( material, dots ) );
|
|
607
654
|
|
|
608
655
|
// MIS weight: balance light sampling vs BRDF sampling
|
|
609
656
|
const misWeight = select(
|
package/src/TSL/Environment.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Fn, wgslFn, vec2, vec3,
|
|
1
|
+
import { Fn, wgslFn, vec2, vec3, ivec2, float, int, If, Loop, sin, sqrt, min, max, clamp, select } from 'three/tsl';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { EXACT_TABLE_MAX_WIDTH, GUIDES_PER_ENTRY } from '../Processor/EnvironmentExactTable.js';
|
|
4
4
|
|
|
5
5
|
// Convert direction to UV coordinates for equirectangular map
|
|
6
6
|
// Exact implementation from three-gpu-pathtracer
|
|
@@ -28,125 +28,96 @@ export const equirectUvToDirection = /*@__PURE__*/ wgslFn( `
|
|
|
28
28
|
}
|
|
29
29
|
` );
|
|
30
30
|
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
export const sampleEquirect = Fn( ( [ environment, direction, environmentMatrix, envTotalSum, envCompensationDelta, envResolution ] ) => {
|
|
31
|
+
// The table (EnvironmentExactTable.js packExactTable): texel ( x, y ) is row y's entry x — its running sum, the one
|
|
32
|
+
// below it, and the guides of steps 2x and 2x + 1 — and texel ( w, y ) the rows' entry y likewise. Sizes as exactTableSize.
|
|
33
|
+
const exactTable = ( cdfTexture, envResolution ) => {
|
|
35
34
|
|
|
36
|
-
const
|
|
35
|
+
const W = int( envResolution.x );
|
|
36
|
+
const H = int( envResolution.y );
|
|
37
|
+
const k = max( W.add( int( EXACT_TABLE_MAX_WIDTH - 1 ) ).div( int( EXACT_TABLE_MAX_WIDTH ) ), int( 1 ) );
|
|
38
|
+
const w = W.add( k ).sub( int( 1 ) ).div( k ).toVar();
|
|
39
|
+
const h = H.add( k ).sub( int( 1 ) ).div( k ).toVar();
|
|
40
|
+
return { w, h, entry: ( x, y ) => cdfTexture.load( ivec2( x, y ) ) };
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
};
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
// The first of `count` entries whose running sum is above target: the guide of target's step names it at once unless
|
|
45
|
+
// the step holds more than one entry boundary, or f32 rounding of target × steps picked the step beside it; then a
|
|
46
|
+
// search from one entry below the guide to the guide two steps on. Returns the entry with its sum and the one below.
|
|
47
|
+
const invert = ( count, entryAt, target ) => {
|
|
42
48
|
|
|
43
|
-
|
|
49
|
+
const steps = count.mul( int( GUIDES_PER_ENTRY ) ).toVar();
|
|
50
|
+
const step = clamp( int( target.mul( float( steps ) ) ), int( 0 ), steps.sub( int( 1 ) ) ).toVar();
|
|
51
|
+
const odd = step.bitAnd( int( 1 ) ).equal( int( 1 ) ).toVar();
|
|
52
|
+
const guides = entryAt( step.shiftRight( int( 1 ) ) ).toVar();
|
|
53
|
+
const index = int( select( odd, guides.w, guides.z ) ).toVar();
|
|
54
|
+
const e = entryAt( index ).toVar();
|
|
55
|
+
If( target.lessThan( e.y ).or( target.greaterThanEqual( e.x ).and( index.lessThan( count.sub( int( 1 ) ) ) ) ), () => {
|
|
44
56
|
|
|
45
|
-
const
|
|
46
|
-
const
|
|
57
|
+
const lo = max( index.sub( int( 1 ) ), int( 0 ) ).toVar();
|
|
58
|
+
const ahead = entryAt( step.shiftRight( int( 1 ) ).add( int( 1 ) ) );
|
|
59
|
+
const hi = select( step.add( int( 2 ) ).lessThan( steps ), int( select( odd, ahead.w, ahead.z ) ), count.sub( int( 1 ) ) ).toVar();
|
|
60
|
+
Loop( lo.lessThan( hi ), () => {
|
|
47
61
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const lum = dot( color, REC709_LUMINANCE_COEFFICIENTS );
|
|
51
|
-
const weightedLum = lum.mul( sinTheta );
|
|
52
|
-
// MIS Compensation: subtract delta to match the sharpened CDF
|
|
53
|
-
const compensatedWeight = max( float( 0.0 ), weightedLum.sub( envCompensationDelta ) );
|
|
54
|
-
const pdf = compensatedWeight.div( envTotalSum );
|
|
62
|
+
const mid = lo.add( hi ).div( 2 ).toVar();
|
|
63
|
+
If( entryAt( mid ).x.lessThanEqual( target ), () => {
|
|
55
64
|
|
|
56
|
-
|
|
57
|
-
// the helper would otherwise re-derive uv via atan2+acos and recompute sin.
|
|
58
|
-
const dirPdf = sinTheta.greaterThan( 0.0 ).select(
|
|
59
|
-
float( 1.0 ).div( float( 2.0 * Math.PI * Math.PI ).mul( sinTheta ) ),
|
|
60
|
-
float( 0.0 )
|
|
61
|
-
);
|
|
62
|
-
const finalPdf = float( envResolution.x ).mul( float( envResolution.y ) ).mul( pdf ).mul( dirPdf );
|
|
65
|
+
lo.assign( mid.add( 1 ) );
|
|
63
66
|
|
|
64
|
-
|
|
67
|
+
} ).Else( () => {
|
|
68
|
+
|
|
69
|
+
hi.assign( mid );
|
|
70
|
+
|
|
71
|
+
} );
|
|
72
|
+
|
|
73
|
+
} );
|
|
74
|
+
index.assign( lo );
|
|
75
|
+
e.assign( entryAt( lo ) );
|
|
65
76
|
|
|
66
77
|
} );
|
|
78
|
+
return { index, top: e.x, bottom: e.y };
|
|
67
79
|
|
|
68
|
-
|
|
80
|
+
};
|
|
69
81
|
|
|
70
|
-
|
|
82
|
+
// Cell density over uv, per steradian at v.
|
|
83
|
+
const cellPdf = ( share, w, h, v ) => {
|
|
71
84
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
export const sampleEquirectProbability = Fn( ( [
|
|
76
|
-
environment,
|
|
77
|
-
envCDFTexture,
|
|
78
|
-
environmentMatrix,
|
|
79
|
-
environmentIntensity,
|
|
80
|
-
envTotalSum,
|
|
81
|
-
envCompensationDelta,
|
|
82
|
-
envResolution,
|
|
83
|
-
r,
|
|
84
|
-
colorOutput
|
|
85
|
-
] ) => {
|
|
86
|
-
|
|
87
|
-
// CDF texture layout: (W+1)×H R32F — conditional[cy*W+cx] at texel (cx,cy); marginal[cy] at column W.
|
|
88
|
-
const cdfMarginalCol = int( envResolution.x ).toVar();
|
|
89
|
-
|
|
90
|
-
// Sample marginal CDF for V coordinate (1D, linear interpolation)
|
|
91
|
-
const marginalSize = envResolution.y;
|
|
92
|
-
const mIdx = clamp( r.x.mul( marginalSize.sub( 1.0 ) ), 0.0, marginalSize.sub( 1.0 ) );
|
|
93
|
-
const mI0 = int( floor( mIdx ) );
|
|
94
|
-
const mI1 = min( mI0.add( 1 ), int( marginalSize ).sub( 1 ) );
|
|
95
|
-
const mFrac = fract( mIdx );
|
|
96
|
-
const v = mix(
|
|
97
|
-
envCDFTexture.load( ivec2( cdfMarginalCol, mI0 ) ).x,
|
|
98
|
-
envCDFTexture.load( ivec2( cdfMarginalCol, mI1 ) ).x,
|
|
99
|
-
mFrac,
|
|
100
|
-
).toVar();
|
|
101
|
-
|
|
102
|
-
// Sample conditional CDF for U coordinate (2D grid, bilinear interpolation)
|
|
103
|
-
const condW = envResolution.x;
|
|
104
|
-
const condH = envResolution.y;
|
|
105
|
-
const cxf = clamp( r.y.mul( condW.sub( 1.0 ) ), 0.0, condW.sub( 1.0 ) );
|
|
106
|
-
const cyf = clamp( v.mul( condH.sub( 1.0 ) ), 0.0, condH.sub( 1.0 ) );
|
|
107
|
-
const cx0 = int( floor( cxf ) );
|
|
108
|
-
const cy0 = int( floor( cyf ) );
|
|
109
|
-
const cx1 = min( cx0.add( 1 ), int( condW ).sub( 1 ) );
|
|
110
|
-
const cy1 = min( cy0.add( 1 ), int( condH ).sub( 1 ) );
|
|
111
|
-
const fx = fract( cxf );
|
|
112
|
-
const fy = fract( cyf );
|
|
113
|
-
const v00 = envCDFTexture.load( ivec2( cx0, cy0 ) ).x;
|
|
114
|
-
const v10 = envCDFTexture.load( ivec2( cx1, cy0 ) ).x;
|
|
115
|
-
const v01 = envCDFTexture.load( ivec2( cx0, cy1 ) ).x;
|
|
116
|
-
const v11 = envCDFTexture.load( ivec2( cx1, cy1 ) ).x;
|
|
117
|
-
const u = mix( mix( v00, v10, fx ), mix( v01, v11, fx ), fy ).toVar();
|
|
118
|
-
|
|
119
|
-
const uv = vec2( u, v ).toVar();
|
|
120
|
-
|
|
121
|
-
// Convert UV to direction
|
|
122
|
-
const direction = equirectUvToDirection( { uv, environmentMatrix } ).toVar();
|
|
123
|
-
|
|
124
|
-
// Sample color
|
|
125
|
-
const color = texture( environment, uv, 0 ).rgb.mul( environmentIntensity ).toVar();
|
|
126
|
-
|
|
127
|
-
// Write color to output parameter (avoids redundant CDF texture lookups)
|
|
128
|
-
colorOutput.assign( color );
|
|
129
|
-
|
|
130
|
-
// Calculate PDF — sin(theta) weighting + MIS Compensation (Karlík et al. 2019)
|
|
131
|
-
const sinTheta = sin( uv.y.mul( Math.PI ) ).toVar();
|
|
132
|
-
const lum = dot( color.div( environmentIntensity ), REC709_LUMINANCE_COEFFICIENTS );
|
|
133
|
-
const weightedLum = lum.mul( sinTheta );
|
|
134
|
-
const compensatedWeight = max( float( 0.0 ), weightedLum.sub( envCompensationDelta ) );
|
|
135
|
-
const pdf = compensatedWeight.div( envTotalSum );
|
|
136
|
-
|
|
137
|
-
// Inline equirectDirectionPdf — uv + sinTheta are already in scope, so we
|
|
138
|
-
// skip the helper's redundant uv-from-direction + sin recompute.
|
|
139
|
-
const dirPdf = sinTheta.greaterThan( 0.0 ).select(
|
|
140
|
-
float( 1.0 ).div( float( 2.0 * Math.PI * Math.PI ).mul( sinTheta ) ),
|
|
141
|
-
float( 0.0 )
|
|
142
|
-
);
|
|
143
|
-
const finalPdf = float( envResolution.x ).mul( float( envResolution.y ) ).mul( pdf ).mul( dirPdf );
|
|
144
|
-
|
|
145
|
-
return vec4( direction, finalPdf );
|
|
85
|
+
const sinTheta = sin( v.mul( Math.PI ) );
|
|
86
|
+
return select( sinTheta.greaterThan( 0.0 ).and( share.greaterThan( 0.0 ) ),
|
|
87
|
+
share.mul( float( w ) ).mul( float( h ) ).div( max( sinTheta.mul( 2 * Math.PI * Math.PI ), 1e-30 ) ), float( 0.0 ) );
|
|
146
88
|
|
|
147
|
-
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** A direction drawn exactly from the table, and its density per steradian. */
|
|
92
|
+
export function sampleEnvironmentExact( cdfTexture, environmentMatrix, envResolution, xi ) {
|
|
93
|
+
|
|
94
|
+
const { w, h, entry } = exactTable( cdfTexture, envResolution );
|
|
95
|
+
const row = invert( h, ( i ) => entry( w, i ), xi.y );
|
|
96
|
+
const y = row.index.toVar();
|
|
97
|
+
const cell = invert( w, ( i ) => entry( i, y ), xi.x );
|
|
98
|
+
const pRow = row.top.sub( row.bottom ).toVar();
|
|
99
|
+
const pCell = cell.top.sub( cell.bottom ).toVar();
|
|
100
|
+
|
|
101
|
+
const fv = clamp( xi.y.sub( row.bottom ).div( max( pRow, 1e-30 ) ), 0.0, 0.99999994 );
|
|
102
|
+
const fu = clamp( xi.x.sub( cell.bottom ).div( max( pCell, 1e-30 ) ), 0.0, 0.99999994 );
|
|
103
|
+
const uv = vec2( float( cell.index ).add( fu ).div( float( w ) ), float( y ).add( fv ).div( float( h ) ) ).toVar();
|
|
104
|
+
|
|
105
|
+
return { direction: equirectUvToDirection( { uv, environmentMatrix } ), pdf: cellPdf( pRow.mul( pCell ), w, h, uv.y ) };
|
|
106
|
+
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** sampleEnvironmentExact's density per steradian for `direction`. */
|
|
110
|
+
export function environmentPdfExact( cdfTexture, environmentMatrix, envResolution, direction ) {
|
|
111
|
+
|
|
112
|
+
const { w, h, entry } = exactTable( cdfTexture, envResolution );
|
|
113
|
+
const uv = equirectDirectionToUv( { direction, environmentMatrix } ).toVar();
|
|
114
|
+
const x = clamp( int( uv.x.mul( float( w ) ) ), int( 0 ), w.sub( int( 1 ) ) ).toVar();
|
|
115
|
+
const y = clamp( int( uv.y.mul( float( h ) ) ), int( 0 ), h.sub( int( 1 ) ) ).toVar();
|
|
116
|
+
const rowEntry = entry( w, y ).toVar();
|
|
117
|
+
const cellEntry = entry( x, y ).toVar();
|
|
118
|
+
return cellPdf( rowEntry.x.sub( rowEntry.y ).mul( cellEntry.x.sub( cellEntry.y ) ), w, h, uv.y );
|
|
148
119
|
|
|
149
|
-
|
|
120
|
+
}
|
|
150
121
|
|
|
151
122
|
// Simple environment lookup (no importance sampling) — native WGSL
|
|
152
123
|
export const sampleEnvironment = /*@__PURE__*/ wgslFn( `
|