three-gpu-pathtracer 0.0.25 → 0.0.27
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 +4 -1
- package/build/index.module.js +18 -2
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +18 -2
- package/build/index.umd.cjs.map +1 -1
- package/package.json +3 -2
- package/src/core/utils/sceneUpdateUtils.js +2 -1
- package/src/uniforms/LightsInfoUniformStruct.js +16 -1
- package/src/webgpu/AtlasTexture.js +30 -9
- package/src/webgpu/EquirectHdrInfoNode.js +11 -1
- package/src/webgpu/LightsInfoNode.js +141 -3
- package/src/webgpu/MegaKernelPathTracer.js +1 -0
- package/src/webgpu/PathTracerBackend.js +2 -0
- package/src/webgpu/TurquinTexture.js +2 -0
- package/src/webgpu/WaveFrontPathTracer.js +13 -0
- package/src/webgpu/WebGPUPathTracer.js +9 -3
- package/src/webgpu/compute/ComputeKernel.js +10 -0
- package/src/webgpu/compute/PathTracerMegaKernel.js +75 -44
- package/src/webgpu/compute/wavefront/LogicKernel.js +77 -43
- package/src/webgpu/compute/wavefront/MaterialKernel.js +1 -1
- package/src/webgpu/compute/wavefront/TraceShadowRayKernel.js +6 -4
- package/src/webgpu/compute/wavefront/structs.js +3 -1
- package/src/webgpu/index.d.ts +38 -0
- package/src/webgpu/materials/GltfCompliantMaterial.js +1 -1
- package/src/webgpu/nodes/PathtracerBVHComputeData.js +79 -9
- package/src/webgpu/nodes/lights.wgsl.js +21 -1
- package/src/webgpu/nodes/structs.wgsl.js +1 -0
- package/src/webgpu/upscale/FSRUpscaler.js +2 -1
|
@@ -53,12 +53,17 @@ export class LogicKernel extends ComputeKernel {
|
|
|
53
53
|
const sampleEnvColor = proxy( 'envInfo.value.sampleColor', params );
|
|
54
54
|
const sampleEnvDir = proxy( 'envInfo.value.sampleDir', params );
|
|
55
55
|
const getEnvDirPdf = proxy( 'envInfo.value.getDirPdf', params );
|
|
56
|
+
const getEnvWeight = proxy( 'envInfo.value.getWeight', params );
|
|
56
57
|
const sampleBackground = proxy( 'backgroundInfo.value.sampleColor', params );
|
|
57
58
|
|
|
58
59
|
// analytic scene lights pulled off the lightsInfo provider (LightsInfoNode)
|
|
59
60
|
const lightsCountNode = proxy( 'lightsInfo.value.countNode', params );
|
|
60
61
|
const randomLightSampleFn = proxyFn( 'lightsInfo.value.randomLightSample', params );
|
|
61
62
|
const intersectLightAtIndexFn = proxyFn( 'lightsInfo.value.intersectLightAtIndex', params );
|
|
63
|
+
const getLightWeightFn = proxyFn( 'lightsInfo.value.getLightWeight', params );
|
|
64
|
+
const getLightsWeightFn = proxyFn( 'lightsInfo.value.getLightsWeight', params );
|
|
65
|
+
const selectLightFn = proxyFn( 'lightsInfo.value.selectLight', params );
|
|
66
|
+
const isLightVisibleToCameraFn = proxyFn( 'lightsInfo.value.isLightVisibleToCamera', params );
|
|
62
67
|
|
|
63
68
|
const fn = wgslTagFn/* wgsl */`
|
|
64
69
|
|
|
@@ -97,19 +102,24 @@ export class LogicKernel extends ComputeKernel {
|
|
|
97
102
|
let indexUV = vec2u( input.pixelIndex >> 16, input.pixelIndex & 0xFFFF );
|
|
98
103
|
${ rngInit }( indexUV, input.seed, input.currentBounce + input.alphaDepth );
|
|
99
104
|
|
|
100
|
-
// one-sample NEE
|
|
105
|
+
// one-sample NEE chooses between the analytic lights and the environment, matched with
|
|
106
|
+
// the megakernel
|
|
101
107
|
let envActive = ${ envTotalSumNode } > 0.0 && ${ envIntensityNode } > 0.0;
|
|
102
108
|
let lightsCount = ${ lightsCountNode };
|
|
103
|
-
var
|
|
109
|
+
var envWeight = 0.0;
|
|
104
110
|
if ( envActive ) {
|
|
105
111
|
|
|
106
|
-
|
|
112
|
+
envWeight = ${ getEnvWeight }();
|
|
107
113
|
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
var resultColor = input.resultColor;
|
|
111
117
|
var throughputColor = input.throughputColor;
|
|
112
118
|
|
|
119
|
+
// added last, since a camera ray miss overwrites the pixel with the background
|
|
120
|
+
var lightHits = vec3f( 0.0 );
|
|
121
|
+
var cameraHitLight = false;
|
|
122
|
+
|
|
113
123
|
// resolve the previous surface's NEE shadow ray (pre-scatter throughput). The index
|
|
114
124
|
// is negative when no shadow ray was enqueued last frame
|
|
115
125
|
if ( input.shadowRayIntersectionIndex >= 0 && input.lightPdf > 0.0 ) {
|
|
@@ -118,8 +128,10 @@ export class LogicKernel extends ComputeKernel {
|
|
|
118
128
|
let occluded = shadowHit.objectIndex >= 0;
|
|
119
129
|
if ( ! occluded ) {
|
|
120
130
|
|
|
121
|
-
// env + area lights are also bsdf-sampled, so MIS-weight them; punctual take full weight
|
|
122
|
-
|
|
131
|
+
// env + area lights are also bsdf-sampled, so MIS-weight them; punctual take full weight,
|
|
132
|
+
// or when a bsdf ray could never reach the light
|
|
133
|
+
let isMISWeighted = ${ isMISWeightLightFn }( input.lightType ) && shadowHit.forwardBlocked == 0u;
|
|
134
|
+
let misWeight = select( 1.0, ${ misHeuristicFn }( input.lightPdf, input.lightBsdfPdf ), isMISWeighted );
|
|
123
135
|
let directLight = throughputColor * input.lightEmission * input.lightBsdf * misWeight / input.lightPdf;
|
|
124
136
|
let contribution = ${ clampPathContributionFunc }( directLight, input.currentBounce, clampDirect, clampIndirect );
|
|
125
137
|
resultColor += vec4f( contribution, 0.0 );
|
|
@@ -151,28 +163,38 @@ export class LogicKernel extends ComputeKernel {
|
|
|
151
163
|
let didHit = hitResult.objectIndex >= 0;
|
|
152
164
|
let surfaceDist = select( ${ LIGHT_FAR_DISTANCE }, hitResult.dist, didHit );
|
|
153
165
|
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
166
|
+
// NEE's total weight at the vertex this segment left, for MIS. Camera segments have none
|
|
167
|
+
let isMISWeighted = misEnabled != 0u && input.currentBounce > 0u;
|
|
168
|
+
var lightTotalWeight = 0.0;
|
|
169
|
+
if ( isMISWeighted ) {
|
|
157
170
|
|
|
158
|
-
|
|
171
|
+
lightTotalWeight = ${ getLightsWeightFn }( input.origin ) + envWeight;
|
|
159
172
|
|
|
160
|
-
|
|
161
|
-
if ( ${ intersectLightAtIndexFn }( input.origin, input.direction, li, &lightRec ) && lightRec.dist < surfaceDist ) {
|
|
173
|
+
}
|
|
162
174
|
|
|
163
|
-
|
|
164
|
-
|
|
175
|
+
// forward hits on area lights. Camera rays only see visibleToCameraRays lights, at full weight
|
|
176
|
+
for ( var li = 0u; li < lightsCount; li ++ ) {
|
|
165
177
|
|
|
166
|
-
|
|
167
|
-
misWeight = ${ misHeuristicFn }( input.scatterPdf, lightPdf );
|
|
178
|
+
if ( input.currentBounce == 0u && ! ${ isLightVisibleToCameraFn }( li ) ) {
|
|
168
179
|
|
|
169
|
-
|
|
180
|
+
continue;
|
|
181
|
+
|
|
182
|
+
}
|
|
170
183
|
|
|
171
|
-
|
|
172
|
-
|
|
184
|
+
var lightRec: ${ lightRecordStruct };
|
|
185
|
+
if ( ${ intersectLightAtIndexFn }( input.origin, input.direction, li, &lightRec ) && lightRec.dist < surfaceDist ) {
|
|
186
|
+
|
|
187
|
+
var misWeight = 1.0;
|
|
188
|
+
if ( isMISWeighted ) {
|
|
189
|
+
|
|
190
|
+
let selectionPdf = ${ getLightWeightFn }( li, input.origin ) / lightTotalWeight;
|
|
191
|
+
misWeight = ${ misHeuristicFn }( input.scatterPdf, lightRec.pdf * selectionPdf );
|
|
173
192
|
|
|
174
193
|
}
|
|
175
194
|
|
|
195
|
+
lightHits += ${ clampPathContributionFunc }( lightRec.emission * throughputColor * misWeight, input.currentBounce, clampDirect, clampIndirect );
|
|
196
|
+
cameraHitLight = cameraHitLight || input.currentBounce == 0u;
|
|
197
|
+
|
|
176
198
|
}
|
|
177
199
|
|
|
178
200
|
}
|
|
@@ -187,36 +209,40 @@ export class LogicKernel extends ComputeKernel {
|
|
|
187
209
|
rayDataStorage[ index ].objectIndex = hitResult.objectIndex;
|
|
188
210
|
rayDataStorage[ index ].dist = hitResult.dist;
|
|
189
211
|
|
|
190
|
-
// next event estimation:
|
|
191
|
-
// MaterialKernel evaluates the bsdf and enqueues the shadow ray.
|
|
192
|
-
// TODO: importance-sample the selection by light intensity and solid angle
|
|
212
|
+
// next event estimation: choose one light or the environment by its estimated
|
|
213
|
+
// contribution. MaterialKernel evaluates the bsdf and enqueues the shadow ray.
|
|
193
214
|
var lightPdf = 0.0;
|
|
194
|
-
if ( misEnabled != 0u
|
|
215
|
+
if ( misEnabled != 0u ) {
|
|
195
216
|
|
|
196
217
|
let ruv = ${ rand3 }( ${ RNG_INDEX_DIRECT_LIGHT_SAMPLE } );
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
// the environment, sampled from its CDF, as a light of kind ENVIRONMENT
|
|
202
|
-
let envSample = ${ sampleEnvDir }( ruv.yz );
|
|
203
|
-
lightRec.direction = envSample.direction;
|
|
204
|
-
lightRec.emission = envSample.color;
|
|
205
|
-
lightRec.pdf = envSample.pdf;
|
|
206
|
-
lightRec.dist = ${ LIGHT_FAR_DISTANCE };
|
|
207
|
-
lightRec.lightType = ${ ENVIRONMENT_LIGHT_TYPE };
|
|
218
|
+
var selectionPdf = 0.0;
|
|
219
|
+
let lightIndex = ${ selectLightFn }( hitResult.position, envWeight, ruv.x, &selectionPdf );
|
|
220
|
+
if ( selectionPdf > 0.0 ) {
|
|
208
221
|
|
|
209
|
-
|
|
222
|
+
var lightRec: ${ lightRecordStruct };
|
|
223
|
+
if ( envActive && lightIndex == lightsCount ) {
|
|
210
224
|
|
|
211
|
-
|
|
225
|
+
// the environment, sampled from its CDF, as a light of kind ENVIRONMENT
|
|
226
|
+
let envSample = ${ sampleEnvDir }( ruv.yz );
|
|
227
|
+
lightRec.direction = envSample.direction;
|
|
228
|
+
lightRec.emission = envSample.color;
|
|
229
|
+
lightRec.pdf = envSample.pdf;
|
|
230
|
+
lightRec.dist = ${ LIGHT_FAR_DISTANCE };
|
|
231
|
+
lightRec.lightType = ${ ENVIRONMENT_LIGHT_TYPE };
|
|
212
232
|
|
|
213
|
-
|
|
233
|
+
} else {
|
|
234
|
+
|
|
235
|
+
lightRec = ${ randomLightSampleFn }( lightIndex, hitResult.position, ruv.yz );
|
|
214
236
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
lightPdf = lightRec.pdf * selectionPdf;
|
|
240
|
+
rayDataStorage[ index ].lightDirection = lightRec.direction;
|
|
241
|
+
rayDataStorage[ index ].lightEmission = lightRec.emission;
|
|
242
|
+
rayDataStorage[ index ].lightDist = lightRec.dist;
|
|
243
|
+
rayDataStorage[ index ].lightType = lightRec.lightType;
|
|
244
|
+
|
|
245
|
+
}
|
|
220
246
|
|
|
221
247
|
}
|
|
222
248
|
|
|
@@ -229,10 +255,10 @@ export class LogicKernel extends ComputeKernel {
|
|
|
229
255
|
if ( input.currentBounce > 0u && input.isFullyTransmissive == 0u ) {
|
|
230
256
|
|
|
231
257
|
var misWeight = 1.0;
|
|
232
|
-
if (
|
|
258
|
+
if ( isMISWeighted && envActive ) {
|
|
233
259
|
|
|
234
260
|
// match the env pdf scaling used by the NEE selection so the two estimators balance
|
|
235
|
-
let envPdf = ${ getEnvDirPdf }( input.direction ) /
|
|
261
|
+
let envPdf = ${ getEnvDirPdf }( input.direction ) * envWeight / lightTotalWeight;
|
|
236
262
|
misWeight = ${ misHeuristicFn }( input.scatterPdf, envPdf );
|
|
237
263
|
|
|
238
264
|
}
|
|
@@ -308,6 +334,14 @@ export class LogicKernel extends ComputeKernel {
|
|
|
308
334
|
|
|
309
335
|
}
|
|
310
336
|
|
|
337
|
+
// visible lights are opaque against a transparent background
|
|
338
|
+
resultColor += vec4f( lightHits, 0.0 );
|
|
339
|
+
if ( cameraHitLight ) {
|
|
340
|
+
|
|
341
|
+
resultColor.a = 1.0;
|
|
342
|
+
|
|
343
|
+
}
|
|
344
|
+
|
|
311
345
|
// the color rows are stored top down to match a rasterized render target
|
|
312
346
|
let colorIndex = vec2u( indexUV.x, textureDimensions( ${ params.outputTarget } ).y - 1u - indexUV.y );
|
|
313
347
|
let storedSamples = textureLoad( ${ params.sampleCountTarget }, indexUV ).r & ${ SAMPLE_COUNT_MASK }u;
|
|
@@ -339,7 +339,7 @@ export class MaterialKernel extends ComputeKernel {
|
|
|
339
339
|
shadowRayQueue.elements[ shadowIndex ].currentBounce = input.currentBounce;
|
|
340
340
|
shadowRayQueue.elements[ shadowIndex ].seed = input.seed;
|
|
341
341
|
shadowRayQueue.elements[ shadowIndex ].alphaDepth = input.alphaDepth;
|
|
342
|
-
shadowRayQueue.elements[ shadowIndex ].maxDist = input.lightDist - ${ LIGHT_EPSILON };
|
|
342
|
+
shadowRayQueue.elements[ shadowIndex ].maxDist = input.lightDist * ( 1.0 - ${ LIGHT_EPSILON } );
|
|
343
343
|
rayDataStorage[ index ].shadowRayIntersectionIndex = i32( shadowIndex );
|
|
344
344
|
|
|
345
345
|
} else {
|
|
@@ -5,8 +5,8 @@ import { proxy, wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
|
5
5
|
import { rngInit } from '../../nodes/random.wgsl.js';
|
|
6
6
|
import { rayQueueStruct, intersectionResultStruct } from './structs.js';
|
|
7
7
|
|
|
8
|
-
// Pure BVH traversal over the queued shadow rays. Uses
|
|
9
|
-
//
|
|
8
|
+
// Pure BVH traversal over the queued shadow rays. Uses a first-hit traversal without face culling
|
|
9
|
+
// ( no dedicated any-hit traversal exists yet ); LogicKernel decides occlusion by comparing the
|
|
10
10
|
// hit distance against the light distance.
|
|
11
11
|
export class TraceShadowRayKernel extends ComputeKernel {
|
|
12
12
|
|
|
@@ -22,7 +22,7 @@ export class TraceShadowRayKernel extends ComputeKernel {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
const raycastOutput = proxy( 'bvhData.value.fns.raycastFirstHit.outputType', params );
|
|
25
|
-
const
|
|
25
|
+
const raycastShadowFn = proxy( 'bvhData.value.fns.raycastShadow', params );
|
|
26
26
|
|
|
27
27
|
const fn = wgslTagFn /* wgsl */`
|
|
28
28
|
|
|
@@ -44,7 +44,7 @@ export class TraceShadowRayKernel extends ComputeKernel {
|
|
|
44
44
|
|
|
45
45
|
let ray = Ray( queuedRay.origin, queuedRay.direction, queuedRay.maxDist );
|
|
46
46
|
var hitResult: ${ raycastOutput };
|
|
47
|
-
if ( ${
|
|
47
|
+
if ( ${ raycastShadowFn }( ray, &hitResult ) ) {
|
|
48
48
|
|
|
49
49
|
shadowRayIntersectionsStorage[ index ].objectIndex = i32( hitResult.objectIndex );
|
|
50
50
|
shadowRayIntersectionsStorage[ index ].dist = hitResult.dist;
|
|
@@ -55,6 +55,8 @@ export class TraceShadowRayKernel extends ComputeKernel {
|
|
|
55
55
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
shadowRayIntersectionsStorage[ index ].forwardBlocked = u32( hitResult.forwardBlocked );
|
|
59
|
+
|
|
58
60
|
}
|
|
59
61
|
`;
|
|
60
62
|
|
|
@@ -118,7 +118,9 @@ export const intersectionResultStruct = new StructTypeNode( {
|
|
|
118
118
|
side: 'float',
|
|
119
119
|
|
|
120
120
|
indices: 'vec3u',
|
|
121
|
-
|
|
121
|
+
|
|
122
|
+
// shadow rays only: a face a bsdf ray would hit was passed
|
|
123
|
+
forwardBlocked: 'uint',
|
|
122
124
|
|
|
123
125
|
}, 'TraceResult' );
|
|
124
126
|
|
package/src/webgpu/index.d.ts
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
import { Camera, Scene, Texture, ExternalTexture } from 'three';
|
|
2
2
|
import { WebGPURenderer } from 'three/webgpu';
|
|
3
3
|
|
|
4
|
+
// three.js type augmentation
|
|
5
|
+
|
|
6
|
+
declare module 'three' {
|
|
7
|
+
|
|
8
|
+
export interface Material {
|
|
9
|
+
|
|
10
|
+
matte?: boolean;
|
|
11
|
+
castShadow?: boolean;
|
|
12
|
+
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface Object3D {
|
|
16
|
+
|
|
17
|
+
visibleToShadowRays?: boolean;
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Light {
|
|
22
|
+
|
|
23
|
+
visibleToCameraRays?: boolean;
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SpotLight {
|
|
28
|
+
|
|
29
|
+
radius?: number;
|
|
30
|
+
iesMap?: Texture | null;
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface RectAreaLight {
|
|
35
|
+
|
|
36
|
+
isCircular?: boolean;
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
|
|
4
42
|
// constants
|
|
5
43
|
|
|
6
44
|
export type RandomStrategy = object;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { float } from 'three/tsl';
|
|
2
2
|
import { wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
3
|
-
import { PathtracingMaterial } from './PathtracingMaterial';
|
|
3
|
+
import { PathtracingMaterial } from './PathtracingMaterial.js';
|
|
4
4
|
import { specularBrdfFunc, specularBtdfFunc, fresnelMixFunc, conductorFresnelFunc, fresnelCoatFunc, iridescentFresnelFunc, isMatchedIorFunc, thinWallTransmissionRoughnessFunc, transmissionFresnelFunc } from '../nodes/material.wgsl.js';
|
|
5
5
|
import { eonBrdfFunc, eonDirectionFunc, eonPDFFunc } from '../nodes/eon.wgsl.js';
|
|
6
6
|
import { sheenColorFunc, sheenAlbedoScalingFunc } from '../nodes/sheen.wgsl.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BackSide, FrontSide, DoubleSide, BufferAttribute, BufferGeometry, StorageBufferAttribute, StructTypeNode, Vector4, SkinnedMesh, RepeatWrapping, ClampToEdgeWrapping, MirroredRepeatWrapping, NearestFilter } from 'three/webgpu';
|
|
2
|
-
import { BVHComputeData, intersectRayTriangle, bvhNodeBoundsStruct, bvhNodeStruct, rayStruct,
|
|
3
|
-
import { storage, float, texture, uniformArray, uint } from 'three/tsl';
|
|
2
|
+
import { BVHComputeData, intersectRayTriangle, bvhNodeBoundsStruct, bvhNodeStruct, rayStruct, wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
3
|
+
import { storage, float, bool, texture, uniformArray, uint } from 'three/tsl';
|
|
4
4
|
import { SkinnedMeshBVH, MeshBVH, SAH } from 'three-mesh-bvh';
|
|
5
5
|
import { materialStruct } from './structs.wgsl.js';
|
|
6
6
|
import { getTextureHash } from '../../core/utils/sceneUpdateUtils.js';
|
|
@@ -14,11 +14,23 @@ const transformStruct = new StructTypeNode( {
|
|
|
14
14
|
inverseMatrixWorld: 'mat4x4f',
|
|
15
15
|
visible: 'uint',
|
|
16
16
|
materialIndex: 'uint',
|
|
17
|
+
visibleToShadowRays: 'uint',
|
|
17
18
|
_alignment0: 'uint',
|
|
18
|
-
_alignment1: 'uint',
|
|
19
19
|
color: 'vec4f',
|
|
20
20
|
}, 'TransformStruct' );
|
|
21
21
|
|
|
22
|
+
// three-mesh-bvh's ray hit result, plus whether a shadow ray passed a face a bsdf ray would hit
|
|
23
|
+
const intersectionResultStruct = new StructTypeNode( {
|
|
24
|
+
indices: 'vec4u',
|
|
25
|
+
normal: 'vec3f',
|
|
26
|
+
didHit: 'bool',
|
|
27
|
+
barycoord: 'vec3f',
|
|
28
|
+
objectIndex: 'uint',
|
|
29
|
+
side: 'float',
|
|
30
|
+
dist: 'float',
|
|
31
|
+
forwardBlocked: 'bool',
|
|
32
|
+
}, 'PathTracerIntersectionResult' );
|
|
33
|
+
|
|
22
34
|
// Pathtracer-specific version of the BVHComputeData tht includes material mapping, property structs
|
|
23
35
|
export class PathtracerBVHComputeData extends BVHComputeData {
|
|
24
36
|
|
|
@@ -181,8 +193,11 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
181
193
|
const scratchRayScalar = float( 1.0 ).toVar( 'bvh_rayScalar' );
|
|
182
194
|
const baseOpacityScalar = float( 1.0 ).toVar( 'bvh_baseOpacity' );
|
|
183
195
|
|
|
184
|
-
|
|
185
|
-
|
|
196
|
+
// shadow rays hit both sides, and pass through the objects not visible to them
|
|
197
|
+
const isShadowRay = bool( false ).toVar( 'bvh_isShadowRay' );
|
|
198
|
+
const skipShadowObject = bool( false ).toVar( 'bvh_skipShadowObject' );
|
|
199
|
+
|
|
200
|
+
const raycastOptions = {
|
|
186
201
|
shapeStruct: rayStruct,
|
|
187
202
|
resultStruct: intersectionResultStruct,
|
|
188
203
|
|
|
@@ -259,7 +274,8 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
259
274
|
let material = ${ storage.materials }[ ${ currentMaterialIndex } ];
|
|
260
275
|
|
|
261
276
|
// TODO: if material is a transmissive volume we may need to assume double-sidedness
|
|
262
|
-
|
|
277
|
+
// shadow rays hit both sides
|
|
278
|
+
if ( ! ${ isShadowRay } && material.side != 0 && triResult.side != material.side ) {
|
|
263
279
|
|
|
264
280
|
continue;
|
|
265
281
|
|
|
@@ -325,6 +341,19 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
325
341
|
|
|
326
342
|
}
|
|
327
343
|
|
|
344
|
+
// shadow rays pass the objects not visible to them, but a bsdf ray would hit this face
|
|
345
|
+
if ( ${ skipShadowObject } ) {
|
|
346
|
+
|
|
347
|
+
if ( material.side == 0 || triResult.side == material.side ) {
|
|
348
|
+
|
|
349
|
+
result.forwardBlocked = true;
|
|
350
|
+
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
continue;
|
|
354
|
+
|
|
355
|
+
}
|
|
356
|
+
|
|
328
357
|
result.didHit = true;
|
|
329
358
|
result.dist = triResult.dist;
|
|
330
359
|
result.normal = triResult.normal;
|
|
@@ -367,6 +396,8 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
367
396
|
|
|
368
397
|
}
|
|
369
398
|
|
|
399
|
+
${ skipShadowObject } = ${ isShadowRay } && object.visibleToShadowRays == 0u;
|
|
400
|
+
|
|
370
401
|
}
|
|
371
402
|
`,
|
|
372
403
|
transformResultFn: wgslTagFn/* wgsl */`
|
|
@@ -384,6 +415,30 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
384
415
|
${ scratchRayScalar } = 1.0;
|
|
385
416
|
${ baseOpacityScalar } = 1.0;
|
|
386
417
|
|
|
418
|
+
}
|
|
419
|
+
`,
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
fns.raycastFirstHit = this.getShapecastFn( {
|
|
423
|
+
...raycastOptions,
|
|
424
|
+
name: 'raycastFirstHit',
|
|
425
|
+
prefixFn: wgslTagFn/* wgsl */`
|
|
426
|
+
fn initFirstHitCull() -> void {
|
|
427
|
+
|
|
428
|
+
${ isShadowRay } = false;
|
|
429
|
+
|
|
430
|
+
}
|
|
431
|
+
`,
|
|
432
|
+
} );
|
|
433
|
+
|
|
434
|
+
fns.raycastShadow = this.getShapecastFn( {
|
|
435
|
+
...raycastOptions,
|
|
436
|
+
name: 'raycastShadow',
|
|
437
|
+
prefixFn: wgslTagFn/* wgsl */`
|
|
438
|
+
fn initShadowCull() -> void {
|
|
439
|
+
|
|
440
|
+
${ isShadowRay } = true;
|
|
441
|
+
|
|
387
442
|
}
|
|
388
443
|
`,
|
|
389
444
|
} );
|
|
@@ -398,6 +453,9 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
398
453
|
|
|
399
454
|
this.updateUvAttributesFromScene();
|
|
400
455
|
|
|
456
|
+
// the material indices are written with the transforms
|
|
457
|
+
this.updateMaterialsMap();
|
|
458
|
+
|
|
401
459
|
super.update();
|
|
402
460
|
|
|
403
461
|
// build the channel -> uv lookup now that the geometry struct (and its uv members) exist
|
|
@@ -423,7 +481,7 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
423
481
|
|
|
424
482
|
if ( Array.isArray( o.material ) ) {
|
|
425
483
|
|
|
426
|
-
o.material.forEach( m => add( m ) );
|
|
484
|
+
o.material.forEach( m => m && add( m ) );
|
|
427
485
|
|
|
428
486
|
} else {
|
|
429
487
|
|
|
@@ -906,9 +964,17 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
906
964
|
|
|
907
965
|
}
|
|
908
966
|
|
|
909
|
-
// save the index
|
|
910
|
-
const index = materialsMap.get( material ) || 0;
|
|
911
967
|
const transformBufferU32 = new Uint32Array( targetBuffer );
|
|
968
|
+
|
|
969
|
+
const index = materialsMap.has( material ) ? materialsMap.get( material ) : - 1;
|
|
970
|
+
if ( index === - 1 ) {
|
|
971
|
+
|
|
972
|
+
// hide the object when there is no material
|
|
973
|
+
transformBufferU32[ writeOffset * transformStruct.getLength() + 32 ] = 0;
|
|
974
|
+
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// save the material index
|
|
912
978
|
transformBufferU32[ writeOffset * transformStruct.getLength() + 33 ] = index;
|
|
913
979
|
|
|
914
980
|
// write color
|
|
@@ -939,6 +1005,10 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
939
1005
|
super.writeTransformData( info, premultiplyMatrix, writeOffset, targetBuffer );
|
|
940
1006
|
this.writeMaterialData( info, writeOffset, targetBuffer );
|
|
941
1007
|
|
|
1008
|
+
// whether shadow rays see the object, in the slot after the material index
|
|
1009
|
+
const transformBufferU32 = new Uint32Array( targetBuffer );
|
|
1010
|
+
transformBufferU32[ writeOffset * this.structs.transform.getLength() + 34 ] = info.object.visibleToShadowRays === false ? 0 : 1;
|
|
1011
|
+
|
|
942
1012
|
}
|
|
943
1013
|
|
|
944
1014
|
getBVH( object, instanceId, rangeTarget ) {
|
|
@@ -11,7 +11,7 @@ export const POINT_LIGHT_TYPE = 4;
|
|
|
11
11
|
export const ENVIRONMENT_LIGHT_TYPE = 5;
|
|
12
12
|
export const LIGHT_FAR_DISTANCE = 1e30;
|
|
13
13
|
|
|
14
|
-
//
|
|
14
|
+
// fraction of the light distance a shadow ray stops short of the light
|
|
15
15
|
export const LIGHT_EPSILON = 1e-5;
|
|
16
16
|
|
|
17
17
|
// light kinds that are also bsdf-sampled and so take MIS-weighted NEE - punctual lights take full weight
|
|
@@ -201,3 +201,23 @@ export const randomSpotLightSampleFn = wgslFn( /* wgsl */ `
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
`, [ lightStruct, lightRecordStruct, constants, getDistanceAttenuationFn ] );
|
|
204
|
+
|
|
205
|
+
// The cosine to the spot axis and the distance from a position to the nearest point on the spot
|
|
206
|
+
// light's disc, where the disc lights the position the most
|
|
207
|
+
export const getSpotLightNearestFn = wgslFn( /* wgsl */ `
|
|
208
|
+
|
|
209
|
+
fn getSpotLightNearest( light: Light, position: vec3f ) -> vec2f {
|
|
210
|
+
|
|
211
|
+
let normal = normalize( cross( light.u, light.v ) );
|
|
212
|
+
let startDistance = light.radius / max( tan( acos( light.coneCos ) ), EPSILON );
|
|
213
|
+
let discCenter = light.position - normal * startDistance;
|
|
214
|
+
|
|
215
|
+
let toPosition = position - discCenter;
|
|
216
|
+
let along = dot( toPosition, - normal );
|
|
217
|
+
let across = max( length( toPosition + normal * along ) - light.radius, 0.0 );
|
|
218
|
+
let dist = length( vec2f( along, across ) );
|
|
219
|
+
return vec2f( along / max( dist, EPSILON ), dist );
|
|
220
|
+
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
`, [ lightStruct, constants ] );
|
|
@@ -50,6 +50,7 @@ export class FSRUpscaler {
|
|
|
50
50
|
init( renderer ) {
|
|
51
51
|
|
|
52
52
|
this.renderer = renderer;
|
|
53
|
+
this._upscaler?.dispose();
|
|
53
54
|
this._upscaler = new this.Upscaler( { renderer } );
|
|
54
55
|
this._upscaler.init();
|
|
55
56
|
|
|
@@ -100,7 +101,7 @@ export class FSRUpscaler {
|
|
|
100
101
|
|
|
101
102
|
dispose() {
|
|
102
103
|
|
|
103
|
-
this._upscaler
|
|
104
|
+
this._upscaler?.dispose();
|
|
104
105
|
this._upscaler = null;
|
|
105
106
|
|
|
106
107
|
}
|