rayzee 9.4.1 → 9.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +226 -25
- package/dist/ActiveColor-YvuQq44O.js +13 -0
- package/dist/ArchiveImporter-xuUJiklP.js +4301 -0
- package/dist/AssetConfig-DSEntE65.js +30 -0
- package/dist/BidirectionalIntegrator-C5PKjJoT.js +934 -0
- package/dist/ColorManagement-CheBCPaW.js +1488 -0
- package/dist/EngineDefaults-DH3bI5t-.js +844 -0
- package/dist/EngineEvents-Ci8dVWCP.js +73 -0
- package/dist/EngineIssues-CIZnXHsF.js +72 -0
- package/dist/FileFormats-sPGJeCRc.js +102 -0
- package/dist/Logger-BnHghprG.js +97 -0
- package/dist/{NeuralRetouch-vkdXXyK2.js → NeuralRetouch-CW11yLZ_.js} +8 -6
- package/dist/{NeuralSuperRes-FVqRJKQV.js → NeuralSuperRes-BekaecYg.js} +55 -120
- package/dist/PathTracer-Ccl9KOuF.js +29729 -0
- package/dist/PhysicalSky-Dv2PMFGW.js +1219 -0
- package/dist/RayzeeRenderer-BQxnzCTD.js +3542 -0
- package/dist/SolarLimb-ken-JbDK.js +721 -0
- package/dist/ToneMapGPU-BHi5JzIq.js +177 -0
- package/dist/ViewTransforms-D0rTLBsX.js +255 -0
- package/dist/WorkingMatrix-C6y233hT.js +27 -0
- package/dist/ZipReader-gDSWr0I5.js +98 -0
- package/dist/addons/archives.es.js +3 -0
- package/dist/addons/bidirectional.es.js +2 -0
- package/dist/addons/color.es.js +3 -0
- package/dist/addons/formats.es.js +2 -0
- package/dist/addons/physical-sky.es.js +2 -0
- package/dist/addons/storage.es.js +3 -0
- package/dist/archiveFormats-BZw7TiDp.js +75 -0
- package/dist/identity-DrQsGT2o.js +39 -0
- package/dist/openStorage-DwcBRUj7.js +820 -0
- package/dist/rayzee-core.es.js +12 -0
- package/dist/rayzee.es.js +2266 -44125
- package/dist/rayzee.umd.js +485 -473
- package/dist/sceneCachePolicy-BmwYOYDu.js +183 -0
- package/dist/shared-BGBcSxBQ.js +17 -0
- package/package.json +23 -2
- package/src/Color/ActiveColor.js +34 -0
- package/src/Color/BasicColor.js +147 -0
- package/src/Color/ColorManagement.js +7 -31
- package/src/EngineDefaults.js +23 -14
- package/src/EngineEvents.js +2 -0
- package/src/EngineIssues.js +1 -0
- package/src/PathTracerApp.js +617 -4016
- package/src/Pipeline/PipelineContext.js +0 -4
- package/src/Platform.js +36 -0
- package/src/Processor/ArchiveCache.js +1 -1
- package/src/Processor/ArchiveImporter.js +1259 -0
- package/src/Processor/AssetLoader.js +322 -1746
- package/src/Processor/AtmosphereModel.js +1 -2
- package/src/Processor/BVHBuilder.js +4 -95
- package/src/Processor/EnvironmentExactTable.js +187 -0
- package/src/Processor/EquirectHDRInfo.js +30 -223
- package/src/Processor/FileFormats.js +116 -0
- package/src/Processor/GLTFDecoders.js +85 -0
- package/src/Processor/GLTFDiffuseTransmission.js +63 -0
- package/src/Processor/GeometryExtractor.js +12 -1
- package/src/Processor/IESParser.js +47 -35
- package/src/Processor/KernelManager.js +35 -2
- package/src/Processor/MaterialPacking.js +8 -7
- package/src/Processor/PBRT/PBRTMaterials.js +129 -41
- package/src/Processor/PBRT/PBRTParser.js +30 -17
- package/src/Processor/PBRT/PBRTSceneBuilder.js +548 -37
- package/src/Processor/PBRT/PBRTTextureBake.js +154 -0
- package/src/Processor/PBRT/PFM.js +59 -0
- package/src/Processor/PackedRayBuffer.js +116 -9
- package/src/Processor/ParallelBVHBuilder.js +3 -6
- package/src/Processor/PhysicalSky.js +5 -4
- package/src/Processor/PlatformImageLoader.js +50 -6
- package/src/Processor/QueueManager.js +12 -3
- package/src/Processor/ReinsertionOptimizer.js +2 -2
- package/src/Processor/SceneProcessor.js +15 -55
- package/src/Processor/ShaderBuilder.js +6 -16
- package/src/Processor/SimpleSky.js +1 -62
- package/src/Processor/SolarLimb.js +3 -0
- package/src/Processor/TextureCreator.js +1 -1
- package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
- package/src/Processor/Workers/BVHWorker.js +5 -18
- package/src/Processor/Workers/CDFWorker.js +6 -183
- package/src/Processor/archiveFormats.js +7 -0
- package/src/Processor/utils.js +2 -1
- package/src/RayzeeRenderer.js +3902 -0
- package/src/RenderSettings.js +49 -41
- package/src/Stages/ASVGF.js +5 -7
- package/src/Stages/AutoExposure.js +3 -12
- package/src/Stages/Compositor.js +8 -23
- package/src/Stages/NRD.js +9 -2
- package/src/Stages/NormalDepth.js +9 -6
- package/src/Stages/PathTracer.js +254 -38
- package/src/Stages/PathTracerStage.js +11 -16
- package/src/Storage/BLASCache.js +1 -1
- package/src/Storage/CDFCache.js +18 -17
- package/src/Storage/DownloadCache.js +1 -1
- package/src/Storage/SceneGraphCodec.js +46 -3
- package/src/Storage/SpillStore.js +1 -1
- package/src/Storage/StorageManager.js +2 -4
- package/src/Storage/areas.js +4 -0
- package/src/TSL/BVHTraversal.js +62 -24
- package/src/TSL/Bidirectional.js +204 -0
- package/src/TSL/BidirectionalLamps.js +132 -0
- package/src/TSL/Clearcoat.js +15 -6
- package/src/TSL/Common.js +19 -14
- package/src/TSL/ConnectKernel.js +147 -0
- package/src/TSL/Debugger.js +1 -1
- package/src/TSL/Displacement.js +4 -3
- package/src/TSL/EmissiveSampling.js +64 -17
- package/src/TSL/Environment.js +77 -106
- package/src/TSL/EnvironmentCDF.js +85 -39
- package/src/TSL/ExtendKernel.js +8 -3
- package/src/TSL/FinalWriteKernel.js +4 -4
- package/src/TSL/GenerateKernel.js +18 -4
- package/src/TSL/HitFacet.js +35 -5
- package/src/TSL/LightBVHSampling.js +40 -9
- package/src/TSL/LightGenerateKernel.js +293 -0
- package/src/TSL/LightGuide.js +198 -0
- package/src/TSL/LightSplatKernel.js +165 -0
- package/src/TSL/LightsCore.js +13 -38
- package/src/TSL/LightsDirect.js +28 -29
- package/src/TSL/LightsIndirect.js +3 -1
- package/src/TSL/LightsSampling.js +90 -38
- package/src/TSL/MaterialEvaluation.js +113 -31
- package/src/TSL/MaterialProperties.js +44 -17
- package/src/TSL/MaterialTransmission.js +52 -47
- package/src/TSL/MergeKernel.js +218 -0
- package/src/TSL/PathTracerCore.js +65 -29
- package/src/TSL/Random.js +4 -0
- package/src/TSL/SceneResources.js +51 -0
- package/src/TSL/ShadeKernel.js +881 -103
- package/src/TSL/Struct.js +10 -0
- package/src/TSL/Sun.js +1 -1
- package/src/TSL/TextureSampling.js +65 -60
- package/src/addons/archives.js +14 -0
- package/src/addons/bidirectional.js +13 -0
- package/src/addons/color.js +32 -0
- package/src/addons/formats.js +16 -0
- package/src/addons/physicalSky.js +14 -0
- package/src/addons/storage.js +19 -0
- package/src/core.js +35 -0
- package/src/index.js +3 -1
- package/src/integrators/BidirectionalIntegrator.js +832 -0
- package/src/managers/EnvironmentManager.js +94 -91
- package/src/managers/MaterialDataManager.js +42 -0
- package/src/managers/UniformManager.js +0 -1
- package/dist/ToneMapGPU-DKQM1pMd.js +0 -552
- package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
- package/src/Processor/TreeletOptimizer.js +0 -574
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The analytic lights as bidirectional sources: the camera side's lamp choice, made as the path tracer
|
|
3
|
+
* makes it, and the densities a light path needs to weigh the strategies against it.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { float, int, If, Loop, select, cos, max, min } from 'three/tsl';
|
|
7
|
+
|
|
8
|
+
import { struct } from './patches.js';
|
|
9
|
+
import {
|
|
10
|
+
DirectionalLight, AreaLight, PointLight, SpotLight,
|
|
11
|
+
getDirectionalLight, getAreaLight, getPointLight, getSpotLight,
|
|
12
|
+
LIGHT_TYPE_DIRECTIONAL, LIGHT_TYPE_AREA, LIGHT_TYPE_POINT, sphQuadSolidAngle,
|
|
13
|
+
} from './LightsCore.js';
|
|
14
|
+
import {
|
|
15
|
+
calculateDirectionalLightImportance, estimateLightImportance, calculatePointLightImportance, calculateSpotLightImportance,
|
|
16
|
+
} from './LightsDirect.js';
|
|
17
|
+
import { SAMPLER_DIM_AUX_BASE } from './Random.js';
|
|
18
|
+
import { PI } from './Common.js';
|
|
19
|
+
|
|
20
|
+
export const LampPick = struct( { kind: 'int', index: 'int', pdf: 'float', total: 'float' } );
|
|
21
|
+
|
|
22
|
+
/** @param lamps { directional, numDirectional, area, numArea, point, numPoint, spot, numSpot } */
|
|
23
|
+
export const lampCount = ( lamps ) => lamps.numDirectional.add( lamps.numArea ).add( lamps.numPoint ).add( lamps.numSpot );
|
|
24
|
+
|
|
25
|
+
export const lampTypeAt = ( lamps, flat ) => select( flat.lessThan( lamps.numDirectional ), int( LIGHT_TYPE_DIRECTIONAL ),
|
|
26
|
+
select( flat.lessThan( lamps.numDirectional.add( lamps.numArea ) ), int( LIGHT_TYPE_AREA ),
|
|
27
|
+
select( flat.lessThan( lamps.numDirectional.add( lamps.numArea ).add( lamps.numPoint ) ), int( LIGHT_TYPE_POINT ), int( 3 ) ) ) );
|
|
28
|
+
|
|
29
|
+
const typeStart = ( lamps, type ) => select( type.equal( int( LIGHT_TYPE_DIRECTIONAL ) ), int( 0 ),
|
|
30
|
+
select( type.equal( int( LIGHT_TYPE_AREA ) ), lamps.numDirectional,
|
|
31
|
+
select( type.equal( int( LIGHT_TYPE_POINT ) ), lamps.numDirectional.add( lamps.numArea ),
|
|
32
|
+
lamps.numDirectional.add( lamps.numArea ).add( lamps.numPoint ) ) ) );
|
|
33
|
+
|
|
34
|
+
// The path tracer's importance (LightsDirect.js) without its bounce-depth factor, which a light path cannot know.
|
|
35
|
+
const walkLamps = ( lamps, hitPoint, N, material, visit ) => {
|
|
36
|
+
|
|
37
|
+
const each = ( type, count, read ) => If( count.greaterThan( int( 0 ) ), () => {
|
|
38
|
+
|
|
39
|
+
Loop( { start: int( 0 ), end: count, type: 'int', condition: '<' }, ( { i } ) => {
|
|
40
|
+
|
|
41
|
+
visit( int( type ), i, read( i ).toVar() );
|
|
42
|
+
|
|
43
|
+
} );
|
|
44
|
+
|
|
45
|
+
} );
|
|
46
|
+
each( LIGHT_TYPE_DIRECTIONAL, lamps.numDirectional, ( i ) =>
|
|
47
|
+
calculateDirectionalLightImportance( DirectionalLight.wrap( getDirectionalLight( lamps.directional, i ) ), N, material, int( 0 ) ) );
|
|
48
|
+
each( LIGHT_TYPE_AREA, lamps.numArea, ( i ) => {
|
|
49
|
+
|
|
50
|
+
const light = AreaLight.wrap( getAreaLight( lamps.area, i ) );
|
|
51
|
+
return select( light.intensity.greaterThan( 0.0 ), estimateLightImportance( light, hitPoint, N, material ), float( 0.0 ) );
|
|
52
|
+
|
|
53
|
+
} );
|
|
54
|
+
each( LIGHT_TYPE_POINT, lamps.numPoint, ( i ) =>
|
|
55
|
+
calculatePointLightImportance( PointLight.wrap( getPointLight( lamps.point, i ) ), hitPoint, N, material ) );
|
|
56
|
+
each( 3, lamps.numSpot, ( i ) =>
|
|
57
|
+
calculateSpotLightImportance( SpotLight.wrap( getSpotLight( lamps.spot, i ) ), hitPoint, N, material ) );
|
|
58
|
+
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* One lamp, as the path tracer's reservoir picks it, on the same dimensions; uniform when nothing has importance.
|
|
63
|
+
* `sample1D( dim )` draws a 1D variate, `u` is the fallback's.
|
|
64
|
+
*/
|
|
65
|
+
export function pickLamp( lamps, hitPoint, N, material, u, sample1D ) {
|
|
66
|
+
|
|
67
|
+
const type = int( - 1 ).toVar();
|
|
68
|
+
const index = int( - 1 ).toVar();
|
|
69
|
+
const chosen = float( 0.0 ).toVar();
|
|
70
|
+
const total = float( 0.0 ).toVar();
|
|
71
|
+
walkLamps( lamps, hitPoint, N, material, ( t, i, w ) => {
|
|
72
|
+
|
|
73
|
+
total.addAssign( w );
|
|
74
|
+
If( w.greaterThan( 0.0 ).and( sample1D( int( SAMPLER_DIM_AUX_BASE + 64 ).add( typeStart( lamps, t ) ).add( i ) ).mul( total ).lessThan( w ) ), () => {
|
|
75
|
+
|
|
76
|
+
type.assign( t );
|
|
77
|
+
index.assign( i );
|
|
78
|
+
chosen.assign( w );
|
|
79
|
+
|
|
80
|
+
} );
|
|
81
|
+
|
|
82
|
+
} );
|
|
83
|
+
|
|
84
|
+
const count = lampCount( lamps ).toVar();
|
|
85
|
+
const pdf = chosen.div( max( total, 1e-10 ) ).toVar();
|
|
86
|
+
If( total.lessThanEqual( 0.0 ), () => {
|
|
87
|
+
|
|
88
|
+
const flat = min( int( u.mul( float( count ) ) ), count.sub( int( 1 ) ) ).toVar();
|
|
89
|
+
type.assign( lampTypeAt( lamps, flat ) );
|
|
90
|
+
index.assign( flat.sub( typeStart( lamps, type ) ) );
|
|
91
|
+
pdf.assign( float( 1.0 ).div( max( float( count ), 1.0 ) ) );
|
|
92
|
+
|
|
93
|
+
} );
|
|
94
|
+
|
|
95
|
+
return LampPick( { kind: type, index, pdf, total } );
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The chance `pickLamp` at this point takes lamp (type, index). */
|
|
100
|
+
export function lampPickPdf( lamps, type, index, hitPoint, N, material ) {
|
|
101
|
+
|
|
102
|
+
const total = float( 0.0 ).toVar();
|
|
103
|
+
const own = float( 0.0 ).toVar();
|
|
104
|
+
walkLamps( lamps, hitPoint, N, material, ( t, i, w ) => {
|
|
105
|
+
|
|
106
|
+
total.addAssign( w );
|
|
107
|
+
If( t.equal( type ).and( i.equal( index ) ), () => {
|
|
108
|
+
|
|
109
|
+
own.assign( w );
|
|
110
|
+
|
|
111
|
+
} );
|
|
112
|
+
|
|
113
|
+
} );
|
|
114
|
+
return select( total.greaterThan( 0.0 ), own.div( total ), float( 1.0 ).div( max( float( lampCount( lamps ) ), 1.0 ) ) );
|
|
115
|
+
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Solid angle of a spot's cone, over which its light paths leave uniformly.
|
|
119
|
+
export const spotConeSolidAngle = ( light ) => float( 2 * PI ).mul( max( float( 1.0 ).sub( cos( light.angle ) ), 1e-10 ) );
|
|
120
|
+
|
|
121
|
+
// A soft directional light's cone (angle = angular diameter), as sampleDirectionalLight computes it.
|
|
122
|
+
export const directionalConeSolidAngle = ( light ) => float( 2 * PI ).mul( max( float( 1.0 ).sub( cos( light.angle.mul( 0.5 ) ) ), 1e-10 ) );
|
|
123
|
+
|
|
124
|
+
/** Area-light NEE's solid-angle density for any point of the light, seen from `from` (sampleRectAreaLight). */
|
|
125
|
+
export const areaLightDirectPdfW = ( light, from, dist, cosLight ) => {
|
|
126
|
+
|
|
127
|
+
const corner = light.position.sub( light.u ).sub( light.v );
|
|
128
|
+
const solidAngle = sphQuadSolidAngle( from, corner, light.u.mul( 2.0 ), light.v.mul( 2.0 ) ).toVar();
|
|
129
|
+
return select( light.shape.lessThan( 0.5 ).and( solidAngle.greaterThan( 1e-5 ) ), float( 1.0 ).div( solidAngle ),
|
|
130
|
+
dist.mul( dist ).div( max( light.area.mul( max( cosLight, 0.001 ) ), 1e-10 ) ) );
|
|
131
|
+
|
|
132
|
+
};
|
package/src/TSL/Clearcoat.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
reflect,
|
|
8
8
|
max,
|
|
9
9
|
If,
|
|
10
|
+
bool as tslBool,
|
|
10
11
|
} from 'three/tsl';
|
|
11
12
|
|
|
12
13
|
import { struct } from './patches.js';
|
|
@@ -15,15 +16,16 @@ import { AnisoFrame, DotProducts,
|
|
|
15
16
|
BRDFWeights,
|
|
16
17
|
} from './Struct.js';
|
|
17
18
|
import { MIN_CLEARCOAT_ROUGHNESS, computeDotProductsAniso, anisoTangentFrame, constructTBN } from './Common.js';
|
|
18
|
-
import { computeAnisoAlphas, calculateBRDFWeightsFromMaterial, calculateBSDFSamplingPDF } from './MaterialProperties.js';
|
|
19
|
+
import { computeAnisoAlphas, calculateBRDFWeightsFromMaterial, calculateBSDFSamplingPDF, diffuseTransmissionPdf } from './MaterialProperties.js';
|
|
19
20
|
import { ImportanceSampleCosine, sampleGGXVNDF, sampleGGXVNDFAniso } from './MaterialSampling.js';
|
|
20
|
-
import { evaluateMaterialResponseFromDots } from './MaterialEvaluation.js';
|
|
21
|
+
import { evaluateMaterialResponseFromDots, evaluateDiffuseTransmission } from './MaterialEvaluation.js';
|
|
21
22
|
import { getRandomSample1D } from './Random.js';
|
|
22
23
|
|
|
23
24
|
export const ClearcoatResult = struct( {
|
|
24
25
|
brdf: 'vec3',
|
|
25
26
|
L: 'vec3',
|
|
26
27
|
pdf: 'float',
|
|
28
|
+
isDiffuseTransmission: 'bool',
|
|
27
29
|
} );
|
|
28
30
|
|
|
29
31
|
// Improved clearcoat sampling function
|
|
@@ -55,6 +57,7 @@ export const sampleClearcoat = Fn( ( [
|
|
|
55
57
|
|
|
56
58
|
const L = vec3( 0.0 ).toVar();
|
|
57
59
|
const H = vec3( 0.0 ).toVar();
|
|
60
|
+
const transmitted = tslBool( false ).toVar();
|
|
58
61
|
|
|
59
62
|
If( rand.lessThan( clearcoatWeight ), () => {
|
|
60
63
|
|
|
@@ -83,6 +86,11 @@ export const sampleClearcoat = Fn( ( [
|
|
|
83
86
|
} );
|
|
84
87
|
L.assign( reflect( V.negate(), H ) );
|
|
85
88
|
|
|
89
|
+
} ).ElseIf( rand.lessThan( clearcoatWeight.add( specularWeight ).add( weights.diffuseTransmission ) ), () => {
|
|
90
|
+
|
|
91
|
+
L.assign( ImportanceSampleCosine( { N: N.negate(), xi: randomSample } ) );
|
|
92
|
+
transmitted.assign( true );
|
|
93
|
+
|
|
86
94
|
} ).Else( () => {
|
|
87
95
|
|
|
88
96
|
// Sample diffuse
|
|
@@ -94,16 +102,17 @@ export const sampleClearcoat = Fn( ( [
|
|
|
94
102
|
// Calculate dot products (aniso-aware: also projects onto the anisotropy frame)
|
|
95
103
|
const dots = DotProducts.wrap( computeDotProductsAniso( N, V, L, material ) );
|
|
96
104
|
|
|
97
|
-
// One density for every MIS site — see calculateBSDFSamplingPDF.
|
|
98
|
-
|
|
105
|
+
// One density for every MIS site — see calculateBSDFSamplingPDF. Below the surface only the
|
|
106
|
+
// diffuse transmission lobe draws.
|
|
107
|
+
const pdf = max( transmitted.select( diffuseTransmissionPdf( weights.diffuseTransmission, dot( N, L ).negate() ), calculateBSDFSamplingPDF( material, weights, dots ) ), 0.001 );
|
|
99
108
|
|
|
100
109
|
// Evaluate complete BRDF
|
|
101
110
|
// The same BRDF every other site evaluates — see the clearcoat note in MaterialEvaluation.
|
|
102
|
-
const brdf = evaluateMaterialResponseFromDots( material, dots );
|
|
111
|
+
const brdf = transmitted.select( evaluateDiffuseTransmission( material, max( dot( N, V ), 0.001 ) ), evaluateMaterialResponseFromDots( material, dots ) );
|
|
103
112
|
|
|
104
113
|
// Return brdf, L direction, and pdf packed together
|
|
105
114
|
// Caller needs L and pdf - return as struct-like output
|
|
106
115
|
// We pack: result.xyz = brdf, result.w = pdf, L stored in separate output
|
|
107
|
-
return ClearcoatResult( { brdf, L, pdf } );
|
|
116
|
+
return ClearcoatResult( { brdf, L, pdf, isDiffuseTransmission: transmitted } );
|
|
108
117
|
|
|
109
118
|
} );
|
package/src/TSL/Common.js
CHANGED
|
@@ -432,6 +432,13 @@ export const getMaterial = Fn( ( [ materialIndex, materialBuffer ] ) => {
|
|
|
432
432
|
const data30 = getDatafromStorageBuffer( materialBuffer, materialIndex, int( S.EXT_MAP_INDICES_A ), int( MATERIAL_SLOTS ) ).toVar();
|
|
433
433
|
const data31 = getDatafromStorageBuffer( materialBuffer, materialIndex, int( S.EXT_MAP_INDICES_B ), int( MATERIAL_SLOTS ) ).toVar();
|
|
434
434
|
const data32 = getDatafromStorageBuffer( materialBuffer, materialIndex, int( S.EXT_MAP_INDICES_C ), int( MATERIAL_SLOTS ) ).toVar();
|
|
435
|
+
// Only a material with diffuse transmission has maps for it.
|
|
436
|
+
const data33 = vec4( - 1.0, - 1.0, 0.0, 0.0 ).toVar();
|
|
437
|
+
If( data5.a.greaterThan( 0.0 ), () => {
|
|
438
|
+
|
|
439
|
+
data33.assign( getDatafromStorageBuffer( materialBuffer, materialIndex, int( S.EXT_MAP_INDICES_D ), int( MATERIAL_SLOTS ) ) );
|
|
440
|
+
|
|
441
|
+
} );
|
|
435
442
|
|
|
436
443
|
return RayTracingMaterial( {
|
|
437
444
|
color: vec4( data0.rgb, 1.0 ),
|
|
@@ -470,6 +477,10 @@ export const getMaterial = Fn( ( [ materialIndex, materialBuffer ] ) => {
|
|
|
470
477
|
iridescenceThicknessMapIndex: int( data31.b ),
|
|
471
478
|
specularIntensityMapIndex: int( data31.a ),
|
|
472
479
|
specularColorMapIndex: int( data32.r ),
|
|
480
|
+
diffuseTransmission: data5.a,
|
|
481
|
+
diffuseTransmissionColor: data32.gba,
|
|
482
|
+
diffuseTransmissionMapIndex: int( data33.r ),
|
|
483
|
+
diffuseTransmissionColorMapIndex: int( data33.g ),
|
|
473
484
|
albedoMapIndex: int( data8.r ),
|
|
474
485
|
normalMapIndex: int( data8.g ),
|
|
475
486
|
roughnessMapIndex: int( data8.b ),
|
|
@@ -569,6 +580,10 @@ export const diffuseGroundMaterial = Fn( () => {
|
|
|
569
580
|
iridescenceThicknessMapIndex: int( - 1 ),
|
|
570
581
|
specularIntensityMapIndex: int( - 1 ),
|
|
571
582
|
specularColorMapIndex: int( - 1 ),
|
|
583
|
+
diffuseTransmission: float( 0.0 ),
|
|
584
|
+
diffuseTransmissionColor: vec3( 1.0 ),
|
|
585
|
+
diffuseTransmissionMapIndex: int( - 1 ),
|
|
586
|
+
diffuseTransmissionColorMapIndex: int( - 1 ),
|
|
572
587
|
} );
|
|
573
588
|
|
|
574
589
|
} );
|
|
@@ -612,22 +627,12 @@ export const getShadowMaterial = Fn( ( [ materialIndex, materialBuffer ] ) => {
|
|
|
612
627
|
* rides along in the rest — no second binding, which matters on a backend that
|
|
613
628
|
* allows ten storage buffers per stage.
|
|
614
629
|
*/
|
|
615
|
-
//
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
export function setAlphaShadowsUniform( node ) {
|
|
619
|
-
|
|
620
|
-
_enableAlphaShadows = node;
|
|
621
|
-
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
export const getAlphaShadowsUniform = () => _enableAlphaShadows;
|
|
625
|
-
|
|
626
|
-
// Does a hit on a triangle with these flags settle the shadow ray without a material fetch?
|
|
627
|
-
export const shadowFlagsSettle = ( flags ) => {
|
|
630
|
+
// Does a hit on a triangle with these flags settle the shadow ray without a material fetch? `alphaShadows` is the
|
|
631
|
+
// kernel's switch (SceneResources); null means alpha is never tested.
|
|
632
|
+
export const shadowFlagsSettle = ( flags, alphaShadows ) => {
|
|
628
633
|
|
|
629
634
|
const bit = ( shift ) => flags.shiftRight( uint( shift ) ).bitAnd( uint( 1 ) ).equal( uint( 1 ) );
|
|
630
|
-
const alphaOff =
|
|
635
|
+
const alphaOff = alphaShadows ? alphaShadows.equal( int( 0 ) ) : tslBool( true );
|
|
631
636
|
return bit( TRI_BLOCKER_SHIFT ).or( bit( TRI_BLOCKER_ALPHA_SHIFT ).and( alphaOff ) );
|
|
632
637
|
|
|
633
638
|
};
|
|
@@ -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,
|
|
@@ -35,8 +35,10 @@ import {
|
|
|
35
35
|
import { TRI_MATERIAL_MASK } from '../EngineDefaults.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(
|