three-gpu-pathtracer 0.0.26 → 0.0.28
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 -2
- package/build/index.module.js +23 -5
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +22 -4
- package/build/index.umd.cjs.map +1 -1
- package/package.json +8 -8
- package/src/core/PathTracingSceneGenerator.js +1 -1
- package/src/core/utils/sceneUpdateUtils.js +2 -1
- package/src/uniforms/EquirectHdrInfoUniform.js +4 -2
- package/src/uniforms/LightsInfoUniformStruct.js +16 -1
- package/src/webgpu/API.md +167 -19
- package/src/webgpu/AtlasTexture.js +32 -12
- package/src/webgpu/EquirectHdrInfoNode.js +97 -46
- package/src/webgpu/LightsInfoNode.js +142 -4
- package/src/webgpu/MegaKernelPathTracer.js +11 -3
- package/src/webgpu/PathTracerBackend.js +174 -21
- package/src/webgpu/TurquinTexture.js +2 -0
- package/src/webgpu/WaveFrontPathTracer.js +169 -71
- package/src/webgpu/WebGPUPathTracer.js +637 -51
- package/src/webgpu/compute/ComputeKernel.js +18 -0
- package/src/webgpu/compute/PathTracerMegaKernel.js +253 -71
- package/src/webgpu/compute/TallySampleCountsKernel.js +10 -10
- package/src/webgpu/compute/UnpackAuxKernel.js +48 -0
- package/src/webgpu/compute/ZeroOutKernel.js +3 -1
- package/src/webgpu/compute/targets.js +55 -0
- package/src/webgpu/compute/wavefront/LogicKernel.js +136 -55
- package/src/webgpu/compute/wavefront/MaterialKernel.js +153 -66
- package/src/webgpu/compute/wavefront/MediumKernel.js +154 -0
- package/src/webgpu/compute/wavefront/PopulatePixelIndicesKernel.js +5 -5
- package/src/webgpu/compute/wavefront/QueueLengthToDispatchKernel.js +5 -5
- package/src/webgpu/compute/wavefront/QueueMediumWalksKernel.js +52 -0
- package/src/webgpu/compute/wavefront/ResetSlotsKernel.js +6 -6
- package/src/webgpu/compute/wavefront/TraceRayKernel.js +11 -9
- package/src/webgpu/compute/wavefront/TraceShadowRayKernel.js +18 -23
- package/src/webgpu/compute/wavefront/structs.js +63 -35
- package/src/webgpu/constants.js +2 -2
- package/src/webgpu/denoise/OIDNDenoiser.js +81 -78
- package/src/webgpu/index.d.ts +79 -6
- package/src/webgpu/materials/GltfCompliantMaterial.js +20 -6
- package/src/webgpu/materials/debug/AuxDebugMaterial.js +54 -0
- package/src/webgpu/nodes/PathtracerBVHComputeData.js +109 -17
- package/src/webgpu/nodes/aux.wgsl.js +61 -0
- package/src/webgpu/nodes/eon.wgsl.js +1 -1
- package/src/webgpu/nodes/lights.wgsl.js +21 -1
- package/src/webgpu/nodes/material.wgsl.js +2 -1
- package/src/webgpu/nodes/medium.wgsl.js +165 -0
- package/src/webgpu/nodes/random.wgsl.js +2 -0
- package/src/webgpu/nodes/sampling.wgsl.js +44 -3
- package/src/webgpu/nodes/structs.wgsl.js +8 -1
- package/src/webgpu/upscale/FSRUpscaler.js +3 -2
package/src/webgpu/index.d.ts
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
|
-
import { Camera, Scene, Texture, ExternalTexture } from 'three';
|
|
2
|
-
import { WebGPURenderer } from 'three/webgpu';
|
|
1
|
+
import { Camera, Color, EventDispatcher, Scene, Texture, ExternalTexture } from 'three';
|
|
2
|
+
import { StorageArrayTexture, WebGPURenderer } from 'three/webgpu';
|
|
3
|
+
import { BVHWorker } from '../index.js';
|
|
4
|
+
|
|
5
|
+
// three.js type augmentation
|
|
6
|
+
|
|
7
|
+
declare module 'three' {
|
|
8
|
+
|
|
9
|
+
export interface Material {
|
|
10
|
+
|
|
11
|
+
matte?: boolean;
|
|
12
|
+
castShadow?: boolean;
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// volume scattering from "KHR_materials_scatter"
|
|
17
|
+
export interface MeshPhysicalMaterial {
|
|
18
|
+
|
|
19
|
+
scatterStrength?: number;
|
|
20
|
+
multiscatterColor?: Color;
|
|
21
|
+
scatterAnisotropy?: number;
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface Object3D {
|
|
26
|
+
|
|
27
|
+
visibleToShadowRays?: boolean;
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Light {
|
|
32
|
+
|
|
33
|
+
visibleToCameraRays?: boolean;
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface SpotLight {
|
|
38
|
+
|
|
39
|
+
radius?: number;
|
|
40
|
+
iesMap?: Texture | null;
|
|
41
|
+
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface RectAreaLight {
|
|
45
|
+
|
|
46
|
+
isCircular?: boolean;
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
}
|
|
3
51
|
|
|
4
52
|
// constants
|
|
5
53
|
|
|
@@ -64,7 +112,14 @@ export interface OIDNDenoiserOptions {
|
|
|
64
112
|
|
|
65
113
|
}
|
|
66
114
|
|
|
67
|
-
export
|
|
115
|
+
export interface OIDNDenoiserEventMap {
|
|
116
|
+
|
|
117
|
+
'error': { error: Error };
|
|
118
|
+
'progress': { progress: number };
|
|
119
|
+
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export class OIDNDenoiser extends EventDispatcher<OIDNDenoiserEventMap> {
|
|
68
123
|
|
|
69
124
|
constructor( options: OIDNDenoiserOptions );
|
|
70
125
|
|
|
@@ -80,8 +135,7 @@ export class OIDNDenoiser {
|
|
|
80
135
|
readonly running: boolean;
|
|
81
136
|
|
|
82
137
|
init( renderer: WebGPURenderer ): void;
|
|
83
|
-
|
|
84
|
-
update( target: Texture ): Texture | null;
|
|
138
|
+
update( target: Texture, aux?: StorageArrayTexture | null ): Texture | null;
|
|
85
139
|
denoise( color: Texture, albedo?: Texture | null, normal?: Texture | null ): Promise<void>;
|
|
86
140
|
reset(): void;
|
|
87
141
|
dispose(): void;
|
|
@@ -117,13 +171,23 @@ export interface DebugBoundsOptions {
|
|
|
117
171
|
|
|
118
172
|
}
|
|
119
173
|
|
|
120
|
-
export
|
|
174
|
+
export interface WebGPUPathTracerEventMap {
|
|
175
|
+
|
|
176
|
+
'complete': {};
|
|
177
|
+
'progress': { stage: 'building' | 'compiling' | 'rendering' | 'denoising', progress: number | null, samples?: SampleCounts };
|
|
178
|
+
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export class WebGPUPathTracer extends EventDispatcher<WebGPUPathTracerEventMap> {
|
|
121
182
|
|
|
122
183
|
constructor( renderer: WebGPURenderer );
|
|
123
184
|
|
|
124
185
|
maxBounces: number;
|
|
125
186
|
frameBudget: number;
|
|
187
|
+
adaptiveFrameBudget: boolean;
|
|
188
|
+
targetFrameTime: number;
|
|
126
189
|
maxTransparentBounces: number;
|
|
190
|
+
maxSubsurfaceBounces: number;
|
|
127
191
|
maxSamples: number;
|
|
128
192
|
transmissiveBackground: TransmissiveBackgroundMode;
|
|
129
193
|
filterGlossyFactor: number;
|
|
@@ -136,17 +200,23 @@ export class WebGPUPathTracer {
|
|
|
136
200
|
fadeDuration: number;
|
|
137
201
|
dynamicLowRes: boolean;
|
|
138
202
|
lowResScale: number;
|
|
203
|
+
adaptiveDynamicRes: boolean;
|
|
139
204
|
renderScale: number;
|
|
140
205
|
synchronizeRenderSize: boolean;
|
|
206
|
+
renderToCanvas: boolean;
|
|
207
|
+
asyncCompile: boolean;
|
|
141
208
|
generateMissingAttributes: boolean;
|
|
142
209
|
commonAttributes: Array<string>;
|
|
143
210
|
stableNoise: boolean;
|
|
144
211
|
pause: boolean;
|
|
145
212
|
|
|
146
213
|
readonly target: Texture | null;
|
|
214
|
+
readonly frameTime: number;
|
|
215
|
+
readonly complete: boolean;
|
|
147
216
|
readonly fadeState: number;
|
|
148
217
|
readonly lowResTarget: Texture;
|
|
149
218
|
readonly lowResMode: boolean;
|
|
219
|
+
readonly compiling: boolean;
|
|
150
220
|
readonly textureAtlas: AtlasTexture;
|
|
151
221
|
|
|
152
222
|
getSampleCountsAsync(): Promise<SampleCounts>;
|
|
@@ -155,6 +225,8 @@ export class WebGPUPathTracer {
|
|
|
155
225
|
setUpscaler( upscaler: FSRUpscaler | null ): void;
|
|
156
226
|
|
|
157
227
|
setScene( scene: Scene, camera: Camera ): void;
|
|
228
|
+
setBVHWorker( WorkerConstructor: new () => BVHWorker & { dispose(): void } ): void;
|
|
229
|
+
setSceneAsync( scene: Scene, camera: Camera ): Promise<void>;
|
|
158
230
|
setRandom( random: RandomStrategy ): void;
|
|
159
231
|
setCamera( camera: Camera ): void;
|
|
160
232
|
setSize( x: number, y: number ): void;
|
|
@@ -170,6 +242,7 @@ export class WebGPUPathTracer {
|
|
|
170
242
|
renderDebugBounds( options?: DebugBoundsOptions ): void;
|
|
171
243
|
renderTextureAtlas( layer?: number ): void;
|
|
172
244
|
renderSampleDensity(): void;
|
|
245
|
+
renderAuxBuffer( buffer?: 'albedo' | 'normal' ): void;
|
|
173
246
|
getRenderTime(): number;
|
|
174
247
|
dispose(): void;
|
|
175
248
|
|
|
@@ -160,8 +160,10 @@ export class GltfCompliantMaterial extends PathtracingMaterial {
|
|
|
160
160
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
// the refracted half is not attenuated by the layers above
|
|
164
|
-
|
|
163
|
+
// the refracted half is not attenuated by the layers above. A volume is entered and exited,
|
|
164
|
+
// so each crossing takes the square root of the color, as in Cycles
|
|
165
|
+
let transmissionTint = select( sqrt( surf.color ), surf.color, surf.thinWall );
|
|
166
|
+
result += ( 1.0 - surf.metalness ) * surf.transmission * refraction * transmissionTint;
|
|
165
167
|
|
|
166
168
|
} else {
|
|
167
169
|
|
|
@@ -228,11 +230,15 @@ export class GltfCompliantMaterial extends PathtracingMaterial {
|
|
|
228
230
|
let metallic = metallicSpecular * metallicReflectance;
|
|
229
231
|
let dielectric = dielectricSpecular * dielectricReflectance;
|
|
230
232
|
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
+
// The energy the specular interface takes from the layers below, at the lobe's specular color
|
|
234
|
+
// and intensity. The table holds the albedo at the ior's f0, and the Schlick albedo is linear
|
|
235
|
+
// in f0, so the tinted albedo slides from it toward the conductor albedo at f0 = 1
|
|
236
|
+
let iorEnergySS = ${ this.turquinTexture.sampleDielectricFn }( NdotV, surf.roughness, surf.ior );
|
|
237
|
+
let tintedEnergySS = iorEnergySS + ( dielectricF0 - surf.f0 ) * ( energySS - iorEnergySS ) / ( 1.0 - surf.f0 );
|
|
238
|
+
let fresnelEnergySS = surf.specularIntensity * tintedEnergySS * dielectricBoost;
|
|
233
239
|
|
|
234
240
|
result += attenuation * mix( ( 1.0 - surf.transmission ) * dielectric, metallic, surf.metalness );
|
|
235
|
-
attenuation *= ( 1.0 - surf.metalness ) * mix( 1.0 - fresnelEnergySS, 1.0 - filmFresnelMax, surf.iridescence );
|
|
241
|
+
attenuation *= ( 1.0 - surf.metalness ) * mix( 1.0 - fresnelEnergySS, vec3f( 1.0 - filmFresnelMax ), surf.iridescence );
|
|
236
242
|
|
|
237
243
|
}
|
|
238
244
|
|
|
@@ -385,6 +391,13 @@ export class GltfCompliantMaterial extends PathtracingMaterial {
|
|
|
385
391
|
|
|
386
392
|
wi = refract( - wo, wh, surf.eta );
|
|
387
393
|
|
|
394
|
+
// "refract" returns zero on total internal reflection, so reflect instead and let "isDead" drop it
|
|
395
|
+
if ( dot( wi, wi ) == 0.0 ) {
|
|
396
|
+
|
|
397
|
+
wi = - normalize( reflect( wo, wh ) );
|
|
398
|
+
|
|
399
|
+
}
|
|
400
|
+
|
|
388
401
|
}
|
|
389
402
|
|
|
390
403
|
// the facet choice must agree with the resulting hemisphere - reflection above,
|
|
@@ -417,7 +430,8 @@ export class GltfCompliantMaterial extends PathtracingMaterial {
|
|
|
417
430
|
|
|
418
431
|
// Cycles represents a singular event with a large matching BSDF and PDF. Their
|
|
419
432
|
// ratio preserves throughput while forward MIS correctly gives the event full weight.
|
|
420
|
-
|
|
433
|
+
let transmissionTint = select( sqrt( surf.color ), surf.color, surf.thinWall );
|
|
434
|
+
result.color = ( 1.0 - surf.metalness ) * surf.transmission * transmissionTint *
|
|
421
435
|
deltaTransmittance * ${ SINGULAR_EVENT_SCALE };
|
|
422
436
|
result.direction = worldWi;
|
|
423
437
|
result.pdf = weights.transmission * deltaProbability * ${ SINGULAR_EVENT_SCALE };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { MeshBasicNodeMaterial, NoBlending } from 'three/webgpu';
|
|
2
|
+
import { uv, varying, texture, uniform } from 'three/tsl';
|
|
3
|
+
import { wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
4
|
+
import { createAuxTarget } from '../../compute/targets.js';
|
|
5
|
+
import { unpackAuxAlbedoFn, unpackAuxNormalFn, AUX_LAYERS } from '../../nodes/aux.wgsl.js';
|
|
6
|
+
|
|
7
|
+
// Shows the albedo or normal the path tracer accumulates for the denoiser
|
|
8
|
+
export class AuxDebugMaterial extends MeshBasicNodeMaterial {
|
|
9
|
+
|
|
10
|
+
set texture( v ) {
|
|
11
|
+
|
|
12
|
+
this._texNode.value = v;
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
set showNormal( v ) {
|
|
17
|
+
|
|
18
|
+
this._showNormalUniform.value = v ? 1 : 0;
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
|
|
24
|
+
super();
|
|
25
|
+
|
|
26
|
+
this.blending = NoBlending;
|
|
27
|
+
|
|
28
|
+
this._texNode = texture( createAuxTarget() );
|
|
29
|
+
this._showNormalUniform = uniform( 0, 'uint' );
|
|
30
|
+
|
|
31
|
+
const auxDebug = wgslTagFn/* wgsl */`
|
|
32
|
+
fn auxDebug( tex: texture_2d_array<u32>, coord: vec2f, showNormal: u32 ) -> vec4f {
|
|
33
|
+
|
|
34
|
+
// the rows are stored top down, so the quad's bottom up uv is flipped
|
|
35
|
+
let texel = vec2u( vec2f( coord.x, 1.0 - coord.y ) * vec2f( textureDimensions( tex, 0 ) ) );
|
|
36
|
+
var packed: vec3u;
|
|
37
|
+
for ( var layer = 0u; layer < ${ AUX_LAYERS }u; layer ++ ) {
|
|
38
|
+
|
|
39
|
+
packed[ layer ] = textureLoad( tex, texel, layer, 0 ).r;
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let albedo = ${ unpackAuxAlbedoFn }( packed );
|
|
44
|
+
let normal = ${ unpackAuxNormalFn }( packed );
|
|
45
|
+
return vec4f( select( albedo, normal, showNormal == 1u ), 1.0 );
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
this.colorNode = auxDebug( this._texNode, varying( uv() ), this._showNormalUniform );
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
}
|
|
@@ -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,25 @@ 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. Shadow rays also record the share of bsdf rays the faces they skipped
|
|
23
|
+
// would stop, and the share of light the partially transparent surfaces they passed block
|
|
24
|
+
const intersectionResultStruct = new StructTypeNode( {
|
|
25
|
+
indices: 'vec4u',
|
|
26
|
+
normal: 'vec3f',
|
|
27
|
+
didHit: 'bool',
|
|
28
|
+
barycoord: 'vec3f',
|
|
29
|
+
objectIndex: 'uint',
|
|
30
|
+
side: 'float',
|
|
31
|
+
dist: 'float',
|
|
32
|
+
skippedOcclusion: 'float',
|
|
33
|
+
occlusion: 'float',
|
|
34
|
+
}, 'PathTracerIntersectionResult' );
|
|
35
|
+
|
|
22
36
|
// Pathtracer-specific version of the BVHComputeData tht includes material mapping, property structs
|
|
23
37
|
export class PathtracerBVHComputeData extends BVHComputeData {
|
|
24
38
|
|
|
@@ -167,7 +181,7 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
167
181
|
|
|
168
182
|
const { textureAtlas, storage, fns } = this;
|
|
169
183
|
const textures = textureAtlas.texture;
|
|
170
|
-
const textureInfo = uniformArray( textureAtlas.textureInfo, 'uvec4' );
|
|
184
|
+
const textureInfo = uniformArray( textureAtlas.textureInfo, 'uvec4' ).setName( 'bvh_textureInfo' );
|
|
171
185
|
|
|
172
186
|
// build the single sampleTexel bound to this instance's textureInfo node
|
|
173
187
|
const sampleTexel = sampleTexelFunc( textureInfo, texture( textures ) );
|
|
@@ -181,8 +195,12 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
181
195
|
const scratchRayScalar = float( 1.0 ).toVar( 'bvh_rayScalar' );
|
|
182
196
|
const baseOpacityScalar = float( 1.0 ).toVar( 'bvh_baseOpacity' );
|
|
183
197
|
|
|
184
|
-
|
|
185
|
-
|
|
198
|
+
// shadow rays hit both sides, pass through the objects not visible to them and partially transparent
|
|
199
|
+
// surfaces, and stop at the first opaque hit
|
|
200
|
+
const isShadowRay = bool( false ).toVar( 'bvh_isShadowRay' );
|
|
201
|
+
const skipShadowObject = bool( false ).toVar( 'bvh_skipShadowObject' );
|
|
202
|
+
|
|
203
|
+
const raycastOptions = {
|
|
186
204
|
shapeStruct: rayStruct,
|
|
187
205
|
resultStruct: intersectionResultStruct,
|
|
188
206
|
|
|
@@ -196,8 +214,8 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
196
214
|
intersectsBoundsFn: wgslTagFn/* wgsl */`
|
|
197
215
|
fn rayIntersectsBounds( ray: ${ rayStruct }, bounds: ${ bvhNodeBoundsStruct }, result: ptr<function, ${ intersectionResultStruct }> ) -> u32 {
|
|
198
216
|
|
|
199
|
-
// early-out if our object is completely transparent
|
|
200
|
-
if ( ${ baseOpacityScalar } == 0.0 ) {
|
|
217
|
+
// early-out if our object is completely transparent, or a shadow ray is already occluded
|
|
218
|
+
if ( ${ baseOpacityScalar } == 0.0 || ( ${ isShadowRay } && result.didHit ) ) {
|
|
201
219
|
|
|
202
220
|
return 0u;
|
|
203
221
|
|
|
@@ -259,16 +277,16 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
259
277
|
let material = ${ storage.materials }[ ${ currentMaterialIndex } ];
|
|
260
278
|
|
|
261
279
|
// TODO: if material is a transmissive volume we may need to assume double-sidedness
|
|
262
|
-
|
|
280
|
+
// shadow rays hit both sides
|
|
281
|
+
if ( ! ${ isShadowRay } && material.side != 0 && triResult.side != material.side ) {
|
|
263
282
|
|
|
264
283
|
continue;
|
|
265
284
|
|
|
266
285
|
}
|
|
267
286
|
|
|
287
|
+
var opacity = ${ baseOpacityScalar };
|
|
268
288
|
if ( material.transparent != 0 || material.alphaTest > 0.0 ) {
|
|
269
289
|
|
|
270
|
-
var opacity = ${ baseOpacityScalar };
|
|
271
|
-
|
|
272
290
|
// add support for vertex color opacity
|
|
273
291
|
if ( material.vertexColors == 1 ) {
|
|
274
292
|
|
|
@@ -325,6 +343,29 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
325
343
|
|
|
326
344
|
}
|
|
327
345
|
|
|
346
|
+
// shadow rays skip the objects not visible to them. Record the share of bsdf rays this face
|
|
347
|
+
// would stop: all of them if it's opaque, its opacity if it's partially transparent
|
|
348
|
+
if ( ${ skipShadowObject } ) {
|
|
349
|
+
|
|
350
|
+
if ( material.side == 0 || triResult.side == material.side ) {
|
|
351
|
+
|
|
352
|
+
result.skippedOcclusion += ( 1.0 - result.skippedOcclusion ) * select( 1.0, opacity, material.transparent != 0 );
|
|
353
|
+
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
continue;
|
|
357
|
+
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// shadow rays pass partially transparent surfaces, each blocking its opacity's share of the
|
|
361
|
+
// light still getting through, just as bsdf rays pass them by chance
|
|
362
|
+
if ( ${ isShadowRay } && material.transparent != 0 && opacity < 1.0 ) {
|
|
363
|
+
|
|
364
|
+
result.occlusion += ( 1.0 - result.occlusion ) * opacity;
|
|
365
|
+
continue;
|
|
366
|
+
|
|
367
|
+
}
|
|
368
|
+
|
|
328
369
|
result.didHit = true;
|
|
329
370
|
result.dist = triResult.dist;
|
|
330
371
|
result.normal = triResult.normal;
|
|
@@ -367,6 +408,8 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
367
408
|
|
|
368
409
|
}
|
|
369
410
|
|
|
411
|
+
${ skipShadowObject } = ${ isShadowRay } && object.visibleToShadowRays == 0u;
|
|
412
|
+
|
|
370
413
|
}
|
|
371
414
|
`,
|
|
372
415
|
transformResultFn: wgslTagFn/* wgsl */`
|
|
@@ -384,6 +427,30 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
384
427
|
${ scratchRayScalar } = 1.0;
|
|
385
428
|
${ baseOpacityScalar } = 1.0;
|
|
386
429
|
|
|
430
|
+
}
|
|
431
|
+
`,
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
fns.raycastFirstHit = this.getShapecastFn( {
|
|
435
|
+
...raycastOptions,
|
|
436
|
+
name: 'raycastFirstHit',
|
|
437
|
+
prefixFn: wgslTagFn/* wgsl */`
|
|
438
|
+
fn initFirstHitCull() -> void {
|
|
439
|
+
|
|
440
|
+
${ isShadowRay } = false;
|
|
441
|
+
|
|
442
|
+
}
|
|
443
|
+
`,
|
|
444
|
+
} );
|
|
445
|
+
|
|
446
|
+
fns.raycastShadow = this.getShapecastFn( {
|
|
447
|
+
...raycastOptions,
|
|
448
|
+
name: 'raycastShadow',
|
|
449
|
+
prefixFn: wgslTagFn/* wgsl */`
|
|
450
|
+
fn initShadowCull() -> void {
|
|
451
|
+
|
|
452
|
+
${ isShadowRay } = true;
|
|
453
|
+
|
|
387
454
|
}
|
|
388
455
|
`,
|
|
389
456
|
} );
|
|
@@ -398,6 +465,9 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
398
465
|
|
|
399
466
|
this.updateUvAttributesFromScene();
|
|
400
467
|
|
|
468
|
+
// the material indices are written with the transforms
|
|
469
|
+
this.updateMaterialsMap();
|
|
470
|
+
|
|
401
471
|
super.update();
|
|
402
472
|
|
|
403
473
|
// build the channel -> uv lookup now that the geometry struct (and its uv members) exist
|
|
@@ -423,7 +493,7 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
423
493
|
|
|
424
494
|
if ( Array.isArray( o.material ) ) {
|
|
425
495
|
|
|
426
|
-
o.material.forEach( m => add( m ) );
|
|
496
|
+
o.material.forEach( m => m && add( m ) );
|
|
427
497
|
|
|
428
498
|
} else {
|
|
429
499
|
|
|
@@ -716,9 +786,8 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
716
786
|
floatArray[ index ++ ] = getField( m, 'iridescenceIOR', 1.3 );
|
|
717
787
|
|
|
718
788
|
// offset 32
|
|
719
|
-
|
|
720
|
-
floatArray[ index ++ ] =
|
|
721
|
-
floatArray[ index ++ ] = iridescenceThicknessRange[ 1 ];
|
|
789
|
+
floatArray[ index ++ ] = getField( m, 'iridescenceThicknessMinimum', 100 );
|
|
790
|
+
floatArray[ index ++ ] = getField( m, 'iridescenceThicknessMaximum', 400 );
|
|
722
791
|
floatArray[ index ++ ] = getField( m, 'diffuseRoughness', 0.0 );
|
|
723
792
|
floatArray[ index ++ ] = getField( m, 'dispersion', 0.0 );
|
|
724
793
|
|
|
@@ -885,6 +954,17 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
885
954
|
// anisotropyMap transform - offset 264
|
|
886
955
|
index += writeTextureMatrixToArray( m, 'anisotropyMap', floatArray, index );
|
|
887
956
|
|
|
957
|
+
// scattering volume - offset 276
|
|
958
|
+
const multiscatterColor = getField( m, 'multiscatterColor', null );
|
|
959
|
+
floatArray[ index ++ ] = multiscatterColor ? multiscatterColor.r : 1.0;
|
|
960
|
+
floatArray[ index ++ ] = multiscatterColor ? multiscatterColor.g : 1.0;
|
|
961
|
+
floatArray[ index ++ ] = multiscatterColor ? multiscatterColor.b : 1.0;
|
|
962
|
+
floatArray[ index ++ ] = getField( m, 'scatterStrength', 0.0 );
|
|
963
|
+
floatArray[ index ++ ] = getField( m, 'scatterAnisotropy', 0.0 );
|
|
964
|
+
|
|
965
|
+
// padding to the struct's 16 byte alignment
|
|
966
|
+
index += 3;
|
|
967
|
+
|
|
888
968
|
}
|
|
889
969
|
|
|
890
970
|
return { materialData: intArray, textures };
|
|
@@ -906,9 +986,17 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
906
986
|
|
|
907
987
|
}
|
|
908
988
|
|
|
909
|
-
// save the index
|
|
910
|
-
const index = materialsMap.get( material ) || 0;
|
|
911
989
|
const transformBufferU32 = new Uint32Array( targetBuffer );
|
|
990
|
+
|
|
991
|
+
const index = materialsMap.has( material ) ? materialsMap.get( material ) : - 1;
|
|
992
|
+
if ( index === - 1 ) {
|
|
993
|
+
|
|
994
|
+
// hide the object when there is no material
|
|
995
|
+
transformBufferU32[ writeOffset * transformStruct.getLength() + 32 ] = 0;
|
|
996
|
+
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// save the material index
|
|
912
1000
|
transformBufferU32[ writeOffset * transformStruct.getLength() + 33 ] = index;
|
|
913
1001
|
|
|
914
1002
|
// write color
|
|
@@ -939,6 +1027,10 @@ export class PathtracerBVHComputeData extends BVHComputeData {
|
|
|
939
1027
|
super.writeTransformData( info, premultiplyMatrix, writeOffset, targetBuffer );
|
|
940
1028
|
this.writeMaterialData( info, writeOffset, targetBuffer );
|
|
941
1029
|
|
|
1030
|
+
// whether shadow rays see the object, in the slot after the material index
|
|
1031
|
+
const transformBufferU32 = new Uint32Array( targetBuffer );
|
|
1032
|
+
transformBufferU32[ writeOffset * this.structs.transform.getLength() + 34 ] = info.object.visibleToShadowRays === false ? 0 : 1;
|
|
1033
|
+
|
|
942
1034
|
}
|
|
943
1035
|
|
|
944
1036
|
getBVH( object, instanceId, rangeTarget ) {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
2
|
+
import { scatterRecordStruct, surfaceRecordStruct } from './structs.wgsl.js';
|
|
3
|
+
|
|
4
|
+
// The denoiser's auxiliary images, the first hit albedo and normal, accumulate in an "r32uint" array
|
|
5
|
+
// texture, the one kind of storage texture core WebGPU can both read and write. Each layer packs two
|
|
6
|
+
// halves: albedo.rg, then albedo.b and normal.z, then normal.xy. Normals are mapped to [0,1].
|
|
7
|
+
export const AUX_LAYERS = 3;
|
|
8
|
+
|
|
9
|
+
// the smooth metals the auxiliary images look past to what they reflect, as in mutracer
|
|
10
|
+
const AUX_MIRROR_MAX_ROUGHNESS = 0.02;
|
|
11
|
+
const AUX_MIRROR_MIN_METALNESS = 0.98;
|
|
12
|
+
|
|
13
|
+
// whether a scatter shows its surface in the auxiliary images, rather than looking past it through a
|
|
14
|
+
// transmission or a mirror
|
|
15
|
+
export const isAuxSurfaceFn = wgslTagFn/* wgsl */`
|
|
16
|
+
|
|
17
|
+
fn isAuxSurface( surface: ${ surfaceRecordStruct }, scatterRec: ${ scatterRecordStruct } ) -> bool {
|
|
18
|
+
|
|
19
|
+
let isMirror = surface.roughness <= ${ AUX_MIRROR_MAX_ROUGHNESS } && surface.metalness >= ${ AUX_MIRROR_MIN_METALNESS };
|
|
20
|
+
return ! scatterRec.isTransmissive && ! isMirror;
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
export const unpackAuxAlbedoFn = wgslTagFn/* wgsl */`
|
|
27
|
+
|
|
28
|
+
fn unpackAuxAlbedo( packed: vec3u ) -> vec3f {
|
|
29
|
+
|
|
30
|
+
return vec3f( unpack2x16float( packed.x ), unpack2x16float( packed.y ).x );
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
export const unpackAuxNormalFn = wgslTagFn/* wgsl */`
|
|
37
|
+
|
|
38
|
+
fn unpackAuxNormal( packed: vec3u ) -> vec3f {
|
|
39
|
+
|
|
40
|
+
return vec3f( unpack2x16float( packed.z ), unpack2x16float( packed.y ).y );
|
|
41
|
+
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
// blends a sample's albedo and normal into the packed running means
|
|
47
|
+
export const blendAuxFn = wgslTagFn/* wgsl */`
|
|
48
|
+
|
|
49
|
+
fn blendAux( packed: vec3u, albedo: vec3f, normal: vec3f, weight: f32 ) -> vec3u {
|
|
50
|
+
|
|
51
|
+
let meanAlbedo = mix( ${ unpackAuxAlbedoFn }( packed ), albedo, weight );
|
|
52
|
+
let meanNormal = mix( ${ unpackAuxNormalFn }( packed ), normal * 0.5 + 0.5, weight );
|
|
53
|
+
return vec3u(
|
|
54
|
+
pack2x16float( meanAlbedo.rg ),
|
|
55
|
+
pack2x16float( vec2f( meanAlbedo.b, meanNormal.z ) ),
|
|
56
|
+
pack2x16float( meanNormal.xy ),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
`;
|
|
@@ -119,7 +119,7 @@ const eonLtcBasisFunc = wgslFn( /* wgsl */ `
|
|
|
119
119
|
fn eonLtcBasis( wo: vec3f ) -> mat3x3f {
|
|
120
120
|
|
|
121
121
|
let lenSqr = dot( wo.xy, wo.xy );
|
|
122
|
-
let invLen = inverseSqrt(
|
|
122
|
+
let invLen = inverseSqrt( lenSqr );
|
|
123
123
|
let x = select(
|
|
124
124
|
vec3f( 1.0, 0.0, 0.0 ),
|
|
125
125
|
vec3f( wo.x, wo.y, 0.0 ) * invLen,
|
|
@@ -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 ] );
|
|
@@ -848,7 +848,8 @@ export const transmissionFresnelFunc = ( iridescentFresnel = iridescentFresnelFu
|
|
|
848
848
|
// Include the base-color tint in the transmission energy, as Cycles does when it
|
|
849
849
|
// computes the glass reflection probability from its two tinted coefficients.
|
|
850
850
|
let reflectEnergy = dot( reflectance, vec3f( 1.0 / 3.0 ) );
|
|
851
|
-
let
|
|
851
|
+
let transmissionTint = select( sqrt( surf.color ), surf.color, surf.thinWall );
|
|
852
|
+
let transmitEnergy = dot( transmittance * transmissionTint, vec3f( 1.0 / 3.0 ) );
|
|
852
853
|
let totalEnergy = reflectEnergy + transmitEnergy;
|
|
853
854
|
result.reflectProbability = select( 0.0, reflectEnergy / max( totalEnergy, EPSILON ), totalEnergy > EPSILON );
|
|
854
855
|
|