three-gpu-pathtracer 0.0.1 → 0.0.2
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 +229 -18
- package/build/index.module.js +528 -198
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +534 -198
- package/build/index.umd.cjs.map +1 -1
- package/package.json +7 -3
- package/src/core/DynamicPathTracingSceneGenerator.js +105 -0
- package/src/core/PathTracingSceneGenerator.js +27 -21
- package/src/core/PhysicalCamera.js +28 -0
- package/src/index.js +2 -0
- package/src/materials/PhysicalPathTracingMaterial.js +56 -6
- package/src/shader/shaderStructs.js +12 -0
- package/src/shader/shaderUtils.js +51 -0
- package/src/uniforms/MaterialStructUniform.js +21 -1
- package/src/uniforms/PhysicalCameraUniform.js +36 -0
- package/src/utils/GeometryPreparationUtils.js +200 -172
- package/src/workers/PathTracingSceneWorker.js +40 -0
- package/src/viewers/PathTracingViewer.js +0 -259
|
@@ -1,23 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Mesh } from 'three';
|
|
2
|
+
import { SAH, MeshBVH, StaticGeometryGenerator } from 'three-mesh-bvh';
|
|
3
3
|
import { mergeMeshes } from '../utils/GeometryPreparationUtils.js';
|
|
4
4
|
|
|
5
5
|
export class PathTracingSceneGenerator {
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
prepScene( scene ) {
|
|
8
8
|
|
|
9
|
-
this.bvhGenerator = new GenerateMeshBVHWorker();
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
async generate( scene, options = {} ) {
|
|
14
|
-
|
|
15
|
-
const { bvhGenerator } = this;
|
|
16
9
|
const meshes = [];
|
|
17
|
-
|
|
18
10
|
scene.traverse( c => {
|
|
19
11
|
|
|
20
|
-
if ( c.isMesh ) {
|
|
12
|
+
if ( c.isSkinnedMesh || c.isMesh && c.morphTargetInfluences ) {
|
|
13
|
+
|
|
14
|
+
const generator = new StaticGeometryGenerator( c );
|
|
15
|
+
generator.applyWorldTransforms = false;
|
|
16
|
+
const mesh = new Mesh(
|
|
17
|
+
generator.generate(),
|
|
18
|
+
c.material,
|
|
19
|
+
);
|
|
20
|
+
mesh.matrixWorld.copy( c.matrixWorld );
|
|
21
|
+
mesh.matrix.copy( c.matrixWorld );
|
|
22
|
+
mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
|
|
23
|
+
meshes.push( mesh );
|
|
24
|
+
|
|
25
|
+
} else if ( c.isMesh ) {
|
|
21
26
|
|
|
22
27
|
meshes.push( c );
|
|
23
28
|
|
|
@@ -25,22 +30,23 @@ export class PathTracingSceneGenerator {
|
|
|
25
30
|
|
|
26
31
|
} );
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
return mergeMeshes( meshes, {
|
|
34
|
+
attributes: [ 'position', 'normal', 'tangent', 'uv' ],
|
|
35
|
+
} );
|
|
30
36
|
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
generate( scene, options = {} ) {
|
|
40
|
+
|
|
41
|
+
const { materials, textures, geometry } = this.prepScene( scene );
|
|
42
|
+
const bvhOptions = { strategy: SAH, ...options, maxLeafTris: 1 };
|
|
31
43
|
return {
|
|
32
44
|
scene,
|
|
33
45
|
materials,
|
|
34
46
|
textures,
|
|
35
|
-
bvh:
|
|
47
|
+
bvh: new MeshBVH( geometry, bvhOptions ),
|
|
36
48
|
};
|
|
37
49
|
|
|
38
50
|
}
|
|
39
51
|
|
|
40
|
-
dispose() {
|
|
41
|
-
|
|
42
|
-
this.bvhGenerator.terminate();
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
52
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { PerspectiveCamera } from 'three';
|
|
2
|
+
|
|
3
|
+
export class PhysicalCamera extends PerspectiveCamera {
|
|
4
|
+
|
|
5
|
+
set bokehSize( size ) {
|
|
6
|
+
|
|
7
|
+
this.fStop = this.getFocalLength() / size;
|
|
8
|
+
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
get bokehSize() {
|
|
12
|
+
|
|
13
|
+
return this.getFocalLength() / this.fStop;
|
|
14
|
+
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
constructor( ...args ) {
|
|
18
|
+
|
|
19
|
+
super( ...args );
|
|
20
|
+
this.fStop = 1.4;
|
|
21
|
+
this.apertureBlades = 0;
|
|
22
|
+
this.apertureRotation = 0;
|
|
23
|
+
this.focusDistance = 25;
|
|
24
|
+
this.anamorphicRatio = 1;
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// core
|
|
2
2
|
export * from './core/PathTracingRenderer.js';
|
|
3
3
|
export * from './core/PathTracingSceneGenerator.js';
|
|
4
|
+
export * from './core/DynamicPathTracingSceneGenerator.js';
|
|
4
5
|
export * from './core/MaterialReducer.js';
|
|
6
|
+
export * from './core/PhysicalCamera.js';
|
|
5
7
|
|
|
6
8
|
// uniforms
|
|
7
9
|
export * from './uniforms/MaterialStructArrayUniform.js';
|
|
@@ -9,6 +9,7 @@ import { MaterialStructArrayUniform } from '../uniforms/MaterialStructArrayUnifo
|
|
|
9
9
|
import { RenderTarget2DArray } from '../uniforms/RenderTarget2DArray.js';
|
|
10
10
|
import { shaderMaterialSampling } from '../shader/shaderMaterialSampling.js';
|
|
11
11
|
import { shaderUtils } from '../shader/shaderUtils.js';
|
|
12
|
+
import { PhysicalCameraUniform } from '../uniforms/PhysicalCameraUniform.js';
|
|
12
13
|
|
|
13
14
|
export class PhysicalPathTracingMaterial extends MaterialBase {
|
|
14
15
|
|
|
@@ -27,13 +28,16 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
27
28
|
depthWrite: false,
|
|
28
29
|
|
|
29
30
|
defines: {
|
|
30
|
-
|
|
31
|
+
DOF_SUPPORT: 1,
|
|
31
32
|
TRANSPARENT_TRAVERSALS: 5,
|
|
32
33
|
MATERIAL_LENGTH: 0,
|
|
33
34
|
GRADIENT_BG: 0,
|
|
34
35
|
},
|
|
35
36
|
|
|
36
37
|
uniforms: {
|
|
38
|
+
bounces: { value: 3 },
|
|
39
|
+
physicalCamera: { value: new PhysicalCameraUniform() },
|
|
40
|
+
|
|
37
41
|
bvh: { value: new MeshBVHUniformStruct() },
|
|
38
42
|
normalAttribute: { value: new FloatVertexAttributeTexture() },
|
|
39
43
|
tangentAttribute: { value: new FloatVertexAttributeTexture() },
|
|
@@ -110,6 +114,14 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
110
114
|
|
|
111
115
|
#endif
|
|
112
116
|
|
|
117
|
+
#if DOF_SUPPORT
|
|
118
|
+
|
|
119
|
+
uniform PhysicalCamera physicalCamera;
|
|
120
|
+
|
|
121
|
+
#endif
|
|
122
|
+
|
|
123
|
+
uniform int bounces;
|
|
124
|
+
|
|
113
125
|
uniform mat4 cameraWorldMatrix;
|
|
114
126
|
uniform mat4 invProjectionMatrix;
|
|
115
127
|
uniform sampler2D normalAttribute;
|
|
@@ -134,6 +146,30 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
134
146
|
vec3 rayOrigin, rayDirection;
|
|
135
147
|
ndcToCameraRay( ndc, cameraWorldMatrix, invProjectionMatrix, rayOrigin, rayDirection );
|
|
136
148
|
|
|
149
|
+
#if DOF_SUPPORT
|
|
150
|
+
|
|
151
|
+
// depth of field
|
|
152
|
+
vec3 focalPoint = rayOrigin + normalize( rayDirection ) * physicalCamera.focusDistance;
|
|
153
|
+
|
|
154
|
+
// get the aperture sample
|
|
155
|
+
vec2 apertureSample = sampleAperture( physicalCamera.apertureBlades ) * physicalCamera.bokehSize * 0.5 * 1e-3;
|
|
156
|
+
|
|
157
|
+
// rotate the aperture shape
|
|
158
|
+
float ac = cos( physicalCamera.apertureRotation );
|
|
159
|
+
float as = sin( physicalCamera.apertureRotation );
|
|
160
|
+
apertureSample = vec2(
|
|
161
|
+
apertureSample.x * ac - apertureSample.y * as,
|
|
162
|
+
apertureSample.x * as + apertureSample.y * ac
|
|
163
|
+
);
|
|
164
|
+
apertureSample.x *= saturate( physicalCamera.anamorphicRatio );
|
|
165
|
+
apertureSample.y *= saturate( 1.0 / physicalCamera.anamorphicRatio );
|
|
166
|
+
|
|
167
|
+
#endif
|
|
168
|
+
|
|
169
|
+
// create the new ray
|
|
170
|
+
rayOrigin += ( cameraWorldMatrix * vec4( apertureSample, 0.0, 0.0 ) ).xyz;
|
|
171
|
+
rayDirection = focalPoint - rayOrigin;
|
|
172
|
+
|
|
137
173
|
// Lambertian render
|
|
138
174
|
gl_FragColor = vec4( 0.0 );
|
|
139
175
|
|
|
@@ -148,7 +184,7 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
148
184
|
float accumulatedRoughness = 0.0;
|
|
149
185
|
int i;
|
|
150
186
|
int transparentTraversals = TRANSPARENT_TRAVERSALS;
|
|
151
|
-
for ( i = 0; i <
|
|
187
|
+
for ( i = 0; i < bounces; i ++ ) {
|
|
152
188
|
|
|
153
189
|
if ( ! bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
|
|
154
190
|
|
|
@@ -199,12 +235,23 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
199
235
|
|
|
200
236
|
}
|
|
201
237
|
|
|
202
|
-
// possibly skip this sample if it's transparent
|
|
203
|
-
//
|
|
238
|
+
// possibly skip this sample if it's transparent, alpha test is enabled, or we hit the wrong material side
|
|
239
|
+
// and it's single sided.
|
|
240
|
+
// - alpha test is disabled when it === 0
|
|
241
|
+
// - the material sidedness test is complicated because we want light to pass through the back side but still
|
|
242
|
+
// be able to see the front side. This boolean checks if the side we hit is the front side on the first ray
|
|
243
|
+
// and we're rendering the other then we skip it. Do the opposite on subsequent bounces to get incoming light.
|
|
204
244
|
float alphaTest = material.alphaTest;
|
|
205
245
|
bool useAlphaTest = alphaTest != 0.0;
|
|
246
|
+
bool isFirstHit = i == 0;
|
|
206
247
|
if (
|
|
207
|
-
|
|
248
|
+
// material sidedness
|
|
249
|
+
material.side != 0.0 && ( side != material.side ) == isFirstHit
|
|
250
|
+
|
|
251
|
+
// alpha test
|
|
252
|
+
|| useAlphaTest && albedo.a < alphaTest
|
|
253
|
+
|
|
254
|
+
// opacity
|
|
208
255
|
|| ! useAlphaTest && albedo.a < rand()
|
|
209
256
|
) {
|
|
210
257
|
|
|
@@ -288,7 +335,6 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
288
335
|
SurfaceRec surfaceRec;
|
|
289
336
|
surfaceRec.normal = normal;
|
|
290
337
|
surfaceRec.faceNormal = faceNormal;
|
|
291
|
-
surfaceRec.frontFace = side == 1.0;
|
|
292
338
|
surfaceRec.transmission = transmission;
|
|
293
339
|
surfaceRec.ior = material.ior;
|
|
294
340
|
surfaceRec.emission = emission;
|
|
@@ -296,6 +342,10 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
296
342
|
surfaceRec.color = albedo.rgb;
|
|
297
343
|
surfaceRec.roughness = roughness;
|
|
298
344
|
|
|
345
|
+
// frontFace is used to determine transmissive properties and PDF. If no transmission is used
|
|
346
|
+
// then we can just always assume this is a front face.
|
|
347
|
+
surfaceRec.frontFace = side == 1.0 || transmission == 0.0;
|
|
348
|
+
|
|
299
349
|
// Compute the filtered roughness value to use during specular reflection computations. A minimum
|
|
300
350
|
// value of 1e-6 is needed because the GGX functions do not work with a roughness value of 0 and
|
|
301
351
|
// the accumulated roughness value is scaled by a user setting and a "magic value" of 5.0.
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
export const shaderMaterialStructs = /* glsl */ `
|
|
2
2
|
|
|
3
|
+
struct PhysicalCamera {
|
|
4
|
+
|
|
5
|
+
float focusDistance;
|
|
6
|
+
float anamorphicRatio;
|
|
7
|
+
float bokehSize;
|
|
8
|
+
int apertureBlades;
|
|
9
|
+
float apertureRotation;
|
|
10
|
+
|
|
11
|
+
};
|
|
12
|
+
|
|
3
13
|
struct Material {
|
|
4
14
|
|
|
5
15
|
vec3 color;
|
|
@@ -25,6 +35,8 @@ export const shaderMaterialStructs = /* glsl */ `
|
|
|
25
35
|
float opacity;
|
|
26
36
|
float alphaTest;
|
|
27
37
|
|
|
38
|
+
float side;
|
|
39
|
+
|
|
28
40
|
};
|
|
29
41
|
|
|
30
42
|
`;
|
|
@@ -98,6 +98,7 @@ export const shaderUtils = /* glsl */`
|
|
|
98
98
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// returns [ 0, 1 ]
|
|
101
102
|
float rand() {
|
|
102
103
|
|
|
103
104
|
pcg4d(s0);
|
|
@@ -137,4 +138,54 @@ export const shaderUtils = /* glsl */`
|
|
|
137
138
|
return vec3( f * cos( t ), f * sin( t ), u );
|
|
138
139
|
|
|
139
140
|
}
|
|
141
|
+
|
|
142
|
+
vec2 triangleSample( vec2 a, vec2 b, vec2 c ) {
|
|
143
|
+
|
|
144
|
+
// get the edges of the triangle and the diagonal across the
|
|
145
|
+
// center of the parallelogram
|
|
146
|
+
vec2 e1 = a - b;
|
|
147
|
+
vec2 e2 = c - b;
|
|
148
|
+
vec2 diag = normalize( e1 + e2 );
|
|
149
|
+
|
|
150
|
+
// pick a random point in the parallelogram
|
|
151
|
+
vec2 r = rand2();
|
|
152
|
+
if ( r.x + r.y > 1.0 ) {
|
|
153
|
+
|
|
154
|
+
r = vec2( 1.0 ) - r;
|
|
155
|
+
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return e1 * r.x + e2 * r.y;
|
|
159
|
+
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// samples an aperture shape with the given number of sides. 0 means circle
|
|
163
|
+
vec2 sampleAperture( int blades ) {
|
|
164
|
+
|
|
165
|
+
if ( blades == 0 ) {
|
|
166
|
+
|
|
167
|
+
vec2 r = rand2();
|
|
168
|
+
float angle = 2.0 * PI * r.x;
|
|
169
|
+
float radius = sqrt( rand() );
|
|
170
|
+
return vec2( cos( angle ), sin( angle ) ) * radius;
|
|
171
|
+
|
|
172
|
+
} else {
|
|
173
|
+
|
|
174
|
+
blades = max( blades, 3 );
|
|
175
|
+
|
|
176
|
+
vec3 r = rand3();
|
|
177
|
+
float anglePerSegment = 2.0 * PI / float( blades );
|
|
178
|
+
float segment = floor( float( blades ) * r.x );
|
|
179
|
+
|
|
180
|
+
float angle1 = anglePerSegment * segment;
|
|
181
|
+
float angle2 = angle1 + anglePerSegment;
|
|
182
|
+
vec2 a = vec2( sin( angle1 ), cos( angle1 ) );
|
|
183
|
+
vec2 b = vec2( 0.0, 0.0 );
|
|
184
|
+
vec2 c = vec2( sin( angle2 ), cos( angle2 ) );
|
|
185
|
+
|
|
186
|
+
return triangleSample( a, b, c );
|
|
187
|
+
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
}
|
|
140
191
|
`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Color, Vector2 } from 'three';
|
|
1
|
+
import { Color, Vector2, DoubleSide, FrontSide, BackSide } from 'three';
|
|
2
2
|
export class MaterialStructUniform {
|
|
3
3
|
|
|
4
4
|
constructor() {
|
|
@@ -32,6 +32,8 @@ export class MaterialStructUniform {
|
|
|
32
32
|
this.opacity = 1.0;
|
|
33
33
|
this.alphaTest = 0.0;
|
|
34
34
|
|
|
35
|
+
this.side = 0;
|
|
36
|
+
|
|
35
37
|
// TODO: Clearcoat
|
|
36
38
|
|
|
37
39
|
// TODO: Sheen
|
|
@@ -91,4 +93,22 @@ export class MaterialStructUniform {
|
|
|
91
93
|
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
setSide( side ) {
|
|
97
|
+
|
|
98
|
+
switch ( side ) {
|
|
99
|
+
|
|
100
|
+
case DoubleSide:
|
|
101
|
+
this.side = 0;
|
|
102
|
+
break;
|
|
103
|
+
case FrontSide:
|
|
104
|
+
this.side = 1;
|
|
105
|
+
break;
|
|
106
|
+
case BackSide:
|
|
107
|
+
this.side = - 1;
|
|
108
|
+
break;
|
|
109
|
+
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
|
|
94
114
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { PhysicalCamera } from '../core/PhysicalCamera.js';
|
|
2
|
+
export class PhysicalCameraUniform {
|
|
3
|
+
|
|
4
|
+
constructor() {
|
|
5
|
+
|
|
6
|
+
this.bokehSize = 0;
|
|
7
|
+
this.apertureBlades = 0;
|
|
8
|
+
this.apertureRotation = 0;
|
|
9
|
+
this.focusDistance = 10;
|
|
10
|
+
this.anamorphicRatio = 1;
|
|
11
|
+
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
updateFrom( camera ) {
|
|
15
|
+
|
|
16
|
+
if ( camera instanceof PhysicalCamera ) {
|
|
17
|
+
|
|
18
|
+
this.bokehSize = camera.bokehSize;
|
|
19
|
+
this.apertureBlades = camera.apertureBlades;
|
|
20
|
+
this.apertureRotation = camera.apertureRotation;
|
|
21
|
+
this.focusDistance = camera.focusDistance;
|
|
22
|
+
this.anamorphicRatio = camera.anamorphicRatio;
|
|
23
|
+
|
|
24
|
+
} else {
|
|
25
|
+
|
|
26
|
+
this.bokehSize = 0;
|
|
27
|
+
this.apertureRotation = 0;
|
|
28
|
+
this.apertureBlades = 0;
|
|
29
|
+
this.focusDistance = 10;
|
|
30
|
+
this.anamorphicRatio = 1;
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
}
|