three-gpu-pathtracer 0.0.1

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.
@@ -0,0 +1,94 @@
1
+ import { Color, Vector2 } from 'three';
2
+ export class MaterialStructUniform {
3
+
4
+ constructor() {
5
+
6
+ this.init();
7
+
8
+ }
9
+
10
+ init() {
11
+
12
+ this.color = new Color( 0xffffff );
13
+ this.map = - 1;
14
+
15
+ this.metalness = 1.0;
16
+ this.metalnessMap = - 1;
17
+
18
+ this.roughness = 1.0;
19
+ this.roughnessMap = - 1;
20
+
21
+ this.ior = 1.0;
22
+ this.transmission = 0.0;
23
+ this.transmissionMap = - 1;
24
+
25
+ this.emissive = new Color( 0 );
26
+ this.emissiveIntensity = 1.0;
27
+ this.emissiveMap = - 1;
28
+
29
+ this.normalMap = - 1;
30
+ this.normalScale = new Vector2( 1, 1 );
31
+
32
+ this.opacity = 1.0;
33
+ this.alphaTest = 0.0;
34
+
35
+ // TODO: Clearcoat
36
+
37
+ // TODO: Sheen
38
+
39
+ }
40
+
41
+ updateFrom( material, textures = [] ) {
42
+
43
+ this.init();
44
+
45
+ // color
46
+ if ( 'color' in material ) this.color.copy( material.color );
47
+ else material.color.set( 0xffffff );
48
+
49
+ this.map = textures.indexOf( material.map );
50
+
51
+ // metalness
52
+ if ( 'metalness' in material ) this.metalness = material.metalness;
53
+ else this.metalness = 1.0;
54
+
55
+ this.metalnessMap = textures.indexOf( material.metalnessMap );
56
+
57
+ // roughness
58
+ if ( 'roughness' in material ) this.roughness = material.roughness;
59
+ else this.roughness = 1.0;
60
+
61
+ this.roughnessMap = textures.indexOf( material.roughnessMap );
62
+
63
+ // transmission
64
+ if ( 'ior' in material ) this.ior = material.ior;
65
+ else this.ior = 1.0;
66
+
67
+ if ( 'transmission' in material ) this.transmission = material.transmission;
68
+ else this.transmission = 0.0;
69
+
70
+ if ( 'transmissionMap' in material ) this.transmissionMap = textures.indexOf( material.transmissionMap );
71
+
72
+ // emission
73
+ if ( 'emissive' in material ) this.emissive.copy( material.emissive );
74
+ else this.emissive.set( 0 );
75
+
76
+ if ( 'emissiveIntensity' in material ) this.emissiveIntensity = material.emissiveIntensity;
77
+ else this.emissiveIntensity = 1.0;
78
+
79
+ this.emissiveMap = textures.indexOf( material.emissiveMap );
80
+
81
+ // normals
82
+ this.normalMap = textures.indexOf( material.normalMap );
83
+ if ( 'normalScale' in material ) this.normalScale.copy( material.normalScale );
84
+ else this.normalScale.set( 1, 1 );
85
+
86
+ // opacity
87
+ this.opacity = material.opacity;
88
+
89
+ // alpha test
90
+ this.alphaTest = material.alphaTest;
91
+
92
+ }
93
+
94
+ }
@@ -0,0 +1,80 @@
1
+ import {
2
+ WebGLArrayRenderTarget,
3
+ RGBAFormat,
4
+ UnsignedByteType,
5
+ MeshBasicMaterial,
6
+ Color,
7
+ RepeatWrapping,
8
+ LinearFilter,
9
+ NoToneMapping,
10
+ } from 'three';
11
+ import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
12
+
13
+ const prevColor = new Color();
14
+ export class RenderTarget2DArray extends WebGLArrayRenderTarget {
15
+
16
+ constructor( ...args ) {
17
+
18
+ super( ...args );
19
+
20
+ const tex = this.texture;
21
+ tex.format = RGBAFormat;
22
+ tex.type = UnsignedByteType;
23
+ tex.minFilter = LinearFilter;
24
+ tex.magFilter = LinearFilter;
25
+ tex.wrapS = RepeatWrapping;
26
+ tex.wrapT = RepeatWrapping;
27
+ tex.setTextures = ( ...args ) => {
28
+
29
+ this.setTextures( ...args );
30
+
31
+ };
32
+
33
+ const fsQuad = new FullScreenQuad( new MeshBasicMaterial() );
34
+ this.fsQuad = fsQuad;
35
+
36
+ }
37
+
38
+ setTextures( renderer, width, height, textures ) {
39
+
40
+ // save previous renderer state
41
+ const prevRenderTarget = renderer.getRenderTarget();
42
+ const prevToneMapping = renderer.toneMapping;
43
+ const prevAlpha = renderer.getClearAlpha();
44
+ renderer.getClearColor( prevColor );
45
+
46
+ // resize the render target
47
+ const depth = textures.length;
48
+ this.setSize( width, height, depth );
49
+ renderer.setClearColor( 0, 0 );
50
+ renderer.toneMapping = NoToneMapping;
51
+
52
+ // render each texture into each layer of the target
53
+ const fsQuad = this.fsQuad;
54
+ for ( let i = 0, l = depth; i < l; i ++ ) {
55
+
56
+ const texture = textures[ i ];
57
+ fsQuad.material.map = texture;
58
+ fsQuad.material.transparent = true;
59
+
60
+ renderer.setRenderTarget( this, i );
61
+ fsQuad.render( renderer );
62
+
63
+ }
64
+
65
+ // reset the renderer
66
+ fsQuad.material.map = null;
67
+ renderer.setClearColor( prevColor, prevAlpha );
68
+ renderer.setRenderTarget( prevRenderTarget );
69
+ renderer.toneMapping = prevToneMapping;
70
+
71
+ }
72
+
73
+ dispose() {
74
+
75
+ super.dispose();
76
+ this.fsQuad.dispose();
77
+
78
+ }
79
+
80
+ }
@@ -0,0 +1,172 @@
1
+ import { BufferAttribute } from 'three';
2
+ import { mergeBufferGeometries, mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
3
+ function getGroupMaterialIndicesAttribute( geometry, materials, allMaterials ) {
4
+
5
+ if ( ! Array.isArray( materials ) ) {
6
+
7
+ materials = [ materials ];
8
+
9
+ }
10
+
11
+ const vertCount = geometry.attributes.position.count;
12
+ const materialArray = new Uint8Array( vertCount );
13
+ let groups = geometry.groups;
14
+ if ( groups.length === 0 ) {
15
+
16
+ groups = [ { count: vertCount, start: 0, materialIndex: 0 } ];
17
+
18
+ }
19
+
20
+ for ( let i = 0; i < groups.length; i ++ ) {
21
+
22
+ const group = groups[ i ];
23
+ const { count, start } = group;
24
+ const endCount = Math.min( count, vertCount - start );
25
+ const mat = materials[ group.materialIndex ];
26
+ const materialIndex = allMaterials.indexOf( mat );
27
+
28
+ for ( let j = 0; j < endCount; j ++ ) {
29
+
30
+ materialArray[ start + j ] = materialIndex;
31
+
32
+ }
33
+
34
+ }
35
+
36
+ return new BufferAttribute( materialArray, 1, false );
37
+
38
+ }
39
+
40
+ export function mergeMeshes( meshes, options = {} ) {
41
+
42
+ options = { attributes: null, cloneGeometry: true, ...options };
43
+
44
+ const transformedGeometry = [];
45
+ const materialSet = new Set();
46
+ for ( let i = 0, l = meshes.length; i < l; i ++ ) {
47
+
48
+ // save any materials
49
+ const mesh = meshes[ i ];
50
+ if ( mesh.visible === false ) continue;
51
+
52
+ if ( Array.isArray( mesh.material ) ) {
53
+
54
+ mesh.material.forEach( m => materialSet.add( m ) );
55
+
56
+ } else {
57
+
58
+ materialSet.add( mesh.material );
59
+
60
+ }
61
+
62
+ }
63
+
64
+ const materials = Array.from( materialSet );
65
+ for ( let i = 0, l = meshes.length; i < l; i ++ ) {
66
+
67
+ // ensure the matrix world is up to date
68
+ const mesh = meshes[ i ];
69
+ if ( mesh.visible === false ) continue;
70
+
71
+ mesh.updateMatrixWorld();
72
+
73
+ // apply the matrix world to the geometry
74
+ const originalGeometry = meshes[ i ].geometry;
75
+ let geometry = options.cloneGeometry ? originalGeometry.clone() : originalGeometry;
76
+ geometry.applyMatrix4( mesh.matrixWorld );
77
+
78
+ const attrs = options.attributes;
79
+ if ( ! geometry.attributes.normal && ( attrs && attrs.includes( 'normal' ) ) ) {
80
+
81
+ geometry.computeVertexNormals();
82
+
83
+ }
84
+
85
+ if ( ! geometry.attributes.uv && ( attrs && attrs.includes( 'uv' ) ) ) {
86
+
87
+ const vertCount = geometry.attributes.position.count;
88
+ geometry.setAttribute( 'uv', new BufferAttribute( new Float32Array( vertCount * 2 ), 2, false ) );
89
+
90
+ }
91
+
92
+ if ( ! geometry.attributes.tangent && ( attrs && attrs.includes( 'tangent' ) ) ) {
93
+
94
+ if ( mesh.material.normalMap ) {
95
+
96
+ // computeTangents requires an index buffer
97
+ if ( geometry.index === null ) {
98
+
99
+ geometry = mergeVertices( geometry );
100
+
101
+ }
102
+
103
+ geometry.computeTangents();
104
+
105
+ } else {
106
+
107
+ const vertCount = geometry.attributes.position.count;
108
+ geometry.setAttribute( 'tangent', new BufferAttribute( new Float32Array( vertCount * 4 ), 4, false ) );
109
+
110
+ }
111
+
112
+ }
113
+
114
+ if ( ! geometry.index ) {
115
+
116
+ // TODO: compute a typed array
117
+ const indexCount = geometry.attributes.position.count;
118
+ const array = new Array( indexCount );
119
+ for ( let i = 0; i < indexCount; i ++ ) {
120
+
121
+ array[ i ] = i;
122
+
123
+ }
124
+
125
+ geometry.setIndex( array );
126
+
127
+ }
128
+
129
+ // trim any unneeded attributes
130
+ if ( options.attributes ) {
131
+
132
+ for ( const key in geometry.attributes ) {
133
+
134
+ if ( ! options.attributes.includes( key ) ) {
135
+
136
+ geometry.deleteAttribute( key );
137
+
138
+ }
139
+
140
+ }
141
+
142
+ }
143
+
144
+ // create the material index attribute
145
+ const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, mesh.material, materials );
146
+ geometry.setAttribute( 'materialIndex', materialIndexAttribute );
147
+
148
+ transformedGeometry.push( geometry );
149
+
150
+ }
151
+
152
+ const textureSet = new Set();
153
+ materials.forEach( material => {
154
+
155
+ for ( const key in material ) {
156
+
157
+ const value = material[ key ];
158
+ if ( value && value.isTexture ) {
159
+
160
+ textureSet.add( value );
161
+
162
+ }
163
+
164
+ }
165
+
166
+ } );
167
+
168
+ const geometry = mergeBufferGeometries( transformedGeometry, false );
169
+ const textures = Array.from( textureSet );
170
+ return { geometry, materials, textures };
171
+
172
+ }
@@ -0,0 +1,101 @@
1
+ // https://github.com/mozilla/Spoke/commit/9701d647020e09d584885bd457eb225e9995c12f
2
+ import XAtlas from 'xatlas-web';
3
+ import { BufferGeometry, Float32BufferAttribute, Uint32BufferAttribute } from 'three';
4
+
5
+ export class UVUnwrapper {
6
+
7
+ constructor() {
8
+
9
+ this._module = null;
10
+
11
+ }
12
+
13
+ async load() {
14
+
15
+ const wasmurl = new URL( '../../node_modules/xatlas-web/dist/xatlas-web.wasm', import.meta.url );
16
+ this._module = XAtlas( {
17
+
18
+ locateFile( path ) {
19
+
20
+ if ( path.endsWith( '.wasm' ) ) {
21
+
22
+ return wasmurl.toString();
23
+
24
+ }
25
+
26
+ return path;
27
+
28
+ }
29
+
30
+ } );
31
+ return this._module.ready;
32
+
33
+ }
34
+
35
+ generate( geometry ) {
36
+
37
+ const xatlas = this._module;
38
+ const originalVertexCount = geometry.attributes.position.count;
39
+ const originalIndexCount = geometry.index.count;
40
+
41
+ xatlas.createAtlas();
42
+
43
+ const meshInfo = xatlas.createMesh( originalVertexCount, originalIndexCount, true, true );
44
+ xatlas.HEAPU16.set( geometry.index.array, meshInfo.indexOffset / Uint16Array.BYTES_PER_ELEMENT );
45
+ xatlas.HEAPF32.set( geometry.attributes.position.array, meshInfo.positionOffset / Float32Array.BYTES_PER_ELEMENT );
46
+ xatlas.HEAPF32.set( geometry.attributes.normal.array, meshInfo.normalOffset / Float32Array.BYTES_PER_ELEMENT );
47
+ xatlas.HEAPF32.set( geometry.attributes.uv.array, meshInfo.uvOffset / Float32Array.BYTES_PER_ELEMENT );
48
+
49
+ const statusCode = xatlas.addMesh();
50
+ if ( statusCode !== AddMeshStatus.Success ) {
51
+
52
+ throw new Error( `UVUnwrapper: Error adding mesh. Status code ${ statusCode }` );
53
+
54
+ }
55
+
56
+ xatlas.generateAtlas();
57
+
58
+ const meshData = xatlas.getMeshData( meshInfo.meshId );
59
+ const oldPositionArray = geometry.attributes.position.array;
60
+ const oldNormalArray = geometry.attributes.normal.array;
61
+ const oldUvArray = geometry.attributes.uv.array;
62
+
63
+ const newPositionArray = new Float32Array( meshData.newVertexCount * 3 );
64
+ const newNormalArray = new Float32Array( meshData.newVertexCount * 3 );
65
+ const newUvArray = new Float32Array( meshData.newVertexCount * 2 );
66
+ const newUv2Array = new Float32Array( xatlas.HEAPF32.buffer, meshData.uvOffset, meshData.newVertexCount * 2 );
67
+ const newIndexArray = new Uint32Array( xatlas.HEAPU32.buffer, meshData.indexOffset, meshData.newIndexCount );
68
+ const originalIndexArray = new Uint32Array(
69
+ xatlas.HEAPU32.buffer,
70
+ meshData.originalIndexOffset,
71
+ meshData.newVertexCount
72
+ );
73
+
74
+ for ( let i = 0; i < meshData.newVertexCount; i ++ ) {
75
+
76
+ const originalIndex = originalIndexArray[ i ];
77
+ newPositionArray[ i * 3 ] = oldPositionArray[ originalIndex * 3 ];
78
+ newPositionArray[ i * 3 + 1 ] = oldPositionArray[ originalIndex * 3 + 1 ];
79
+ newPositionArray[ i * 3 + 2 ] = oldPositionArray[ originalIndex * 3 + 2 ];
80
+ newNormalArray[ i * 3 ] = oldNormalArray[ originalIndex * 3 ];
81
+ newNormalArray[ i * 3 + 1 ] = oldNormalArray[ originalIndex * 3 + 1 ];
82
+ newNormalArray[ i * 3 + 2 ] = oldNormalArray[ originalIndex * 3 + 2 ];
83
+ newUvArray[ i * 2 ] = oldUvArray[ originalIndex * 2 ];
84
+ newUvArray[ i * 2 + 1 ] = oldUvArray[ originalIndex * 2 + 1 ];
85
+
86
+ }
87
+
88
+ const newGeometry = new BufferGeometry();
89
+ newGeometry.addAttribute( 'position', new Float32BufferAttribute( newPositionArray, 3 ) );
90
+ newGeometry.addAttribute( 'normal', new Float32BufferAttribute( newNormalArray, 3 ) );
91
+ newGeometry.addAttribute( 'uv', new Float32BufferAttribute( newUvArray, 2 ) );
92
+ newGeometry.addAttribute( 'uv2', new Float32BufferAttribute( newUv2Array, 2 ) );
93
+ newGeometry.setIndex( new Uint32BufferAttribute( newIndexArray, 1 ) );
94
+
95
+ mesh.geometry = newGeometry;
96
+
97
+ xatlas.destroyAtlas();
98
+
99
+ }
100
+
101
+ }
@@ -0,0 +1,259 @@
1
+ import { Scene, WebGLRenderer, MeshBasicMaterial, Vector2, Mesh, PerspectiveCamera, sRGBEncoding } from 'three';
2
+ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
3
+ import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
4
+ import { SAH } from 'three-mesh-bvh';
5
+ import { GenerateMeshBVHWorker } from 'three-mesh-bvh/src/workers/GenerateMeshBVHWorker.js';
6
+ import { PathTracingRenderer } from '../core/PathTracingRenderer.js';
7
+ import { mergeMeshes } from '../utils/GeometryPreparationUtils.js';
8
+ import { PhysicalPathTracingMaterial } from '../materials/PhysicalPathTracingMaterial.js';
9
+ import { MaterialReducer } from '../core/MaterialReducer.js';
10
+
11
+ export class PathTracingViewer {
12
+
13
+ get domElement() {
14
+
15
+ return this._container;
16
+
17
+ }
18
+
19
+ constructor() {
20
+
21
+ this.scene = new Scene();
22
+ this.camera = new PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.025, 500 );
23
+ this.camera.position.set( - 1, 0.25, 1 );
24
+
25
+ this.renderer = new WebGLRenderer( { antialias: true } );
26
+ this.fsQuad = new FullScreenQuad( new MeshBasicMaterial( { transparent: true } ) );
27
+ this.ptRenderer = new PathTracingRenderer( this.renderer );
28
+ this.ptModel = null;
29
+ this.ptMaterials = null;
30
+ this.ptTextures = null;
31
+ this.model = null;
32
+ this.bvhGenerator = new GenerateMeshBVHWorker();
33
+ this.onRender = null;
34
+ this.enablePathTracing = true;
35
+ this.pausePathTracing = false;
36
+ this.samplesPerFrame = 1;
37
+ this._scale = 1;
38
+ this._nextObject = null;
39
+ this._needsSizeUpdate = false;
40
+ this._newSize = new Vector2();
41
+ this._resizeObserver = new ResizeObserver( entries => {
42
+
43
+ const { contentRect } = entries[ 0 ];
44
+ this._newSize.set( contentRect.width, contentRect.height );
45
+ this._needsSizeUpdate = true;
46
+
47
+ } );
48
+
49
+ const container = document.createElement( 'div' );
50
+ container.style.overflow = 'hidden';
51
+ container.appendChild( this.renderer.domElement );
52
+ this._container = container;
53
+
54
+ this.ptRenderer.camera = this.camera;
55
+ this.ptRenderer.material = new PhysicalPathTracingMaterial();
56
+ this.renderer.outputEncoding = sRGBEncoding;
57
+ this._resizeObserver.observe( container );
58
+
59
+ }
60
+
61
+ _updateSize() {
62
+
63
+ const dpr = window.devicePixelRatio;
64
+ const scale = this._scale;
65
+ const size = this._newSize;
66
+ this.renderer.setPixelRatio( dpr );
67
+
68
+ this.renderer.domElement.style.aspectRatio = `${ size.width } / ${ size.height }`;
69
+ this.renderer.domElement.style.width = `100%`;
70
+ this.renderer.setSize( scale * size.width, scale * size.height, false );
71
+ this.ptRenderer.target.setSize( size.width * scale * dpr, size.height * scale * dpr );
72
+ this.camera.aspect = size.width / size.height;
73
+ this.camera.updateProjectionMatrix();
74
+ this._needsSizeUpdate = false;
75
+
76
+ }
77
+
78
+ setScale( scale ) {
79
+
80
+ this._scale = scale;
81
+ this._needsSizeUpdate = true;
82
+
83
+ }
84
+
85
+ setModel( object, bvhOptions = {} ) {
86
+
87
+ if ( this.bvhGenerator.running ) {
88
+
89
+ this._nextObject = object;
90
+ return;
91
+
92
+ }
93
+
94
+ object.updateMatrixWorld( true );
95
+
96
+ const materialReducer = new MaterialReducer();
97
+ materialReducer.process( object );
98
+
99
+ const meshes = [];
100
+ object.traverse( c => {
101
+
102
+ if ( c.isMesh ) {
103
+
104
+ meshes.push( c );
105
+
106
+ }
107
+
108
+ } );
109
+
110
+ const { geometry, materials, textures } = mergeMeshes( meshes, { attributes: [ 'position', 'normal', 'tangent', 'uv' ] } );
111
+ return this
112
+ .bvhGenerator
113
+ .generate( geometry, { strategy: SAH, maxLeafTris: 1, ...bvhOptions } )
114
+ .then( bvh => {
115
+
116
+ if ( this._nextObject ) {
117
+
118
+ this.setModel( this._nextObject );
119
+ this._nextObject = null;
120
+ return;
121
+
122
+ }
123
+
124
+ if ( this.model ) {
125
+
126
+ this.scene.remove( this.model );
127
+ this.ptTextures.forEach( tex => tex.dispose() );
128
+ this.ptMaterials.forEach( mat => mat.dispose() );
129
+
130
+ }
131
+
132
+ const mesh = new Mesh( geometry );
133
+ this.scene.add( object );
134
+ this.ptModel = mesh;
135
+ this.ptMaterials = materials;
136
+ this.ptTextures = textures;
137
+ this.model = object;
138
+
139
+ const { ptRenderer } = this;
140
+ const { material } = ptRenderer;
141
+
142
+ // dispose of textures because they cannot be updated in later version of three.js
143
+ material.bvh.dispose();
144
+ material.normalAttribute.dispose();
145
+ material.tangentAttribute.dispose();
146
+ material.uvAttribute.dispose();
147
+ material.materialIndexAttribute.dispose();
148
+ material.textures.dispose();
149
+
150
+ material.bvh.updateFrom( bvh );
151
+ material.normalAttribute.updateFrom( geometry.attributes.normal );
152
+ material.tangentAttribute.updateFrom( geometry.attributes.tangent );
153
+ material.uvAttribute.updateFrom( geometry.attributes.uv );
154
+ material.materialIndexAttribute.updateFrom( geometry.attributes.materialIndex );
155
+ material.textures.setTextures( this.renderer, 2048, 2048, textures );
156
+ material.materials.updateFrom( materials, textures );
157
+ material.setDefine( 'MATERIAL_LENGTH', materials.length );
158
+ ptRenderer.reset();
159
+
160
+ } );
161
+
162
+ }
163
+
164
+ setEnvironment( envMap ) {
165
+
166
+ this.scene.environment = envMap;
167
+
168
+ }
169
+
170
+ init() {
171
+
172
+ const { ptRenderer, renderer, fsQuad, camera } = this;
173
+
174
+ const controls = new OrbitControls( camera, renderer.domElement );
175
+ let delaySamples = 0;
176
+ controls.addEventListener( 'change', () => {
177
+
178
+ const tiles = this.ptRenderer.tiles;
179
+ if ( tiles.x * tiles.y !== 1 ) {
180
+
181
+ delaySamples = 1;
182
+
183
+ }
184
+
185
+ ptRenderer.reset();
186
+
187
+ } );
188
+
189
+ renderer.setAnimationLoop( () => {
190
+
191
+ if ( this.pausePathTracing ) {
192
+
193
+ return;
194
+
195
+ }
196
+
197
+ if ( this._needsSizeUpdate ) {
198
+
199
+ this._updateSize();
200
+ this.ptRenderer.reset();
201
+
202
+ }
203
+
204
+ if ( this.model ) {
205
+
206
+ // ensure materials are updated
207
+ const { ptRenderer, ptMaterials, ptTextures } = this;
208
+ ptRenderer.material.materials.updateFrom( ptMaterials, ptTextures );
209
+
210
+ if ( this.enablePathTracing && delaySamples === 0 ) {
211
+
212
+ camera.updateMatrixWorld();
213
+
214
+ for ( let i = 0, l = this.samplesPerFrame; i < l; i ++ ) {
215
+
216
+ ptRenderer.update();
217
+
218
+ }
219
+
220
+ if ( ptRenderer.samples < 1 ) {
221
+
222
+ renderer.render( this.scene, this.camera );
223
+
224
+ }
225
+
226
+ renderer.autoClear = false;
227
+ fsQuad.material.map = ptRenderer.target.texture;
228
+ fsQuad.render( renderer );
229
+ renderer.autoClear = true;
230
+
231
+ } else {
232
+
233
+ if ( delaySamples > 0 ) {
234
+
235
+ delaySamples --;
236
+
237
+ }
238
+
239
+ renderer.render( this.scene, this.camera );
240
+
241
+ }
242
+
243
+ } else {
244
+
245
+ renderer.clear();
246
+
247
+ }
248
+
249
+ if ( this.onRender ) {
250
+
251
+ this.onRender();
252
+
253
+ }
254
+
255
+ } );
256
+
257
+ }
258
+
259
+ }