three-gpu-pathtracer 0.0.24 → 0.0.25

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.
Files changed (73) hide show
  1. package/README.md +53 -530
  2. package/build/index.module.js +262 -50
  3. package/build/index.module.js.map +1 -1
  4. package/build/index.umd.cjs +261 -48
  5. package/build/index.umd.cjs.map +1 -1
  6. package/package.json +28 -11
  7. package/src/core/WebGLPathTracer.js +8 -0
  8. package/src/core/utils/sceneUpdateUtils.js +8 -12
  9. package/src/detectors/PrecisionMaterial.js +9 -9
  10. package/src/index.js +1 -0
  11. package/src/objects/PhysicalSpotLight.js +2 -0
  12. package/src/shader/bsdf/bsdf_functions.glsl.js +0 -1
  13. package/src/textures/BlueNoiseTexture.js +6 -6
  14. package/src/textures/ProceduralEquirectTexture.js +8 -7
  15. package/src/textures/turquinMetal.png +0 -0
  16. package/src/uniforms/EquirectHdrInfoUniform.js +9 -4
  17. package/src/uniforms/FloatAttributeTextureArray.js +11 -11
  18. package/src/uniforms/MaterialsTexture.js +9 -9
  19. package/src/webgpu/API.md +762 -0
  20. package/src/webgpu/AtlasTexture.js +471 -0
  21. package/src/webgpu/BlurredEnvMapGenerator.js +129 -0
  22. package/src/webgpu/EquirectBackgroundInfo.js +97 -0
  23. package/src/webgpu/EquirectHdrInfoNode.js +151 -0
  24. package/src/webgpu/LightsInfoNode.js +224 -0
  25. package/src/webgpu/MegaKernelPathTracer.js +277 -0
  26. package/src/webgpu/PathTracerBackend.js +216 -0
  27. package/src/webgpu/TurquinTexture.js +169 -0
  28. package/src/webgpu/WaveFrontPathTracer.js +586 -0
  29. package/src/webgpu/WebGPUPathTracer.js +1302 -0
  30. package/src/webgpu/compute/ComputeKernel.js +92 -0
  31. package/src/webgpu/compute/CopyBufferKernel.js +40 -0
  32. package/src/webgpu/compute/PathTracerMegaKernel.js +470 -0
  33. package/src/webgpu/compute/SampleDebugKernel.js +51 -0
  34. package/src/webgpu/compute/TallySampleCountsKernel.js +111 -0
  35. package/src/webgpu/compute/ZeroOutBufferKernel.js +35 -0
  36. package/src/webgpu/compute/ZeroOutKernel.js +31 -0
  37. package/src/webgpu/compute/wavefront/LogicKernel.js +349 -0
  38. package/src/webgpu/compute/wavefront/MaterialKernel.js +371 -0
  39. package/src/webgpu/compute/wavefront/PopulatePixelIndicesKernel.js +72 -0
  40. package/src/webgpu/compute/wavefront/QueueLengthToDispatchKernel.js +38 -0
  41. package/src/webgpu/compute/wavefront/ResetSlotsKernel.js +66 -0
  42. package/src/webgpu/compute/wavefront/TraceRayKernel.js +71 -0
  43. package/src/webgpu/compute/wavefront/TraceShadowRayKernel.js +67 -0
  44. package/src/webgpu/compute/wavefront/structs.js +155 -0
  45. package/src/webgpu/constants.js +62 -0
  46. package/src/webgpu/denoise/OIDNDenoiser.js +431 -0
  47. package/src/webgpu/index.d.ts +176 -0
  48. package/src/webgpu/index.js +11 -0
  49. package/src/webgpu/materials/GltfCompliantMaterial.js +608 -0
  50. package/src/webgpu/materials/GraphMaterial.js +331 -0
  51. package/src/webgpu/materials/PathtracingMaterial.js +89 -0
  52. package/src/webgpu/materials/RenderToScreenMaterial.js +150 -0
  53. package/src/webgpu/materials/debug/AtlasDebugMaterial.js +41 -0
  54. package/src/webgpu/materials/debug/SampleDensityMaterial.js +61 -0
  55. package/src/webgpu/nodes/PathtracerBVHComputeData.js +1037 -0
  56. package/src/webgpu/nodes/debugBounds.wgsl.js +228 -0
  57. package/src/webgpu/nodes/eon.wgsl.js +262 -0
  58. package/src/webgpu/nodes/ggx.wgsl.js +190 -0
  59. package/src/webgpu/nodes/lights.wgsl.js +203 -0
  60. package/src/webgpu/nodes/material.wgsl.js +990 -0
  61. package/src/webgpu/nodes/rand/bluedither.wgsl.js +92 -0
  62. package/src/webgpu/nodes/rand/pcg.wgsl.js +81 -0
  63. package/src/webgpu/nodes/rand/sobol.wgsl.js +287 -0
  64. package/src/webgpu/nodes/random.wgsl.js +20 -0
  65. package/src/webgpu/nodes/reset.wgsl.js +21 -0
  66. package/src/webgpu/nodes/sampling.wgsl.js +193 -0
  67. package/src/webgpu/nodes/sheen.wgsl.js +148 -0
  68. package/src/webgpu/nodes/structs.wgsl.js +272 -0
  69. package/src/webgpu/nodes/utils.wgsl.js +366 -0
  70. package/src/webgpu/shims/ArrayCameraShim.js +74 -0
  71. package/src/webgpu/shims/EquirectCameraShim.js +37 -0
  72. package/src/webgpu/shims/PhysicalCameraShim.js +151 -0
  73. package/src/webgpu/upscale/FSRUpscaler.js +108 -0
@@ -0,0 +1,366 @@
1
+ import { wgslFn } from 'three/tsl';
2
+ import { scatterRecordStruct } from './structs.wgsl.js';
3
+ import { wgslTagFn } from 'three-mesh-bvh/webgpu';
4
+
5
+ export const inverseMat3x3Func = wgslFn( /* wgsl */ `
6
+
7
+ fn inverse(m: mat3x3f) -> mat3x3f {
8
+ var adj: mat3x3f;
9
+ adj[0][0] = (m[1][1] * m[2][2] - m[2][1] * m[1][2]);
10
+ adj[1][0] = - (m[1][0] * m[2][2] - m[2][0] * m[1][2]);
11
+ adj[2][0] = (m[1][0] * m[2][1] - m[2][0] * m[1][1]);
12
+ adj[0][1] = - (m[0][1] * m[2][2] - m[2][1] * m[0][2]);
13
+ adj[1][1] = (m[0][0] * m[2][2] - m[2][0] * m[0][2]);
14
+ adj[2][1] = - (m[0][0] * m[2][1] - m[2][0] * m[0][1]);
15
+ adj[0][2] = (m[0][1] * m[1][2] - m[1][1] * m[0][2]);
16
+ adj[1][2] = - (m[0][0] * m[1][2] - m[1][0] * m[0][2]);
17
+ adj[2][2] = (m[0][0] * m[1][1] - m[1][0] * m[0][1]);
18
+
19
+ let det = ( m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
20
+ - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
21
+ + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]));
22
+
23
+ return adj * ( 1.0 / det );
24
+ }
25
+
26
+ ` );
27
+
28
+ export const getBasisFromNormalFunc = wgslFn( /* wgsl */ `
29
+
30
+ fn getBasisFromNormal( normal: vec3f ) -> mat3x3f {
31
+
32
+ var other: vec3f;
33
+ if ( abs( normal.x ) > 0.5 ) {
34
+
35
+ other = vec3f( 0.0, 1.0, 0.0 );
36
+
37
+ } else {
38
+
39
+ other = vec3f( 1.0, 0.0, 0.0 );
40
+
41
+ }
42
+
43
+ let ortho = normalize( cross( normal, other ) );
44
+ let ortho2 = normalize( cross( normal, ortho ) );
45
+ return mat3x3f( ortho2, ortho, normal );
46
+
47
+ }
48
+
49
+ ` );
50
+
51
+ export const iorToF0Func = wgslFn( /* wgsl */ `
52
+
53
+ fn iorToF0( ior: f32 ) -> f32 {
54
+ return pow( ( 1 - ior ) / ( 1 + ior ), 2 );
55
+ }
56
+
57
+ ` );
58
+
59
+ export const iorToF0GeneralFunc = wgslFn( /* wgsl */ `
60
+
61
+ fn iorToF0General( transmittedIor: f32, incidentIor: f32 ) -> f32 {
62
+
63
+ return pow( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor ), 2 );
64
+
65
+ }
66
+
67
+ ` );
68
+
69
+ export const iorToF0GeneralVecFunc = wgslFn( /* wgsl */ `
70
+
71
+ fn iorToF0GeneralVec( transmittedIor: vec3f, incidentIor: vec3f ) -> vec3f {
72
+
73
+ let v = ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor );
74
+ return v * v;
75
+
76
+ }
77
+
78
+ ` );
79
+
80
+ export const fresnel0ToIorFunc = wgslFn( /* wgsl */ `
81
+
82
+ fn fresnel0ToIor( f0: vec3f ) -> vec3f {
83
+
84
+ let sqrtF0 = sqrt( f0 );
85
+ return ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 );
86
+
87
+ }
88
+
89
+ ` );
90
+
91
+ export const schlickFresnelFunc = wgslFn( /* wgsl */ `
92
+
93
+ fn schlickFresnel( cosine: f32, f0: f32 ) -> f32 {
94
+
95
+ return f0 + ( 1.0 - f0 ) * pow( 1.0 - cosine, 5.0 );
96
+
97
+ }
98
+
99
+ ` );
100
+
101
+ export const schlickFresnelVecFunc = wgslFn( /* wgsl */ `
102
+
103
+ fn schlickFresnelVec( cosine: f32, f0: vec3f, f90: vec3f ) -> vec3f {
104
+
105
+ return f0 + ( f90 - f0 ) * pow( 1.0 - cosine, 5.0 );
106
+
107
+ }
108
+
109
+ ` );
110
+
111
+ export const totalInternalReflectionFunc = wgslFn( /* wgsl */ `
112
+
113
+ fn totalInternalReflection( cosTheta: f32, eta: f32 ) -> bool {
114
+
115
+ let sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
116
+ return eta * sinTheta > 1.0;
117
+
118
+ }
119
+
120
+ ` );
121
+
122
+ export const totalInternalReflectionVecFunc = wgslFn( /* wgsl */ `
123
+
124
+ fn totalInternalReflectionVec( cosTheta: f32, eta: vec3f ) -> vec3<bool> {
125
+
126
+ let sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
127
+ return eta * sinTheta > vec3f( 1.0 );
128
+
129
+ }
130
+
131
+ ` );
132
+
133
+ export const evaluateFresnelFunc = wgslFn( /* wgsl */ `
134
+
135
+ fn evaluateFresnel( cosine: f32, eta: f32, f0: vec3f, f90: vec3f ) -> vec3f {
136
+
137
+ if ( totalInternalReflection( cosine, eta ) ) {
138
+
139
+ return f90;
140
+
141
+ }
142
+
143
+ return f0 + ( f90 - f0 ) * pow( 1.0 - cosine, 5.0 );
144
+ }
145
+
146
+ `, [ totalInternalReflectionFunc ] );
147
+
148
+ export const dielectricFresnelFunc = wgslFn( /* wgsl */ `
149
+
150
+ fn dielectricFresnel( cosThetaI: f32, eta: f32 ) -> f32 {
151
+
152
+ // https://schuttejoe.github.io/post/disneybsdf/
153
+ let ni = eta;
154
+ let nt = 1.0;
155
+
156
+ // Check for total internal reflection
157
+ let sinThetaISq = 1.0 - cosThetaI * cosThetaI;
158
+ let sinThetaTSq = eta * eta * sinThetaISq;
159
+ if ( sinThetaTSq >= 1.0 ) {
160
+
161
+ return 1.0;
162
+
163
+ }
164
+
165
+ let sinThetaT = sqrt( sinThetaTSq );
166
+ let cosThetaT = sqrt( max( 0.0, 1.0 - sinThetaT * sinThetaT ) );
167
+ let rParallel = ( ( nt * cosThetaI ) - ( ni * cosThetaT ) ) / ( ( nt * cosThetaI ) + ( ni * cosThetaT ) );
168
+ let rPerpendicular = ( ( ni * cosThetaI ) - ( nt * cosThetaT ) ) / ( ( ni * cosThetaI ) + ( nt * cosThetaT ) );
169
+ return ( rParallel * rParallel + rPerpendicular * rPerpendicular ) / 2.0;
170
+
171
+ }
172
+
173
+ ` );
174
+
175
+ export const disneyFresnelFunc = wgslFn( /* wgsl */ `
176
+
177
+ fn disneyFresnel( wo: vec3f, wi: vec3f, wh: vec3f, f0: f32, eta: f32, metalness: f32 ) -> f32 {
178
+
179
+ let dotHV = dot( wo, wh );
180
+ if ( totalInternalReflection( dotHV, eta ) ) {
181
+
182
+ return 1.0;
183
+
184
+ }
185
+
186
+ let dotHL = dot( wi, wh );
187
+ let dielectricF = dielectricFresnel( abs( dotHV ), eta );
188
+ let metallicF = schlickFresnel( dotHL, f0 );
189
+
190
+ return mix( dielectricF, metallicF, metalness );
191
+
192
+ }
193
+
194
+ `, [ totalInternalReflectionFunc, dielectricFresnelFunc, schlickFresnelFunc ] );
195
+
196
+ export const isTerminatingScatterFunc = wgslFn( /* wgsl */ `
197
+
198
+ fn isTerminatingScatter( scatterRec: ScatterRecord ) -> bool {
199
+
200
+ return scatterRec.pdf <= 0;
201
+
202
+ }
203
+
204
+ `, [ scatterRecordStruct ] );
205
+
206
+ // Clamp individual light-path contributions using Cycles' RGB sum while preserving chromaticity.
207
+ // Matches film_clamp_light in Cycles' kernel/film/light_passes.h, including the x3 limit
208
+ // scaling applied to the user-facing clamp values in scene/integrator.cpp.
209
+ export const clampPathContributionFunc = wgslFn( /* wgsl */ `
210
+
211
+ fn clampPathContribution( contribution: vec3f, pathDepth: u32, clampDirect: f32, clampIndirect: f32 ) -> vec3f {
212
+
213
+ let limit = select( clampIndirect, clampDirect, pathDepth <= 1u ) * 3.0;
214
+ if ( limit <= 0.0 ) {
215
+
216
+ return contribution;
217
+
218
+ }
219
+
220
+ let strength = dot( abs( contribution ), vec3f( 1.0 ) );
221
+ if ( strength > limit ) {
222
+
223
+ return contribution * ( limit / strength );
224
+
225
+ }
226
+
227
+ return contribution;
228
+
229
+ }
230
+
231
+ ` );
232
+
233
+ export const applyWrapFunc = wgslFn( /* wgsl */ `
234
+
235
+ fn applyWrap( v: f32, wrapMode: i32 ) -> f32 {
236
+
237
+ var res = v;
238
+ if ( wrapMode == 1 ) {
239
+
240
+ // ClampToEdge
241
+ res = clamp( res, 0.0, 1.0 );
242
+
243
+ } else if ( wrapMode == 2 ) {
244
+
245
+ // MirroredRepeat
246
+ res = 1.0 - abs( 2.0 * fract( res * 0.5 ) - 1.0 );
247
+
248
+ } else {
249
+
250
+ // Repeat
251
+ res = fract( res );
252
+
253
+ }
254
+
255
+ return res;
256
+
257
+ }
258
+
259
+ ` );
260
+
261
+ // Bit-level ray origin offset based on Section 6.2.2 of "A Fast and Robust
262
+ // Method for Avoiding Self-Intersection" in Ray Tracing Gems:
263
+ // https://github.com/Apress/ray-tracing-gems/blob/master/Ch_06_A_Fast_and_Robust_Method_for_Avoiding_Self-Intersection/offset_ray.cu
264
+ // The original implementation expects a normal oriented for the outgoing ray;
265
+ // this version orients the geometric normal internally.
266
+ export const offsetRayOriginFunc = wgslFn( /* wgsl */ `
267
+
268
+ fn offsetRayOrigin( point: vec3f, direction: vec3f, geometricNormal: vec3f ) -> vec3f {
269
+
270
+ let normal = normalize( select( -geometricNormal, geometricNormal, dot( direction, geometricNormal ) >= 0.0 ) );
271
+ let intScale = 256.0;
272
+ let integerOffset = vec3i( intScale * normal );
273
+ let pointBits = bitcast<vec3i>( point );
274
+ let signedOffset = select( integerOffset, -integerOffset, point < vec3f( 0.0 ) );
275
+ let offsetPoint = bitcast<vec3f>( pointBits + signedOffset );
276
+ let origin = 1.0 / 32.0;
277
+ let floatScale = 1.0 / 65536.0;
278
+ return select( offsetPoint, point + floatScale * normal, abs( point ) < vec3f( origin ) );
279
+
280
+ }
281
+
282
+ ` );
283
+
284
+ // Wraps a bilinear tap that is off the edge of a tile back to the texel the wrap mode calls for
285
+ export const wrapTexelIndexFunc = wgslFn( /* wgsl */ `
286
+
287
+ fn wrapTexelIndex( i: i32, size: i32, wrapMode: i32 ) -> i32 {
288
+
289
+ if ( wrapMode == 0 ) {
290
+
291
+ // Repeat wraps to the opposite edge
292
+ return ( ( i % size ) + size ) % size;
293
+
294
+ }
295
+
296
+ // ClampToEdge and MirroredRepeat both fold back onto the edge texel
297
+ return clamp( i, 0, size - 1 );
298
+
299
+ }
300
+
301
+ ` );
302
+
303
+ // Factory: builds sampleTexel bound to the given per-instance textureInfo uniform
304
+ // array node ( must be named "textureInfo" ). Called once per scene so a single
305
+ // sampleTexel / textureInfo binding is shared by every caller in a pipeline. A distinct
306
+ // name must be given when a second atlas binding is used in the same pipeline.
307
+ export const sampleTexelFunc = ( textureInfoUniform, atlas, name = 'sampleTexel' ) => wgslTagFn/* wgsl */ `
308
+
309
+ fn ${ name }( uv: vec2f, packed: i32, lod: f32 ) -> vec4f {
310
+
311
+ let texIndex = packed & 0x7FFFFF;
312
+ let wrapS = ( packed >> 26 ) & 0x3;
313
+ let wrapT = ( packed >> 28 ) & 0x3;
314
+ let nearest = ( packed >> 30 ) & 0x1;
315
+
316
+ // look up the texture's rect and page within the atlas. three wraps a
317
+ // uniformArray in a struct ( textureInfoStruct ) with a "value" array member.
318
+ let info = ${ textureInfoUniform }[ u32( texIndex ) ];
319
+ let offset = vec2f( vec2u( info.x & 0xFFFFu, info.x >> 16u ) );
320
+ let size = vec2f( vec2u( info.y & 0xFFFFu, info.y >> 16u ) );
321
+ let page = i32( info.z & 0xFFFFu );
322
+
323
+ // wrap is applied on the logical uv first, then remapped into the tile
324
+ let wrappedUv = vec2f(
325
+ ${ applyWrapFunc }( uv.x, wrapS ),
326
+ ${ applyWrapFunc }( uv.y, wrapT ),
327
+ );
328
+
329
+ if ( nearest == 1 ) {
330
+
331
+ let tileTexel = clamp( vec2i( wrappedUv * size ), vec2i( 0 ), vec2i( size ) - vec2i( 1 ) );
332
+ return textureLoad( ${ atlas }, vec2i( offset ) + tileTexel, page, 0 );
333
+
334
+ } else {
335
+
336
+ // The tile's neighbors in the atlas are unrelated textures, so hardware
337
+ // filtering cannot supply the taps that fall outside it. Filter here instead
338
+ // and resolve each tap through the wrap mode.
339
+ let texelPos = wrappedUv * size - vec2f( 0.5 );
340
+ let basePos = floor( texelPos );
341
+ let base = vec2i( basePos );
342
+ let f = texelPos - basePos;
343
+
344
+ let intSize = vec2i( size );
345
+ let x0 = ${ wrapTexelIndexFunc }( base.x, intSize.x, wrapS );
346
+ let x1 = ${ wrapTexelIndexFunc }( base.x + 1, intSize.x, wrapS );
347
+ let y0 = ${ wrapTexelIndexFunc }( base.y, intSize.y, wrapT );
348
+ let y1 = ${ wrapTexelIndexFunc }( base.y + 1, intSize.y, wrapT );
349
+
350
+ let tile = vec2i( offset );
351
+ let c00 = textureLoad( ${ atlas }, tile + vec2i( x0, y0 ), page, 0 );
352
+ let c10 = textureLoad( ${ atlas }, tile + vec2i( x1, y0 ), page, 0 );
353
+ let c01 = textureLoad( ${ atlas }, tile + vec2i( x0, y1 ), page, 0 );
354
+ let c11 = textureLoad( ${ atlas }, tile + vec2i( x1, y1 ), page, 0 );
355
+
356
+ return mix(
357
+ mix( c00, c10, f.x ),
358
+ mix( c01, c11, f.x ),
359
+ f.y,
360
+ );
361
+
362
+ }
363
+
364
+ }
365
+ `;
366
+
@@ -0,0 +1,74 @@
1
+ import { ArrayCamera, Matrix4, Vector4, WebGPUCoordinateSystem } from 'three';
2
+ import { uniformArray, int } from 'three/tsl';
3
+ import { ndcToCameraRay, rayStruct, wgslTagFn } from 'three-mesh-bvh/webgpu';
4
+
5
+ ArrayCamera.prototype.getCameraRayFn = function getCameraRayFn() {
6
+
7
+ const result = { fn: null, update: null };
8
+
9
+ const cameraCount = int( 0 );
10
+ const invViewProjectionMatrices = uniformArray( [], 'mat4' );
11
+ const viewports = uniformArray( [], 'vec4' );
12
+
13
+ result.fn = wgslTagFn/* wgsl */`
14
+ fn getCameraRay( uv: vec2f, resolution: vec2f, ray: ptr<function, ${ rayStruct }> ) -> bool {
15
+
16
+ let pixel = uv * resolution;
17
+
18
+ for ( var i = 0u; i < ${ cameraCount }u; i ++ ) {
19
+
20
+ let viewport = ${ viewports }[ i ];
21
+ let viewportMax = viewport.xy + viewport.zw;
22
+ if ( all( pixel >= viewport.xy ) && all( pixel < viewportMax ) ) {
23
+
24
+ let cameraUv = ( pixel - viewport.xy ) / viewport.zw;
25
+ let ndc = cameraUv * 2.0 - vec2f( 1.0 );
26
+ *ray = ${ ndcToCameraRay }( ndc, ${ invViewProjectionMatrices }[ i ] );
27
+ return true;
28
+
29
+ }
30
+
31
+ }
32
+
33
+ return false;
34
+
35
+ }
36
+ `;
37
+
38
+ result.update = () => {
39
+
40
+ const { cameras } = this;
41
+ const needsRebuild = cameraCount.node.value !== cameras.length;
42
+ cameraCount.node.value = cameras.length;
43
+
44
+ while ( viewports.array.length < cameras.length ) {
45
+
46
+ viewports.array.push( new Vector4() );
47
+ invViewProjectionMatrices.array.push( new Matrix4() );
48
+
49
+ }
50
+
51
+ invViewProjectionMatrices.array.length = cameras.length;
52
+ viewports.array.length = cameras.length;
53
+
54
+ for ( let i = 0; i < cameras.length; i ++ ) {
55
+
56
+ const camera = cameras[ i ];
57
+ const viewport = camera.viewport;
58
+
59
+ camera.coordinateSystem = WebGPUCoordinateSystem;
60
+ camera.updateMatrixWorld();
61
+ camera.updateProjectionMatrix();
62
+
63
+ invViewProjectionMatrices.array[ i ].multiplyMatrices( camera.matrixWorld, camera.projectionMatrixInverse );
64
+ viewports.array[ i ].copy( viewport );
65
+
66
+ }
67
+
68
+ return needsRebuild;
69
+
70
+ };
71
+
72
+ return result;
73
+
74
+ };
@@ -0,0 +1,37 @@
1
+ import { Matrix4 } from 'three';
2
+ import { uniform, PI } from 'three/tsl';
3
+ import { wgslTagFn, rayStruct } from 'three-mesh-bvh/webgpu';
4
+ import { EquirectCamera } from '../../objects/EquirectCamera.js';
5
+
6
+ EquirectCamera.prototype.getCameraRayFn = function getCameraRayFn() {
7
+
8
+ const cameraToWorld = uniform( new Matrix4() );
9
+ const fn = wgslTagFn/* wgsl */`
10
+ fn getCameraRay( uv: vec2f, resolution: vec2f, ray: ptr<function, ${ rayStruct }> ) -> bool {
11
+
12
+ // screen uv to spherical direction, matching three.js' equirect orientation
13
+ let theta = ( uv.x - 0.5 ) * 2.0 * ${ PI };
14
+ let phi = ( 1.0 - uv.y ) * ${ PI };
15
+ let sinPhi = sin( phi );
16
+ let direction = vec3f( sinPhi * cos( theta ), cos( phi ), sinPhi * sin( theta ) );
17
+
18
+ // equirect ignores the projection - orient by the camera world matrix and place the origin.
19
+ // there is no projection far plane, so the trace distance is unbounded
20
+ ray.origin = ( ${ cameraToWorld } * vec4f( 0.0, 0.0, 0.0, 1.0 ) ).xyz;
21
+ ray.direction = ( ${ cameraToWorld } * vec4f( direction, 0.0 ) ).xyz;
22
+ ray.maxDist = 0.0;
23
+ return true;
24
+
25
+ }
26
+ `;
27
+
28
+ const update = () => {
29
+
30
+ cameraToWorld.value.copy( this.matrixWorld );
31
+ return false;
32
+
33
+ };
34
+
35
+ return { fn, update };
36
+
37
+ };
@@ -0,0 +1,151 @@
1
+ import { Matrix4, WebGPUCoordinateSystem } from 'three';
2
+ import { uniform, PI } from 'three/tsl';
3
+ import { wgslTagFn, rayStruct, ndcToCameraRay } from 'three-mesh-bvh/webgpu';
4
+ import { rand3, RNG_INDEX_APERTURE_SAMPLE } from '../nodes/random.wgsl.js';
5
+ import { PhysicalCamera } from '../../objects/PhysicalCamera.js';
6
+
7
+ // aperture sampling helpers
8
+ const sampleCircle = wgslTagFn/* wgsl */`
9
+ fn sampleCircle( uv: vec2f ) -> vec2f {
10
+
11
+ let angle = 2.0 * ${ PI } * uv.x;
12
+ let radius = sqrt( uv.y );
13
+ return vec2f( cos( angle ), sin( angle ) ) * radius;
14
+
15
+ }
16
+ `;
17
+
18
+ const sampleTriangle = wgslTagFn/* wgsl */`
19
+ fn sampleTriangle( a: vec2f, b: vec2f, c: vec2f, rIn: vec2f ) -> vec2f {
20
+
21
+ let e1 = a - b;
22
+ let e2 = c - b;
23
+
24
+ var r = rIn;
25
+ if ( r.x + r.y > 1.0 ) {
26
+
27
+ r = vec2f( 1.0 ) - r;
28
+
29
+ }
30
+
31
+ return e1 * r.x + e2 * r.y;
32
+
33
+ }
34
+ `;
35
+
36
+ const sampleRegularPolygon = wgslTagFn/* wgsl */`
37
+ fn sampleRegularPolygon( sidesIn: i32, uvw: vec3f ) -> vec2f {
38
+
39
+ let sides = max( sidesIn, 3 );
40
+ let anglePerSegment = 2.0 * ${ PI } / f32( sides );
41
+ let segment = floor( f32( sides ) * uvw.x );
42
+
43
+ let angle1 = anglePerSegment * segment;
44
+ let angle2 = angle1 + anglePerSegment;
45
+ let a = vec2f( sin( angle1 ), cos( angle1 ) );
46
+ let b = vec2f( 0.0, 0.0 );
47
+ let c = vec2f( sin( angle2 ), cos( angle2 ) );
48
+
49
+ return ${ sampleTriangle }( a, b, c, uvw.yz );
50
+
51
+ }
52
+ `;
53
+
54
+ // samples an aperture shape with the given number of blades. 0 means circle
55
+ const sampleAperture = wgslTagFn/* wgsl */`
56
+ fn sampleAperture( blades: i32, uvw: vec3f ) -> vec2f {
57
+
58
+ if ( blades == 0 ) {
59
+
60
+ return ${ sampleCircle }( uvw.xy );
61
+
62
+ } else {
63
+
64
+ return ${ sampleRegularPolygon }( blades, uvw );
65
+
66
+ }
67
+
68
+ }
69
+ `;
70
+
71
+ const rotateVector = wgslTagFn/* wgsl */`
72
+ fn rotateVector( v: vec2f, t: f32 ) -> vec2f {
73
+
74
+ let vc = cos( t );
75
+ let vs = sin( t );
76
+ return vec2f( v.x * vc - v.y * vs, v.x * vs + v.y * vc );
77
+
78
+ }
79
+ `;
80
+
81
+ PhysicalCamera.prototype.getCameraRayFn = function getCameraRayFn() {
82
+
83
+ // camera transform fields
84
+ const invViewProjectionMatrix = uniform( new Matrix4() );
85
+ const cameraWorldMatrix = uniform( new Matrix4() );
86
+
87
+ // bokeh shape fields
88
+ const focusDistance = uniform( 0 );
89
+ const bokehSize = uniform( 0 );
90
+ const apertureBlades = uniform( 0, 'int' );
91
+ const apertureRotation = uniform( 0 );
92
+ const anamorphicRatio = uniform( 1 );
93
+
94
+ const fn = wgslTagFn/* wgsl */`
95
+ fn getCameraRay( uv: vec2f, resolution: vec2f, ray: ptr<function, ${ rayStruct }> ) -> bool {
96
+
97
+ // base ray
98
+ let ndc = uv * 2.0 - vec2f( 1.0 );
99
+ *ray = ${ ndcToCameraRay }( ndc, ${ invViewProjectionMatrix } );
100
+
101
+ // camera state
102
+ let forward = normalize( ( ${ cameraWorldMatrix } * vec4f( 0.0, 0.0, - 1.0, 0.0 ) ).xyz );
103
+ let cameraOrigin = ( ${ cameraWorldMatrix } * vec4f( 0.0, 0.0, 0.0, 1.0 ) ).xyz;
104
+
105
+ // depth of field
106
+ // measure focus distance from the camera origin along the optical axis so the focal
107
+ // surface is a flat plane perpendicular to the camera forward vector rather than a sphere
108
+ let focalPoint = cameraOrigin + ray.direction * ( ${ focusDistance } / dot( ray.direction, forward ) );
109
+ let nearDistance = dot( ray.origin - cameraOrigin, forward );
110
+
111
+ // sample the aperture shape
112
+ let shapeUVW = ${ rand3 }( ${ RNG_INDEX_APERTURE_SAMPLE } );
113
+ var apertureSample = ${ sampleAperture }( ${ apertureBlades }, shapeUVW );
114
+ apertureSample *= ${ bokehSize } * 0.5 * 1e-3;
115
+
116
+ // rotate + squash the sample for anamorphic apertures
117
+ apertureSample =
118
+ ${ rotateVector }( apertureSample, ${ apertureRotation } )
119
+ * vec2f( ${ anamorphicRatio }, 1.0 / ${ anamorphicRatio } );
120
+
121
+ // aim from the aperture through the focal point, starting on the near plane like the base ray
122
+ ray.origin = cameraOrigin + ( ${ cameraWorldMatrix } * vec4f( apertureSample, 0.0, 0.0 ) ).xyz;
123
+ ray.direction = normalize( focalPoint - ray.origin );
124
+ ray.origin += ray.direction * ( nearDistance / dot( ray.direction, forward ) );
125
+
126
+ return true;
127
+
128
+ }
129
+ `;
130
+
131
+ const update = () => {
132
+
133
+ this.coordinateSystem = WebGPUCoordinateSystem;
134
+ this.updateMatrixWorld();
135
+ this.updateProjectionMatrix();
136
+
137
+ invViewProjectionMatrix.value.multiplyMatrices( this.matrixWorld, this.projectionMatrixInverse );
138
+ cameraWorldMatrix.value.copy( this.matrixWorld );
139
+
140
+ focusDistance.value = this.focusDistance;
141
+ bokehSize.value = this.bokehSize;
142
+ apertureBlades.value = this.apertureBlades;
143
+ apertureRotation.value = this.apertureRotation;
144
+ anamorphicRatio.value = this.anamorphicRatio;
145
+ return false;
146
+
147
+ };
148
+
149
+ return { fn, update };
150
+
151
+ };