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.
- package/README.md +53 -530
- package/build/index.module.js +262 -50
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +261 -48
- package/build/index.umd.cjs.map +1 -1
- package/package.json +28 -11
- package/src/core/WebGLPathTracer.js +8 -0
- package/src/core/utils/sceneUpdateUtils.js +8 -12
- package/src/detectors/PrecisionMaterial.js +9 -9
- package/src/index.js +1 -0
- package/src/objects/PhysicalSpotLight.js +2 -0
- package/src/shader/bsdf/bsdf_functions.glsl.js +0 -1
- package/src/textures/BlueNoiseTexture.js +6 -6
- package/src/textures/ProceduralEquirectTexture.js +8 -7
- package/src/textures/turquinMetal.png +0 -0
- package/src/uniforms/EquirectHdrInfoUniform.js +9 -4
- package/src/uniforms/FloatAttributeTextureArray.js +11 -11
- package/src/uniforms/MaterialsTexture.js +9 -9
- package/src/webgpu/API.md +762 -0
- package/src/webgpu/AtlasTexture.js +471 -0
- package/src/webgpu/BlurredEnvMapGenerator.js +129 -0
- package/src/webgpu/EquirectBackgroundInfo.js +97 -0
- package/src/webgpu/EquirectHdrInfoNode.js +151 -0
- package/src/webgpu/LightsInfoNode.js +224 -0
- package/src/webgpu/MegaKernelPathTracer.js +277 -0
- package/src/webgpu/PathTracerBackend.js +216 -0
- package/src/webgpu/TurquinTexture.js +169 -0
- package/src/webgpu/WaveFrontPathTracer.js +586 -0
- package/src/webgpu/WebGPUPathTracer.js +1302 -0
- package/src/webgpu/compute/ComputeKernel.js +92 -0
- package/src/webgpu/compute/CopyBufferKernel.js +40 -0
- package/src/webgpu/compute/PathTracerMegaKernel.js +470 -0
- package/src/webgpu/compute/SampleDebugKernel.js +51 -0
- package/src/webgpu/compute/TallySampleCountsKernel.js +111 -0
- package/src/webgpu/compute/ZeroOutBufferKernel.js +35 -0
- package/src/webgpu/compute/ZeroOutKernel.js +31 -0
- package/src/webgpu/compute/wavefront/LogicKernel.js +349 -0
- package/src/webgpu/compute/wavefront/MaterialKernel.js +371 -0
- package/src/webgpu/compute/wavefront/PopulatePixelIndicesKernel.js +72 -0
- package/src/webgpu/compute/wavefront/QueueLengthToDispatchKernel.js +38 -0
- package/src/webgpu/compute/wavefront/ResetSlotsKernel.js +66 -0
- package/src/webgpu/compute/wavefront/TraceRayKernel.js +71 -0
- package/src/webgpu/compute/wavefront/TraceShadowRayKernel.js +67 -0
- package/src/webgpu/compute/wavefront/structs.js +155 -0
- package/src/webgpu/constants.js +62 -0
- package/src/webgpu/denoise/OIDNDenoiser.js +431 -0
- package/src/webgpu/index.d.ts +176 -0
- package/src/webgpu/index.js +11 -0
- package/src/webgpu/materials/GltfCompliantMaterial.js +608 -0
- package/src/webgpu/materials/GraphMaterial.js +331 -0
- package/src/webgpu/materials/PathtracingMaterial.js +89 -0
- package/src/webgpu/materials/RenderToScreenMaterial.js +150 -0
- package/src/webgpu/materials/debug/AtlasDebugMaterial.js +41 -0
- package/src/webgpu/materials/debug/SampleDensityMaterial.js +61 -0
- package/src/webgpu/nodes/PathtracerBVHComputeData.js +1037 -0
- package/src/webgpu/nodes/debugBounds.wgsl.js +228 -0
- package/src/webgpu/nodes/eon.wgsl.js +262 -0
- package/src/webgpu/nodes/ggx.wgsl.js +190 -0
- package/src/webgpu/nodes/lights.wgsl.js +203 -0
- package/src/webgpu/nodes/material.wgsl.js +990 -0
- package/src/webgpu/nodes/rand/bluedither.wgsl.js +92 -0
- package/src/webgpu/nodes/rand/pcg.wgsl.js +81 -0
- package/src/webgpu/nodes/rand/sobol.wgsl.js +287 -0
- package/src/webgpu/nodes/random.wgsl.js +20 -0
- package/src/webgpu/nodes/reset.wgsl.js +21 -0
- package/src/webgpu/nodes/sampling.wgsl.js +193 -0
- package/src/webgpu/nodes/sheen.wgsl.js +148 -0
- package/src/webgpu/nodes/structs.wgsl.js +272 -0
- package/src/webgpu/nodes/utils.wgsl.js +366 -0
- package/src/webgpu/shims/ArrayCameraShim.js +74 -0
- package/src/webgpu/shims/EquirectCameraShim.js +37 -0
- package/src/webgpu/shims/PhysicalCameraShim.js +151 -0
- package/src/webgpu/upscale/FSRUpscaler.js +108 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { Color, SRGBColorSpace, Vector2 } from 'three';
|
|
2
|
+
import { MeshBasicNodeMaterial, StorageBufferAttribute } from 'three/webgpu';
|
|
3
|
+
import { globalId, storage, uniform, uv, varying, wgslFn } from 'three/tsl';
|
|
4
|
+
import { wgslTagFn, wgslTagCode } from 'three-mesh-bvh/webgpu';
|
|
5
|
+
|
|
6
|
+
// pastel palette evenly spaced along the color wheel, looped when there are more graphs
|
|
7
|
+
const GRAPH_COLOR_COUNT = 10;
|
|
8
|
+
|
|
9
|
+
function getGraphColor( i ) {
|
|
10
|
+
|
|
11
|
+
const hue = ( i % GRAPH_COLOR_COUNT ) / GRAPH_COLOR_COUNT;
|
|
12
|
+
return new Color().setHSL( hue, 0.65, 0.7, SRGBColorSpace );
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// anti-aliased background with x / y axes and 1-unit markers
|
|
17
|
+
const getBackgroundFunc = wgslFn( /* wgsl */`
|
|
18
|
+
fn getBackground( point: vec2f, steepness: f32 ) -> vec3f {
|
|
19
|
+
|
|
20
|
+
let pw = fwidth( point );
|
|
21
|
+
let halfWidth = pw * 0.5;
|
|
22
|
+
|
|
23
|
+
// x, y axes
|
|
24
|
+
var distToZero = smoothstep(
|
|
25
|
+
- halfWidth * 0.5,
|
|
26
|
+
halfWidth * 0.5,
|
|
27
|
+
abs( point ) - pw
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// 1 unit markers
|
|
31
|
+
let modAxis = fract( abs( point + vec2f( 0.5 ) ) ) - 0.5;
|
|
32
|
+
var distToAxis = smoothstep(
|
|
33
|
+
- halfWidth,
|
|
34
|
+
halfWidth,
|
|
35
|
+
abs( modAxis ) - pw * 0.5
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// if we're at a chart boundary then remove the artifacts
|
|
39
|
+
if ( abs( pw.y ) > steepness * 0.5 ) {
|
|
40
|
+
|
|
41
|
+
distToZero.y = 1.0;
|
|
42
|
+
distToAxis.y = 1.0;
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// mix colors into a background color
|
|
47
|
+
let axisIntensity = 1.0 - min( distToZero.x, distToZero.y );
|
|
48
|
+
let markerIntensity = 1.0 - min( distToAxis.x, distToAxis.y );
|
|
49
|
+
|
|
50
|
+
let markerColor = mix( vec3f( 0.005 ), vec3f( 0.05 ), markerIntensity );
|
|
51
|
+
return mix( markerColor, vec3f( 0.2 ), axisIntensity );
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
` );
|
|
55
|
+
|
|
56
|
+
// Fullscreen material that plots an arbitrary number of WGSL functions of the form
|
|
57
|
+
// "fn( x: f32 ) -> f32" provided via "setGraphs", either overlaid or in stacked sections.
|
|
58
|
+
export class GraphMaterial extends MeshBasicNodeMaterial {
|
|
59
|
+
|
|
60
|
+
get dim() {
|
|
61
|
+
|
|
62
|
+
return this._dim.value > 0.5;
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
set dim( v ) {
|
|
67
|
+
|
|
68
|
+
this._dim.value = v ? 1 : 0;
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get overlay() {
|
|
73
|
+
|
|
74
|
+
return this._overlay.value > 0.5;
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
set overlay( v ) {
|
|
79
|
+
|
|
80
|
+
this._overlay.value = v ? 1 : 0;
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get thickness() {
|
|
85
|
+
|
|
86
|
+
return this._thickness.value;
|
|
87
|
+
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
set thickness( v ) {
|
|
91
|
+
|
|
92
|
+
this._thickness.value = v;
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get xRange() {
|
|
97
|
+
|
|
98
|
+
return this._xRange.value;
|
|
99
|
+
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get yRange() {
|
|
103
|
+
|
|
104
|
+
return this._yRange.value;
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
get graphCount() {
|
|
109
|
+
|
|
110
|
+
return this._graphs.length;
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// the display names of the plotted graphs, in slot order
|
|
115
|
+
get graphNames() {
|
|
116
|
+
|
|
117
|
+
return this._graphs.map( entry => {
|
|
118
|
+
|
|
119
|
+
return entry.name || entry.code.match( /fn ([^(]+)/ )[ 1 ];
|
|
120
|
+
|
|
121
|
+
} );
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// the mouse position in graph space - set to a distant point to hide the markers
|
|
126
|
+
get mousePoint() {
|
|
127
|
+
|
|
128
|
+
return this._mousePoint.value;
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
constructor( options = {} ) {
|
|
133
|
+
|
|
134
|
+
const { graphs = [], ...params } = options;
|
|
135
|
+
|
|
136
|
+
super();
|
|
137
|
+
|
|
138
|
+
this.depthWrite = false;
|
|
139
|
+
this.depthTest = false;
|
|
140
|
+
|
|
141
|
+
// uniforms - created once and shared across shader rebuilds
|
|
142
|
+
this._xRange = uniform( new Vector2( - 2, 2 ) );
|
|
143
|
+
this._yRange = uniform( new Vector2( - 2, 2 ) );
|
|
144
|
+
this._thickness = uniform( window.devicePixelRatio );
|
|
145
|
+
this._dim = uniform( 1 );
|
|
146
|
+
this._overlay = uniform( 1 );
|
|
147
|
+
this._displayMask = uniform( 0xffffffff, 'uint' );
|
|
148
|
+
this._mousePoint = uniform( new Vector2( 1e10, 1e10 ) );
|
|
149
|
+
this._graphs = [];
|
|
150
|
+
|
|
151
|
+
this.setGraphs( graphs );
|
|
152
|
+
this.setValues( params );
|
|
153
|
+
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// toggles the visibility of the graph at the given index
|
|
157
|
+
setGraphVisible( index, visible ) {
|
|
158
|
+
|
|
159
|
+
const mask = this._displayMask;
|
|
160
|
+
if ( visible ) {
|
|
161
|
+
|
|
162
|
+
mask.value = ( mask.value | ( 1 << index ) ) >>> 0;
|
|
163
|
+
|
|
164
|
+
} else {
|
|
165
|
+
|
|
166
|
+
mask.value = ( mask.value & ~ ( 1 << index ) ) >>> 0;
|
|
167
|
+
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
getGraphVisible( index ) {
|
|
173
|
+
|
|
174
|
+
return ( ( this._displayMask.value >> index ) & 1 ) === 1;
|
|
175
|
+
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
getGraphColor( index ) {
|
|
179
|
+
|
|
180
|
+
return getGraphColor( index );
|
|
181
|
+
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Replaces the set of plotted functions - each an "fn( x: f32 ) -> f32" wgsl function node -
|
|
185
|
+
// and rebuilds the shader to display them.
|
|
186
|
+
setGraphs( graphs ) {
|
|
187
|
+
|
|
188
|
+
const { _xRange, _yRange, _thickness, _dim, _overlay, _displayMask, _mousePoint } = this;
|
|
189
|
+
|
|
190
|
+
this._graphs = [ ...graphs ];
|
|
191
|
+
|
|
192
|
+
// unroll one evaluation block per graph, accumulated into a single code chunk
|
|
193
|
+
let graphBody = '';
|
|
194
|
+
graphs.forEach( ( graph, i ) => {
|
|
195
|
+
|
|
196
|
+
const color = getGraphColor( i );
|
|
197
|
+
graphBody = wgslTagCode/* wgsl */`
|
|
198
|
+
${ graphBody }
|
|
199
|
+
|
|
200
|
+
{
|
|
201
|
+
let visible = ( ( displayMask >> ${ i }u ) & 1u ) == 1u;
|
|
202
|
+
if ( visible && ( sectionCount < 1.5 || section == ${ i } ) ) {
|
|
203
|
+
|
|
204
|
+
let delta = ${ graph }( point.x ) - point.y;
|
|
205
|
+
var halfDdf = fwidth( delta ) * 0.5;
|
|
206
|
+
if ( pointYWidth > yWidth * 0.5 ) {
|
|
207
|
+
|
|
208
|
+
halfDdf = 0.0;
|
|
209
|
+
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
var graph = smoothstep( - halfDdf, halfDdf, abs( delta ) - ${ _thickness } * halfDdf );
|
|
213
|
+
if ( dimmed ) {
|
|
214
|
+
|
|
215
|
+
graph = mix( 1.0, graph, 0.1 );
|
|
216
|
+
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
color = mix( vec3f( ${ color.r }, ${ color.g }, ${ color.b } ), color, graph );
|
|
220
|
+
|
|
221
|
+
// circle marker at the mouse line's intersection with the curve
|
|
222
|
+
let intersection = vec2f( mousePoint.x, ${ graph }( mousePoint.x ) );
|
|
223
|
+
let circleDist = length( ( point - intersection ) / pw );
|
|
224
|
+
let circle = smoothstep( 4.0, 5.0, circleDist );
|
|
225
|
+
color = mix( vec3f( ${ color.r }, ${ color.g }, ${ color.b } ), color, circle );
|
|
226
|
+
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
}
|
|
230
|
+
`;
|
|
231
|
+
|
|
232
|
+
} );
|
|
233
|
+
|
|
234
|
+
// the section math assumes at least one graph
|
|
235
|
+
const count = Math.max( graphs.length, 1 );
|
|
236
|
+
const graphShader = wgslTagFn/* wgsl */`
|
|
237
|
+
fn graphShader( vUv: vec2f ) -> vec4f {
|
|
238
|
+
|
|
239
|
+
var sectionCount = ${ count }.0;
|
|
240
|
+
if ( ${ _overlay } > 0.5 ) {
|
|
241
|
+
|
|
242
|
+
sectionCount = 1.0;
|
|
243
|
+
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let yWidth = abs( ${ _yRange }.y - ${ _yRange }.x );
|
|
247
|
+
|
|
248
|
+
// separate into sections
|
|
249
|
+
let scaledY = sectionCount * vUv.y;
|
|
250
|
+
let sectionY = fract( scaledY );
|
|
251
|
+
let section = clamp( i32( sectionCount - floor( scaledY ) - 1.0 ), 0, ${ count - 1 } );
|
|
252
|
+
|
|
253
|
+
// the graph-space point for this pixel
|
|
254
|
+
let point = vec2f(
|
|
255
|
+
mix( ${ _xRange }.x, ${ _xRange }.y, vUv.x ),
|
|
256
|
+
mix( ${ _yRange }.x, ${ _yRange }.y, sectionY )
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
let pw = fwidth( point );
|
|
260
|
+
let pointYWidth = pw.y;
|
|
261
|
+
let dimmed = ${ _dim } > 0.5 && ( point.x < 0.0 || point.y < 0.0 );
|
|
262
|
+
let displayMask = ${ _displayMask };
|
|
263
|
+
let mousePoint = ${ _mousePoint };
|
|
264
|
+
|
|
265
|
+
var color = ${ getBackgroundFunc }( point, yWidth );
|
|
266
|
+
|
|
267
|
+
// vertical marker line at the mouse position
|
|
268
|
+
let mouseLine = smoothstep( - pw.x * 0.5, pw.x * 0.5, abs( point.x - mousePoint.x ) - pw.x );
|
|
269
|
+
color = mix( vec3f( 0.35 ), color, mouseLine );
|
|
270
|
+
|
|
271
|
+
${ graphBody }
|
|
272
|
+
|
|
273
|
+
return vec4f( color, 1.0 );
|
|
274
|
+
|
|
275
|
+
}
|
|
276
|
+
`;
|
|
277
|
+
|
|
278
|
+
// NOTE: varyings cannot be referenced directly and must be passed as arguments
|
|
279
|
+
this.colorNode = graphShader( varying( uv() ) );
|
|
280
|
+
this.needsUpdate = true;
|
|
281
|
+
|
|
282
|
+
// compute kernel that evaluates every graph at the mouse x so the values can be read back.
|
|
283
|
+
// The buffer is padded to a minimum length of 2 to avoid TSL treating it as a scalar.
|
|
284
|
+
const valuesAttribute = new StorageBufferAttribute( new Float32Array( Math.max( count, 2 ) ), 1 );
|
|
285
|
+
const valuesStorage = storage( valuesAttribute, 'float' );
|
|
286
|
+
|
|
287
|
+
let valuesBody = '';
|
|
288
|
+
graphs.forEach( ( graph, i ) => {
|
|
289
|
+
|
|
290
|
+
valuesBody = wgslTagCode/* wgsl */`
|
|
291
|
+
${ valuesBody }
|
|
292
|
+
${ valuesStorage }[ ${ i } ] = ${ graph }( x );
|
|
293
|
+
`;
|
|
294
|
+
|
|
295
|
+
} );
|
|
296
|
+
|
|
297
|
+
const valuesFn = wgslTagFn/* wgsl */`
|
|
298
|
+
fn computeGraphValues( globalId: vec3u ) -> void {
|
|
299
|
+
|
|
300
|
+
let x = ${ _mousePoint }.x;
|
|
301
|
+
${ valuesBody }
|
|
302
|
+
|
|
303
|
+
}
|
|
304
|
+
`;
|
|
305
|
+
|
|
306
|
+
this._valuesAttribute?.dispose();
|
|
307
|
+
this._valuesAttribute = valuesAttribute;
|
|
308
|
+
this._valuesKernel = valuesFn( { globalId } ).computeKernel( [ 1, 1, 1 ] );
|
|
309
|
+
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
dispose() {
|
|
313
|
+
|
|
314
|
+
super.dispose();
|
|
315
|
+
this._valuesAttribute.dispose();
|
|
316
|
+
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Evaluates every graph function at the current "mousePoint.x" on the gpu and resolves to a
|
|
320
|
+
// Float32Array of the resulting values.
|
|
321
|
+
async readGraphValues( renderer ) {
|
|
322
|
+
|
|
323
|
+
const count = this._graphs.length;
|
|
324
|
+
renderer.compute( this._valuesKernel, [ 1, 1, 1 ] );
|
|
325
|
+
|
|
326
|
+
const buffer = await renderer.getArrayBufferAsync( this._valuesAttribute );
|
|
327
|
+
return new Float32Array( buffer ).slice( 0, count );
|
|
328
|
+
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
2
|
+
import { rand2, RNG_INDEX_SCATTER_DIRECTION } from '../nodes/random.wgsl.js';
|
|
3
|
+
import { diffuseDirectionFunc } from '../nodes/sampling.wgsl.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Defines a material sampled by the pathtracer
|
|
7
|
+
*/
|
|
8
|
+
export class PathtracingMaterial {
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* Called once per material
|
|
13
|
+
* Adds ability to initialize state
|
|
14
|
+
*
|
|
15
|
+
*/
|
|
16
|
+
init( /* renderer */ ) {
|
|
17
|
+
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* Must return a bsdf sampling function node with signature
|
|
23
|
+
* ( worldView: vec3f, surface: Surface ) -> ScatterRecord
|
|
24
|
+
*
|
|
25
|
+
*/
|
|
26
|
+
getBsdfNode() {
|
|
27
|
+
|
|
28
|
+
return wgslTagFn/* wgsl */`
|
|
29
|
+
|
|
30
|
+
fn bsdfSample( worldWo: vec3f, surf: SurfaceRecord ) -> ScatterRecord {
|
|
31
|
+
|
|
32
|
+
var record: ScatterRecord;
|
|
33
|
+
|
|
34
|
+
let wo = normalize( surf.normalInvBasis * worldWo );
|
|
35
|
+
let wi = ${ diffuseDirectionFunc }( wo, ${ rand2 }( ${ RNG_INDEX_SCATTER_DIRECTION } ) );
|
|
36
|
+
record.color = surf.color * max( wi.z, 0.0 );
|
|
37
|
+
record.pdf = max( wi.z, 0.0 ) / PI;
|
|
38
|
+
record.direction = normalize( surf.normalBasis * wi );
|
|
39
|
+
|
|
40
|
+
return record;
|
|
41
|
+
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
*
|
|
50
|
+
* Must return a bsdf evaluation function node with signature
|
|
51
|
+
* ( worldView: vec3f, worldLight: vec3f, surface: Surface ) -> ScatterRecord
|
|
52
|
+
* returning the bsdf value and sampling pdf for a given light direction.
|
|
53
|
+
* Used by next event estimation to weight a chosen light direction.
|
|
54
|
+
*
|
|
55
|
+
*/
|
|
56
|
+
getBsdfEvalPdfNode() {
|
|
57
|
+
|
|
58
|
+
return wgslTagFn/* wgsl */`
|
|
59
|
+
|
|
60
|
+
fn bsdfEvalPdf( worldWo: vec3f, worldWi: vec3f, surf: SurfaceRecord ) -> ScatterRecord {
|
|
61
|
+
|
|
62
|
+
var record: ScatterRecord;
|
|
63
|
+
record.direction = worldWi;
|
|
64
|
+
|
|
65
|
+
let wo = normalize( surf.normalInvBasis * worldWo );
|
|
66
|
+
let wi = normalize( surf.normalInvBasis * worldWi );
|
|
67
|
+
record.color = surf.color * max( wi.z, 0.0 );
|
|
68
|
+
record.pdf = max( wi.z, 0.0 ) / PI;
|
|
69
|
+
|
|
70
|
+
return record;
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getData() {
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
|
|
82
|
+
bsdfSample: this.getBsdfNode(),
|
|
83
|
+
bsdfEvalPdf: this.getBsdfEvalPdfNode(),
|
|
84
|
+
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { MeshBasicNodeMaterial, NoToneMapping, StorageTexture } from 'three/webgpu';
|
|
2
|
+
import { uv, varying, texture, vec4, toneMapping, uniform, wgslFn } from 'three/tsl';
|
|
3
|
+
import { wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
4
|
+
|
|
5
|
+
// TODO: we could fall back to hardware-based filtering if available but it has to be specifically
|
|
6
|
+
// requested and available on the renderer which we don't have access to immediately. It's possible this
|
|
7
|
+
// can be detected during build time via a custom node to adjust the sample approach used?
|
|
8
|
+
const sampleTexelFn = wgslFn( /* wgsl */`
|
|
9
|
+
fn sampleTexel( tex: texture_2d<f32>, coord: vec2f ) -> vec4f {
|
|
10
|
+
|
|
11
|
+
// Manual bilinear filtering using textureLoad to support filterable float32 textures
|
|
12
|
+
// on all devices
|
|
13
|
+
let size = vec2f( textureDimensions( tex, 0 ) );
|
|
14
|
+
|
|
15
|
+
// the path tracer stores its color rows top down
|
|
16
|
+
let pxCoord = vec2f( coord.x, 1.0 - coord.y ) * size - 0.5;
|
|
17
|
+
let px = vec2i( floor( pxCoord ) );
|
|
18
|
+
let fr = fract( pxCoord );
|
|
19
|
+
|
|
20
|
+
// get the four sibling samples
|
|
21
|
+
let s00 = textureLoad( tex, clamp( px, vec2i( 0 ), vec2i( size ) - 1 ), 0 );
|
|
22
|
+
let s10 = textureLoad( tex, clamp( px + vec2i( 1, 0 ), vec2i( 0 ), vec2i( size ) - 1 ), 0 );
|
|
23
|
+
let s01 = textureLoad( tex, clamp( px + vec2i( 0, 1 ), vec2i( 0 ), vec2i( size ) - 1 ), 0 );
|
|
24
|
+
let s11 = textureLoad( tex, clamp( px + vec2i( 1, 1 ), vec2i( 0 ), vec2i( size ) - 1 ), 0 );
|
|
25
|
+
|
|
26
|
+
// interpolate on the x axis
|
|
27
|
+
let y0 = mix( s00, s10, fr.x );
|
|
28
|
+
let y1 = mix( s01, s11, fr.x );
|
|
29
|
+
|
|
30
|
+
// then y
|
|
31
|
+
return mix( y0, y1, fr.y );
|
|
32
|
+
|
|
33
|
+
}
|
|
34
|
+
` );
|
|
35
|
+
|
|
36
|
+
// Material to apply tone mapping _before_ applying alpha blending
|
|
37
|
+
export class RenderToScreenNodeMaterial extends MeshBasicNodeMaterial {
|
|
38
|
+
|
|
39
|
+
get texture() {
|
|
40
|
+
|
|
41
|
+
return this._texNode.value;
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
set texture( v ) {
|
|
46
|
+
|
|
47
|
+
this._texNode.value = v;
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get fromTexture() {
|
|
52
|
+
|
|
53
|
+
return this._fromTexNode.value;
|
|
54
|
+
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
set fromTexture( v ) {
|
|
58
|
+
|
|
59
|
+
this._fromTexNode.value = v;
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get toneMapping() {
|
|
64
|
+
|
|
65
|
+
return this._toneMapping.toneMapping;
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
set toneMapping( v ) {
|
|
70
|
+
|
|
71
|
+
this._toneMapping.setToneMapping( v );
|
|
72
|
+
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get transition() {
|
|
76
|
+
|
|
77
|
+
return this._transitionUniform.value;
|
|
78
|
+
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
set transition( v ) {
|
|
82
|
+
|
|
83
|
+
this._transitionUniform.value = v;
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get exposure() {
|
|
88
|
+
|
|
89
|
+
return this._toneMapping.exposureNode.value;
|
|
90
|
+
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
set exposure( v ) {
|
|
94
|
+
|
|
95
|
+
this._toneMapping.exposureNode.value = v;
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
constructor( params ) {
|
|
100
|
+
|
|
101
|
+
super();
|
|
102
|
+
|
|
103
|
+
const texNode = texture( new StorageTexture() );
|
|
104
|
+
this._texNode = texNode;
|
|
105
|
+
|
|
106
|
+
const fromTexNode = texture( new StorageTexture() );
|
|
107
|
+
this._fromTexNode = fromTexNode;
|
|
108
|
+
|
|
109
|
+
const texUV = varying( uv() );
|
|
110
|
+
|
|
111
|
+
const transitionUniform = uniform( 1.0 );
|
|
112
|
+
this._transitionUniform = transitionUniform;
|
|
113
|
+
|
|
114
|
+
// NOTE: varyings cannot be referenced directly and must be passed as arguments
|
|
115
|
+
const getFadedColorFn = wgslTagFn/* wgsl */`
|
|
116
|
+
fn fade( uv: vec2f ) -> vec4f {
|
|
117
|
+
|
|
118
|
+
if ( ${ transitionUniform } <= 0.0 ) {
|
|
119
|
+
|
|
120
|
+
return ${ sampleTexelFn }( ${ fromTexNode }, uv );
|
|
121
|
+
|
|
122
|
+
} else if ( ${ transitionUniform } >= 1.0 ) {
|
|
123
|
+
|
|
124
|
+
return ${ sampleTexelFn }( ${ texNode }, uv );
|
|
125
|
+
|
|
126
|
+
} else {
|
|
127
|
+
|
|
128
|
+
let col0 = ${ sampleTexelFn }( ${ fromTexNode }, uv );
|
|
129
|
+
let col1 = ${ sampleTexelFn }( ${ texNode }, uv );
|
|
130
|
+
return mix( col0, col1, ${ transitionUniform } );
|
|
131
|
+
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
}
|
|
135
|
+
`;
|
|
136
|
+
|
|
137
|
+
const toneMappingNode = toneMapping( NoToneMapping, 1.0, getFadedColorFn( texUV ) );
|
|
138
|
+
this._toneMapping = toneMappingNode;
|
|
139
|
+
|
|
140
|
+
// apply alpha _after_ applying tone mapping
|
|
141
|
+
// NOTE: alpha is being multiplied twice here to accommodate some odd blending in three.js
|
|
142
|
+
// See mrdoob/three.js#33104. It's possible this should be removed or rethought once fixed.
|
|
143
|
+
this.transparent = true;
|
|
144
|
+
this.colorNode = vec4( toneMappingNode.rgb, toneMappingNode.a );
|
|
145
|
+
|
|
146
|
+
this.setValues( params );
|
|
147
|
+
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { MeshBasicNodeMaterial, DataTexture, NoBlending } from 'three/webgpu';
|
|
2
|
+
import { uv, varying, texture, uniform, wgslFn } from 'three/tsl';
|
|
3
|
+
|
|
4
|
+
export class AtlasDebugMaterial extends MeshBasicNodeMaterial {
|
|
5
|
+
|
|
6
|
+
set texture( v ) {
|
|
7
|
+
|
|
8
|
+
this._texNode.value = v;
|
|
9
|
+
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
set layer( v ) {
|
|
13
|
+
|
|
14
|
+
this._layerUniform.value = v;
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
|
|
20
|
+
super();
|
|
21
|
+
|
|
22
|
+
this.blending = NoBlending;
|
|
23
|
+
|
|
24
|
+
this._texNode = texture( new DataTexture() );
|
|
25
|
+
this._layerUniform = uniform( 0 );
|
|
26
|
+
|
|
27
|
+
// Shows one layer of the atlas array texture.
|
|
28
|
+
this.colorNode = wgslFn( /* wgsl */ `
|
|
29
|
+
fn atlasDebug( tex: texture_2d_array<f32>, coord: vec2f, layer: f32 ) -> vec4f {
|
|
30
|
+
|
|
31
|
+
// flip y so texel origin shows at the top-left
|
|
32
|
+
let dims = vec2f( textureDimensions( tex, 0 ).xy );
|
|
33
|
+
let texel = vec2i( vec2f( coord.x, 1.0 - coord.y ) * dims );
|
|
34
|
+
return textureLoad( tex, texel, i32( layer ), 0 );
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
` )( this._texNode, varying( uv() ), this._layerUniform );
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { MeshBasicNodeMaterial, NoBlending, StorageTexture } from 'three/webgpu';
|
|
2
|
+
import { uv, varying, texture, uniform } from 'three/tsl';
|
|
3
|
+
import { wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
4
|
+
import { heatColorFn } from '../../nodes/debugBounds.wgsl.js';
|
|
5
|
+
import { SAMPLE_COUNT_MASK } from '../../constants.js';
|
|
6
|
+
|
|
7
|
+
// Shows how many samples each pixel has accumulated
|
|
8
|
+
export class SampleDensityMaterial extends MeshBasicNodeMaterial {
|
|
9
|
+
|
|
10
|
+
set texture( v ) {
|
|
11
|
+
|
|
12
|
+
this._texNode.value = v;
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
set minCount( v ) {
|
|
17
|
+
|
|
18
|
+
this._minUniform.value = v;
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
set maxCount( v ) {
|
|
23
|
+
|
|
24
|
+
this._maxUniform.value = v;
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
constructor() {
|
|
29
|
+
|
|
30
|
+
super();
|
|
31
|
+
|
|
32
|
+
this.blending = NoBlending;
|
|
33
|
+
|
|
34
|
+
this._texNode = texture( new StorageTexture( 1, 1 ) );
|
|
35
|
+
this._minUniform = uniform( 0 );
|
|
36
|
+
this._maxUniform = uniform( 1 );
|
|
37
|
+
|
|
38
|
+
const sampleDensity = wgslTagFn/* wgsl */`
|
|
39
|
+
fn sampleDensity( tex: texture_2d<u32>, coord: vec2f, minCount: f32, maxCount: f32 ) -> vec4f {
|
|
40
|
+
|
|
41
|
+
let dims = vec2f( textureDimensions( tex, 0 ) );
|
|
42
|
+
let texel = vec2i( coord * dims );
|
|
43
|
+
|
|
44
|
+
// the high bits hold the ray state flags rather than part of the count
|
|
45
|
+
let samples = textureLoad( tex, texel, 0 ).r & ${ SAMPLE_COUNT_MASK }u;
|
|
46
|
+
|
|
47
|
+
// stretched across the recorded range rather than from zero, otherwise a converged
|
|
48
|
+
// image with counts bunched near the max reads as uniformly hot
|
|
49
|
+
let range = max( maxCount - minCount, 1.0 );
|
|
50
|
+
let t = clamp( ( f32( samples ) - minCount ) / range, 0.0, 1.0 );
|
|
51
|
+
|
|
52
|
+
return vec4f( ${ heatColorFn }( t ), 1.0 );
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
this.colorNode = sampleDensity( this._texNode, varying( uv() ), this._minUniform, this._maxUniform );
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
}
|