rayzee 7.20.2 → 7.21.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/dist/rayzee.es.js +3855 -3065
- package/dist/rayzee.umd.js +83 -109
- package/package.json +1 -1
- package/src/EngineDefaults.js +4 -3
- package/src/PathTracerApp.js +7 -0
- package/src/Pipeline/BindingAudit.js +178 -0
- package/src/Pipeline/RenderPipeline.js +5 -2
- package/src/Processor/CameraOptimizer.js +0 -5
- package/src/Processor/PackedRayBuffer.js +33 -8
- package/src/Stages/ASVGF.js +10 -10
- package/src/Stages/BilateralFilter.js +70 -11
- package/src/Stages/EdgeFilter.js +44 -6
- package/src/Stages/NormalDepth.js +10 -2
- package/src/Stages/PathTracer.js +7 -3
- package/src/Stages/PathTracerStage.js +15 -46
- package/src/TSL/Clearcoat.js +29 -40
- package/src/TSL/Common.js +1 -47
- package/src/TSL/EmissiveSampling.js +47 -50
- package/src/TSL/LightBVHSampling.js +16 -12
- package/src/TSL/LightsCore.js +3 -2
- package/src/TSL/LightsDirect.js +4 -4
- package/src/TSL/LightsIndirect.js +36 -355
- package/src/TSL/LightsSampling.js +153 -386
- package/src/TSL/MaterialEvaluation.js +44 -71
- package/src/TSL/MaterialProperties.js +209 -176
- package/src/TSL/MaterialTransmission.js +17 -32
- package/src/TSL/PathTracerCore.js +51 -66
- package/src/TSL/Random.js +202 -132
- package/src/TSL/ShadeKernel.js +89 -56
- package/src/TSL/Struct.js +6 -38
- package/src/TSL/Subsurface.js +13 -8
- package/src/index.js +5 -0
- package/src/managers/EnvironmentManager.js +0 -5
- package/src/managers/UniformManager.js +4 -1
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
Fn,
|
|
6
|
-
vec2,
|
|
7
6
|
vec3,
|
|
8
7
|
float,
|
|
9
8
|
int,
|
|
@@ -21,7 +20,7 @@ import {
|
|
|
21
20
|
length,
|
|
22
21
|
} from 'three/tsl';
|
|
23
22
|
import { MIN_PDF } from './Common.js';
|
|
24
|
-
import {
|
|
23
|
+
import { getRandomSample1D, getRandomSample2D } from './Random.js';
|
|
25
24
|
import {
|
|
26
25
|
EmissiveSample,
|
|
27
26
|
sampleTriangle,
|
|
@@ -107,6 +106,7 @@ export const lbvhNodeImportance = Fn( ( [ nMin, power, nMax, coneAxis, cosThetaO
|
|
|
107
106
|
export const sampleLightBVHTriangle = Fn( ( [
|
|
108
107
|
hitPoint, surfaceNormal,
|
|
109
108
|
rngState,
|
|
109
|
+
pixelCoord, resolution, frame, dimBase,
|
|
110
110
|
lbvhBuffer,
|
|
111
111
|
emissiveTriangleBuffer,
|
|
112
112
|
emissiveVec4Offset,
|
|
@@ -130,6 +130,13 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
130
130
|
const nodeIndex = int( 0 ).toVar();
|
|
131
131
|
const foundLeaf = tslBool( false ).toVar();
|
|
132
132
|
|
|
133
|
+
// Hierarchical sample warping: ONE variate recycled down the descent and into the leaf scan,
|
|
134
|
+
// rescaled to [0,1) after each choice. A per-level draw has no fixed dimension to key on
|
|
135
|
+
// (depth varies per pixel), so the descent — which dominates emissive-NEE variance — would
|
|
136
|
+
// otherwise stay white-noise however well the point-on-triangle pair is sampled. Unbiased:
|
|
137
|
+
// the rescaled value is uniform conditional on the branch taken; selectionPdf is unchanged.
|
|
138
|
+
const u = getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 1 ) ), rngState, resolution, frame ).toVar();
|
|
139
|
+
|
|
133
140
|
// Tree descent: at most MAX_LBVH_DEPTH iterations
|
|
134
141
|
Loop( MAX_LBVH_DEPTH, () => {
|
|
135
142
|
|
|
@@ -179,19 +186,19 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
179
186
|
// Probability of choosing left child
|
|
180
187
|
const pLeft = lImportance.div( totalImportance );
|
|
181
188
|
|
|
182
|
-
|
|
183
|
-
const rand = RandomValue( rngState );
|
|
184
|
-
|
|
185
|
-
If( rand.lessThan( pLeft ), () => {
|
|
189
|
+
If( u.lessThan( pLeft ), () => {
|
|
186
190
|
|
|
187
191
|
// Choose left child
|
|
192
|
+
u.assign( clamp( u.div( max( pLeft, float( 1e-6 ) ) ), 0.0, 0.999999 ) );
|
|
188
193
|
selectionPdf.mulAssign( pLeft );
|
|
189
194
|
nodeIndex.assign( leftChildIdx );
|
|
190
195
|
|
|
191
196
|
} ).Else( () => {
|
|
192
197
|
|
|
193
198
|
// Choose right child
|
|
194
|
-
|
|
199
|
+
const pRight = float( 1.0 ).sub( pLeft ).toVar();
|
|
200
|
+
u.assign( clamp( u.sub( pLeft ).div( max( pRight, float( 1e-6 ) ) ), 0.0, 0.999999 ) );
|
|
201
|
+
selectionPdf.mulAssign( pRight );
|
|
195
202
|
nodeIndex.assign( rightChildIdx );
|
|
196
203
|
|
|
197
204
|
} );
|
|
@@ -213,7 +220,7 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
213
220
|
|
|
214
221
|
// Sample one triangle proportional to power within the leaf
|
|
215
222
|
// Linear scan: pick random threshold against cumulative power sum
|
|
216
|
-
const randLeaf =
|
|
223
|
+
const randLeaf = u.mul( leafTotalPower );
|
|
217
224
|
const cumPower = float( 0.0 ).toVar();
|
|
218
225
|
|
|
219
226
|
// Default to last entry as fallback
|
|
@@ -253,10 +260,7 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
253
260
|
// Fetch triangle geometry
|
|
254
261
|
const triData = TriangleData.wrap( fetchTriangleData( triangleIndex, triangleBuffer ) );
|
|
255
262
|
|
|
256
|
-
|
|
257
|
-
const xi_r1 = RandomValue( rngState ).toVar();
|
|
258
|
-
const xi_r2 = RandomValue( rngState ).toVar();
|
|
259
|
-
const xi = vec2( xi_r1, xi_r2 );
|
|
263
|
+
const xi = getRandomSample2D( pixelCoord, int( 0 ), dimBase.add( int( 3 ) ), rngState, resolution, frame ).toVar();
|
|
260
264
|
|
|
261
265
|
const geoNormal = normalize( cross( triData.v1.sub( triData.v0 ), triData.v2.sub( triData.v0 ) ) );
|
|
262
266
|
|
package/src/TSL/LightsCore.js
CHANGED
|
@@ -83,13 +83,14 @@ export const LightSample = struct( {
|
|
|
83
83
|
distance: 'float',
|
|
84
84
|
lightType: 'int',
|
|
85
85
|
valid: 'bool',
|
|
86
|
+
// Reservoir denominator, carried out so the BSDF-hit MIS partner can rebuild the
|
|
87
|
+
// selection pdf without walking every light buffer a second time.
|
|
88
|
+
selectionTotalWeight: 'float',
|
|
86
89
|
} );
|
|
87
90
|
|
|
88
91
|
export const IndirectLightingResult = struct( {
|
|
89
92
|
direction: 'vec3', // Sampled direction for next bounce
|
|
90
93
|
throughput: 'vec3', // Light throughput along this path
|
|
91
|
-
misWeight: 'float', // MIS weight for this sample
|
|
92
|
-
pdf: 'float', // PDF of the selected strategy
|
|
93
94
|
combinedPdf: 'float', // Weighted sum of all strategy PDFs (for NEE↔implicit MIS pairing)
|
|
94
95
|
} );
|
|
95
96
|
|
package/src/TSL/LightsDirect.js
CHANGED
|
@@ -74,7 +74,7 @@ export const traceShadowRay = Fn( ( [
|
|
|
74
74
|
const rayOrigin = origin.toVar();
|
|
75
75
|
const remainingDist = float( maxDist ).toVar();
|
|
76
76
|
|
|
77
|
-
const MAX_SHADOW_TRANSMISSIONS =
|
|
77
|
+
const MAX_SHADOW_TRANSMISSIONS = 16;
|
|
78
78
|
|
|
79
79
|
Loop( { start: int( 0 ), end: int( MAX_SHADOW_TRANSMISSIONS ) }, () => {
|
|
80
80
|
|
|
@@ -160,7 +160,7 @@ export const traceShadowRay = Fn( ( [
|
|
|
160
160
|
const blendAlpha = clamp( shadowMaterial.color.a.mul( shadowMaterial.opacity ).mul( texAlpha ), 0.0, 1.0 );
|
|
161
161
|
transmittance.mulAssign( float( 1.0 ).sub( blendAlpha ) );
|
|
162
162
|
|
|
163
|
-
If( transmittance.lessThan(
|
|
163
|
+
If( transmittance.lessThan( 1e-4 ), () => {
|
|
164
164
|
|
|
165
165
|
transmittance.assign( 0.0 );
|
|
166
166
|
Break();
|
|
@@ -221,7 +221,7 @@ export const traceShadowRay = Fn( ( [
|
|
|
221
221
|
transmittance.mulAssign( matTransmittance );
|
|
222
222
|
|
|
223
223
|
// Early exit if almost no light passes through
|
|
224
|
-
If( transmittance.lessThan(
|
|
224
|
+
If( transmittance.lessThan( 1e-4 ), () => {
|
|
225
225
|
|
|
226
226
|
transmittance.assign( 0.0 );
|
|
227
227
|
Break();
|
|
@@ -237,7 +237,7 @@ export const traceShadowRay = Fn( ( [
|
|
|
237
237
|
// Handle transparent materials
|
|
238
238
|
transmittance.mulAssign( float( 1.0 ).sub( shadowMaterial.opacity ) );
|
|
239
239
|
|
|
240
|
-
If( transmittance.lessThan(
|
|
240
|
+
If( transmittance.lessThan( 1e-4 ), () => {
|
|
241
241
|
|
|
242
242
|
transmittance.assign( 0.0 );
|
|
243
243
|
Break();
|
|
@@ -7,398 +7,79 @@
|
|
|
7
7
|
* strategy — it is handled via deterministic NEE in LightsSampling.js.
|
|
8
8
|
*
|
|
9
9
|
* Contains:
|
|
10
|
-
* - calculateTransmissionPDF — transmission PDF for MIS
|
|
11
|
-
* - calculateClearcoatPDF — clearcoat PDF for MIS
|
|
12
|
-
* - computeSamplingInfo — compute importance weights for material strategies
|
|
13
|
-
* - selectSamplingStrategy — CDF-based strategy selection (specular, diffuse, transmission, clearcoat)
|
|
14
10
|
* - calculateIndirectLighting — material multi-strategy MIS for bounce direction
|
|
15
11
|
*/
|
|
16
12
|
|
|
17
|
-
import {
|
|
18
|
-
Fn,
|
|
19
|
-
float,
|
|
20
|
-
vec2,
|
|
21
|
-
vec3,
|
|
22
|
-
int,
|
|
23
|
-
bool as tslBool,
|
|
24
|
-
max,
|
|
25
|
-
abs,
|
|
26
|
-
sqrt,
|
|
27
|
-
dot,
|
|
28
|
-
If,
|
|
29
|
-
select,
|
|
30
|
-
} from 'three/tsl';
|
|
13
|
+
import { Fn, float, vec3, int, max, dot, If, select } from 'three/tsl';
|
|
31
14
|
|
|
32
|
-
import {
|
|
33
|
-
|
|
34
|
-
} from './
|
|
35
|
-
import {
|
|
36
|
-
SamplingStrategyWeights,
|
|
37
|
-
} from './Struct.js';
|
|
38
|
-
import {
|
|
39
|
-
PI_INV,
|
|
40
|
-
EPSILON,
|
|
41
|
-
MIN_PDF,
|
|
42
|
-
} from './Common.js';
|
|
43
|
-
import { DistributionGGX, calculateVNDFPDF, specularVNDFPDFForDir } from './MaterialProperties.js';
|
|
44
|
-
import { evaluateMaterialResponse } from './MaterialEvaluation.js';
|
|
45
|
-
import { RandomValue } from './Random.js';
|
|
46
|
-
import { sampleMicrofacetTransmission, MicrofacetTransmissionResult } from './MaterialTransmission.js';
|
|
15
|
+
import { IndirectLightingResult } from './LightsCore.js';
|
|
16
|
+
import { MIN_PDF } from './Common.js';
|
|
17
|
+
import { getRandomSample2D } from './Random.js';
|
|
47
18
|
import { cosineWeightedSample } from './MaterialSampling.js';
|
|
48
19
|
|
|
49
|
-
// =============================================================================
|
|
50
|
-
// PDF Calculation Helpers
|
|
51
|
-
// =============================================================================
|
|
52
|
-
|
|
53
|
-
// Transmission PDF (Walter et al. 2007)
|
|
54
|
-
export const calculateTransmissionPDF = Fn( ( [ V, L, N, ior, roughness, entering ] ) => {
|
|
55
|
-
|
|
56
|
-
// eta is the relative IOR: eta_transmitted / eta_incident
|
|
57
|
-
// When entering: air(1.0) -> material(ior), so eta = ior
|
|
58
|
-
// When exiting: material(ior) -> air(1.0), so eta = 1.0/ior
|
|
59
|
-
const eta = select( entering, ior, float( 1.0 ).div( ior ) ).toVar();
|
|
60
|
-
|
|
61
|
-
// Transmission half-vector formula
|
|
62
|
-
const H_raw = V.add( L.mul( eta ) ).toVar();
|
|
63
|
-
const lenSq = dot( H_raw, H_raw ).toVar();
|
|
64
|
-
const H = select( lenSq.greaterThan( EPSILON ), H_raw.div( sqrt( lenSq ) ), N ).toVar();
|
|
65
|
-
|
|
66
|
-
// Ensure H points into the correct hemisphere
|
|
67
|
-
If( dot( H, N ).lessThan( 0.0 ), () => {
|
|
68
|
-
|
|
69
|
-
H.assign( H.negate() );
|
|
70
|
-
|
|
71
|
-
} );
|
|
72
|
-
|
|
73
|
-
const VoH = abs( dot( V, H ) );
|
|
74
|
-
const LoH = abs( dot( L, H ) ).toVar();
|
|
75
|
-
const NoH = abs( dot( N, H ) ).toVar();
|
|
76
|
-
|
|
77
|
-
// GGX distribution
|
|
78
|
-
const D = DistributionGGX( NoH, roughness );
|
|
79
|
-
|
|
80
|
-
// Jacobian for transmission
|
|
81
|
-
const denom_inner = VoH.add( LoH.mul( eta ) ).toVar();
|
|
82
|
-
const denom = denom_inner.mul( denom_inner );
|
|
83
|
-
const jacobian = LoH.mul( eta ).mul( eta ).div( max( denom, EPSILON ) );
|
|
84
|
-
|
|
85
|
-
return D.mul( NoH ).mul( jacobian );
|
|
86
|
-
|
|
87
|
-
} );
|
|
88
|
-
|
|
89
|
-
// Clearcoat PDF
|
|
90
|
-
export const calculateClearcoatPDF = Fn( ( [ V, L, N, clearcoatRoughness ] ) => {
|
|
91
|
-
|
|
92
|
-
const H_raw = V.add( L ).toVar();
|
|
93
|
-
const lenSq = dot( H_raw, H_raw ).toVar();
|
|
94
|
-
const H = select( lenSq.greaterThan( EPSILON ), H_raw.div( sqrt( lenSq ) ), N );
|
|
95
|
-
|
|
96
|
-
const NoH = max( dot( N, H ), 0.0 );
|
|
97
|
-
const NoV = max( dot( N, V ), 0.0 );
|
|
98
|
-
|
|
99
|
-
return calculateVNDFPDF( NoH, NoV, clearcoatRoughness );
|
|
100
|
-
|
|
101
|
-
} );
|
|
102
|
-
|
|
103
|
-
// =============================================================================
|
|
104
|
-
// Sampling Strategy Computation
|
|
105
|
-
// =============================================================================
|
|
106
|
-
|
|
107
|
-
// Compute normalized strategy weights based on importance info
|
|
108
|
-
export const computeSamplingInfo = Fn( ( [
|
|
109
|
-
samplingInfo,
|
|
110
|
-
] ) => {
|
|
111
|
-
|
|
112
|
-
// Material-only strategies (env handled via deterministic NEE in direct lighting)
|
|
113
|
-
const specularW = samplingInfo.specularImportance.toVar();
|
|
114
|
-
const useSpecular = specularW.greaterThan( 0.001 ).toVar();
|
|
115
|
-
|
|
116
|
-
const diffuseW = samplingInfo.diffuseImportance.toVar();
|
|
117
|
-
const useDiffuse = diffuseW.greaterThan( 0.001 ).toVar();
|
|
118
|
-
|
|
119
|
-
const transmissionW = samplingInfo.transmissionImportance.toVar();
|
|
120
|
-
const useTransmission = transmissionW.greaterThan( 0.001 ).toVar();
|
|
121
|
-
|
|
122
|
-
const clearcoatW = samplingInfo.clearcoatImportance.toVar();
|
|
123
|
-
const useClearcoat = clearcoatW.greaterThan( 0.001 ).toVar();
|
|
124
|
-
|
|
125
|
-
const totalW = specularW.add( diffuseW ).add( transmissionW ).add( clearcoatW ).toVar();
|
|
126
|
-
|
|
127
|
-
If( totalW.lessThan( 0.001 ), () => {
|
|
128
|
-
|
|
129
|
-
specularW.assign( 0.0 );
|
|
130
|
-
diffuseW.assign( 1.0 );
|
|
131
|
-
transmissionW.assign( 0.0 );
|
|
132
|
-
clearcoatW.assign( 0.0 );
|
|
133
|
-
totalW.assign( 1.0 );
|
|
134
|
-
useSpecular.assign( tslBool( false ) );
|
|
135
|
-
useDiffuse.assign( tslBool( true ) );
|
|
136
|
-
useTransmission.assign( tslBool( false ) );
|
|
137
|
-
useClearcoat.assign( tslBool( false ) );
|
|
138
|
-
|
|
139
|
-
} ).Else( () => {
|
|
140
|
-
|
|
141
|
-
const invTotal = float( 1.0 ).div( totalW ).toVar();
|
|
142
|
-
specularW.mulAssign( invTotal );
|
|
143
|
-
diffuseW.mulAssign( invTotal );
|
|
144
|
-
transmissionW.mulAssign( invTotal );
|
|
145
|
-
clearcoatW.mulAssign( invTotal );
|
|
146
|
-
totalW.assign( 1.0 );
|
|
147
|
-
|
|
148
|
-
} );
|
|
149
|
-
|
|
150
|
-
return SamplingStrategyWeights( {
|
|
151
|
-
envWeight: float( 0.0 ),
|
|
152
|
-
specularWeight: specularW,
|
|
153
|
-
diffuseWeight: diffuseW,
|
|
154
|
-
transmissionWeight: transmissionW,
|
|
155
|
-
clearcoatWeight: clearcoatW,
|
|
156
|
-
totalWeight: totalW,
|
|
157
|
-
useEnv: tslBool( false ),
|
|
158
|
-
useSpecular,
|
|
159
|
-
useDiffuse,
|
|
160
|
-
useTransmission,
|
|
161
|
-
useClearcoat,
|
|
162
|
-
} );
|
|
163
|
-
|
|
164
|
-
} );
|
|
165
|
-
|
|
166
|
-
// =============================================================================
|
|
167
|
-
// Strategy Selection via Cumulative Distribution
|
|
168
|
-
// =============================================================================
|
|
169
|
-
|
|
170
|
-
// Strategy IDs: 1=specular, 2=diffuse, 3=transmission, 4=clearcoat
|
|
171
|
-
// (env removed — handled via deterministic NEE in direct lighting)
|
|
172
|
-
export const selectSamplingStrategy = Fn( ( [ weights, randomValue ] ) => {
|
|
173
|
-
|
|
174
|
-
const selectedStrategy = int( 2 ).toVar(); // Default: diffuse
|
|
175
|
-
|
|
176
|
-
const cumulative = float( 0.0 ).toVar();
|
|
177
|
-
const found = tslBool( false ).toVar();
|
|
178
|
-
|
|
179
|
-
If( weights.useSpecular.and( found.not() ), () => {
|
|
180
|
-
|
|
181
|
-
cumulative.addAssign( weights.specularWeight );
|
|
182
|
-
|
|
183
|
-
If( randomValue.lessThan( cumulative ), () => {
|
|
184
|
-
|
|
185
|
-
selectedStrategy.assign( 1 );
|
|
186
|
-
found.assign( tslBool( true ) );
|
|
187
|
-
|
|
188
|
-
} );
|
|
189
|
-
|
|
190
|
-
} );
|
|
191
|
-
|
|
192
|
-
If( weights.useDiffuse.and( found.not() ), () => {
|
|
193
20
|
|
|
194
|
-
cumulative.addAssign( weights.diffuseWeight );
|
|
195
21
|
|
|
196
|
-
If( randomValue.lessThan( cumulative ), () => {
|
|
197
22
|
|
|
198
|
-
selectedStrategy.assign( 2 );
|
|
199
|
-
found.assign( tslBool( true ) );
|
|
200
23
|
|
|
201
|
-
} );
|
|
202
|
-
|
|
203
|
-
} );
|
|
204
|
-
|
|
205
|
-
If( weights.useTransmission.and( found.not() ), () => {
|
|
206
|
-
|
|
207
|
-
cumulative.addAssign( weights.transmissionWeight );
|
|
208
|
-
|
|
209
|
-
If( randomValue.lessThan( cumulative ), () => {
|
|
210
|
-
|
|
211
|
-
selectedStrategy.assign( 3 );
|
|
212
|
-
found.assign( tslBool( true ) );
|
|
213
|
-
|
|
214
|
-
} );
|
|
215
|
-
|
|
216
|
-
} );
|
|
217
|
-
|
|
218
|
-
If( weights.useClearcoat.and( found.not() ), () => {
|
|
219
|
-
|
|
220
|
-
selectedStrategy.assign( 4 );
|
|
221
|
-
|
|
222
|
-
} );
|
|
223
|
-
|
|
224
|
-
return selectedStrategy;
|
|
225
|
-
|
|
226
|
-
} );
|
|
227
|
-
|
|
228
|
-
// =============================================================================
|
|
229
|
-
// Cosine Weighted PDF helper
|
|
230
|
-
// =============================================================================
|
|
231
|
-
|
|
232
|
-
const cosineWeightedPDF = Fn( ( [ NoL ] ) => {
|
|
233
|
-
|
|
234
|
-
return max( NoL, 0.0 ).mul( PI_INV );
|
|
235
|
-
|
|
236
|
-
} );
|
|
237
24
|
|
|
238
25
|
// =============================================================================
|
|
239
26
|
// Indirect Lighting Calculation
|
|
240
27
|
// =============================================================================
|
|
241
28
|
|
|
242
29
|
export const calculateIndirectLighting = Fn( ( [
|
|
243
|
-
|
|
30
|
+
N, material,
|
|
244
31
|
// brdfSample fields (DirectionSample)
|
|
245
32
|
brdfSampleDirection, brdfSamplePdf, brdfSampleValue,
|
|
33
|
+
// DirectionSample carries these so refraction throughput does not need a second sampler run.
|
|
34
|
+
isTransmission, brdfSampleColorWeight,
|
|
246
35
|
rngState,
|
|
247
|
-
|
|
36
|
+
pixelCoord, resolution, frame, dimBase,
|
|
248
37
|
] ) => {
|
|
249
38
|
|
|
250
39
|
// Initialize result
|
|
251
40
|
const r_direction = vec3( 0.0 ).toVar();
|
|
252
41
|
const r_throughput = vec3( 0.0 ).toVar();
|
|
253
|
-
const r_misWeight = float( 0.0 ).toVar();
|
|
254
|
-
const r_pdf = float( 0.0 ).toVar();
|
|
255
42
|
const r_combinedPdf = float( 0.0 ).toVar();
|
|
256
43
|
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
.and( samplingInfo.transmissionImportance.greaterThanEqual( 0.0 ) )
|
|
261
|
-
.and( samplingInfo.clearcoatImportance.greaterThanEqual( 0.0 ) );
|
|
44
|
+
// A non-finite pdf would poison the accumulation buffer permanently, so fall back to a
|
|
45
|
+
// cosine bounce rather than dividing by it.
|
|
46
|
+
If( brdfSamplePdf.greaterThan( 0.0 ).not(), () => {
|
|
262
47
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
// Fallback to diffuse sampling
|
|
266
|
-
const r1_fb = RandomValue( rngState ).toVar();
|
|
267
|
-
const r2_fb = RandomValue( rngState ).toVar();
|
|
268
|
-
const sampleRand = vec2( r1_fb, r2_fb );
|
|
48
|
+
const sampleRand = getRandomSample2D( pixelCoord, int( 0 ), dimBase.add( int( 8 ) ), rngState, resolution, frame );
|
|
269
49
|
r_direction.assign( cosineWeightedSample( N, sampleRand ) );
|
|
270
50
|
r_throughput.assign( material.color.xyz );
|
|
271
|
-
|
|
272
|
-
r_pdf.assign( 1.0 );
|
|
51
|
+
r_combinedPdf.assign( 1.0 );
|
|
273
52
|
|
|
274
53
|
} ).Else( () => {
|
|
275
54
|
|
|
276
|
-
//
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
//
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
55
|
+
// generateSampledDirection IS the sampler — this used to re-select a strategy on top of
|
|
56
|
+
// it, and its strategies 1 and 4 both just returned brdfSampleDirection. So a sample that
|
|
57
|
+
// the inner selection had drawn from the diffuse or sheen lobe was reported to MIS as a
|
|
58
|
+
// specular-VNDF draw, making combinedPdf something no strategy actually sampled from.
|
|
59
|
+
// Since prevBouncePdf is that value, and the NEE sites evaluate a mixture, the two MIS
|
|
60
|
+
// strategies' weights stopped summing to 1.
|
|
61
|
+
//
|
|
62
|
+
// brdfSamplePdf is now the true mixture density (calculateBSDFSamplingPDF), so the
|
|
63
|
+
// estimator is just f·NoL/pdf and the MIS pairs share one function by construction.
|
|
64
|
+
const NoL = max( dot( N, brdfSampleDirection ), 0.0 ).toVar();
|
|
65
|
+
const pdf = max( brdfSamplePdf, MIN_PDF ).toVar();
|
|
66
|
+
|
|
67
|
+
r_direction.assign( brdfSampleDirection );
|
|
68
|
+
r_combinedPdf.assign( pdf );
|
|
69
|
+
|
|
70
|
+
// The reflection BRDF eval is invalid below the surface, so refraction carries the
|
|
71
|
+
// sampler's own tint (material.color × colorWeight), mirroring handleTransmission.
|
|
72
|
+
r_throughput.assign( select(
|
|
73
|
+
isTransmission,
|
|
74
|
+
material.color.xyz.mul( brdfSampleColorWeight ),
|
|
75
|
+
brdfSampleValue.mul( NoL ).div( pdf ),
|
|
76
|
+
) );
|
|
298
77
|
|
|
299
|
-
|
|
300
|
-
If( selectedStrategy.equal( int( 1 ) ), () => {
|
|
301
|
-
|
|
302
|
-
sampleDir.assign( brdfSampleDirection );
|
|
303
|
-
samplePdf.assign( brdfSamplePdf );
|
|
304
|
-
sampleBrdfValue.assign( brdfSampleValue );
|
|
305
|
-
|
|
306
|
-
} ).ElseIf( selectedStrategy.equal( int( 2 ) ), () => {
|
|
307
|
-
|
|
308
|
-
// Strategy 2: Diffuse
|
|
309
|
-
sampleDir.assign( cosineWeightedSample( N, sampleRand ) );
|
|
310
|
-
samplePdf.assign( cosineWeightedPDF( max( dot( N, sampleDir ), 0.0 ) ) );
|
|
311
|
-
sampleBrdfValue.assign( evaluateMaterialResponse( V, sampleDir, N, material ) );
|
|
312
|
-
|
|
313
|
-
} ).ElseIf( selectedStrategy.equal( int( 3 ) ), () => {
|
|
314
|
-
|
|
315
|
-
// Strategy 3: Transmission
|
|
316
|
-
const entering = dot( V, N ).greaterThan( 0.0 );
|
|
317
|
-
const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission(
|
|
318
|
-
V, N, material.ior, material.roughness, entering, material.dispersion, sampleRand, rngState, float( 0.0 )
|
|
319
|
-
).toVar() );
|
|
320
|
-
sampleDir.assign( mtResult.direction );
|
|
321
|
-
samplePdf.assign( mtResult.pdf );
|
|
322
|
-
// evaluateMaterialResponse is reflection-only and returns a non-physical value for
|
|
323
|
-
// the below-surface refraction direction (its dots are floored to 0.001). Carry the
|
|
324
|
-
// sampler's spectral tint and apply an energy-consistent transmission throughput
|
|
325
|
-
// below (mirrors handleTransmission's material.color × colorWeight convention).
|
|
326
|
-
transColorWeight.assign( mtResult.colorWeight );
|
|
327
|
-
|
|
328
|
-
} ).Else( () => {
|
|
329
|
-
|
|
330
|
-
// Strategy 4: Clearcoat (fallback)
|
|
331
|
-
sampleDir.assign( brdfSampleDirection );
|
|
332
|
-
samplePdf.assign( brdfSamplePdf );
|
|
333
|
-
sampleBrdfValue.assign( brdfSampleValue );
|
|
334
|
-
|
|
335
|
-
} );
|
|
336
|
-
|
|
337
|
-
const NoL = max( dot( N, sampleDir ), 0.0 ).toVar();
|
|
338
|
-
|
|
339
|
-
// Calculate combined PDF for MIS (material strategies only)
|
|
340
|
-
const combinedPdf = float( 0.0 ).toVar();
|
|
341
|
-
|
|
342
|
-
If( weights.useSpecular, () => {
|
|
343
|
-
|
|
344
|
-
// Evaluate specular PDF at sampleDir (not brdfSampleDirection which may differ);
|
|
345
|
-
// auto-selects iso/aniso to stay consistent with the BRDF sampler.
|
|
346
|
-
const specPdfAtSampleDir = specularVNDFPDFForDir( material, N, V, sampleDir );
|
|
347
|
-
combinedPdf.addAssign( weights.specularWeight.mul( specPdfAtSampleDir ) );
|
|
348
|
-
|
|
349
|
-
} );
|
|
350
|
-
|
|
351
|
-
If( weights.useDiffuse, () => {
|
|
352
|
-
|
|
353
|
-
combinedPdf.addAssign( weights.diffuseWeight.mul( cosineWeightedPDF( NoL ) ) );
|
|
354
|
-
|
|
355
|
-
} );
|
|
356
|
-
|
|
357
|
-
If( weights.useTransmission.and( material.transmission.greaterThan( 0.0 ) ), () => {
|
|
358
|
-
|
|
359
|
-
// Calculate transmission PDF for this direction
|
|
360
|
-
const entering = dot( V, N ).greaterThan( 0.0 );
|
|
361
|
-
combinedPdf.addAssign( weights.transmissionWeight.mul(
|
|
362
|
-
calculateTransmissionPDF( V, sampleDir, N, material.ior, material.roughness, entering )
|
|
363
|
-
) );
|
|
364
|
-
|
|
365
|
-
} );
|
|
366
|
-
|
|
367
|
-
If( weights.useClearcoat.and( material.clearcoat.greaterThan( 0.0 ) ), () => {
|
|
368
|
-
|
|
369
|
-
// Calculate clearcoat PDF for this direction
|
|
370
|
-
combinedPdf.addAssign( weights.clearcoatWeight.mul(
|
|
371
|
-
calculateClearcoatPDF( V, sampleDir, N, material.clearcoatRoughness )
|
|
372
|
-
) );
|
|
373
|
-
|
|
374
|
-
} );
|
|
375
|
-
|
|
376
|
-
// Ensure valid PDFs
|
|
377
|
-
samplePdf.assign( max( samplePdf, MIN_PDF ) );
|
|
378
|
-
combinedPdf.assign( max( combinedPdf, MIN_PDF ) );
|
|
379
|
-
|
|
380
|
-
// MIS weight calculation
|
|
381
|
-
const misWeight = samplePdf.div( combinedPdf ).toVar();
|
|
382
|
-
|
|
383
|
-
// Reflection lobes: f·NoL·mis/pdf. Transmission lobe: the sampler's energy-consistent
|
|
384
|
-
// weight (material tint × dispersion colorWeight × mis) — the reflection BRDF value is
|
|
385
|
-
// invalid below the surface, so it is not used for transmission.
|
|
386
|
-
const reflThroughput = sampleBrdfValue.mul( NoL ).mul( misWeight ).div( samplePdf );
|
|
387
|
-
const transThroughput = material.color.xyz.mul( transColorWeight ).mul( misWeight );
|
|
388
|
-
|
|
389
|
-
r_direction.assign( sampleDir );
|
|
390
|
-
r_throughput.assign( select( selectedStrategy.equal( int( 3 ) ), transThroughput, reflThroughput ) );
|
|
391
|
-
r_misWeight.assign( misWeight );
|
|
392
|
-
r_pdf.assign( samplePdf );
|
|
393
|
-
r_combinedPdf.assign( combinedPdf );
|
|
394
|
-
|
|
395
|
-
} ); // End validInput check
|
|
78
|
+
} );
|
|
396
79
|
|
|
397
80
|
return IndirectLightingResult( {
|
|
398
81
|
direction: r_direction,
|
|
399
82
|
throughput: r_throughput,
|
|
400
|
-
misWeight: r_misWeight,
|
|
401
|
-
pdf: r_pdf,
|
|
402
83
|
combinedPdf: r_combinedPdf,
|
|
403
84
|
} );
|
|
404
85
|
|