Progressive path tracer running on WebGPU. Call WebGPUPathTracer#setScene once,
then WebGPUPathTracer#renderSample every frame to accumulate samples into the canvas.
The scene is captured when setScene is called, so changes to geometry, materials, lights, or
the environment afterward require the matching update* function. Any change that invalidates
the accumulated image restarts it.
import { ACESFilmicToneMapping, Color, EquirectangularReflectionMapping } from 'three/webgpu';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
import { createFloor } from './floor.js';
import { WebGPUPathTracer } from 'three-gpu-pathtracer/webgpu';
// scene, camera and renderer are initialized here
const URL = 'https://raw.githubusercontent.com/gkjohnson/3d-demo-data/main/models/nasa-m2020/Perseverance.glb';
const ENV_URL = 'https://raw.githubusercontent.com/gkjohnson/3d-demo-data/main/hdri/chinese_garden_1k.hdr';
const { scene: model } = await new GLTFLoader()
.setDRACOLoader( new DRACOLoader() )
.loadAsync( URL );
const environment = await new HDRLoader().loadAsync( ENV_URL );
environment.mapping = EquirectangularReflectionMapping;
scene.environment = environment;
scene.background = new Color( 0xeeeeee );
renderer.toneMapping = ACESFilmicToneMapping;
model.position.z = - 0.32;
scene.add( model, createFloor() );
camera.position.set( - 3.2, 2.2, 4.6 );
controls.target.set( 0, 0.9, 0 );
controls.update();
const pathTracer = new WebGPUPathTracer( renderer );
// mobile GPUs are slower: fewer paths per frame keep frames fast, and fewer pixels converge sooner
if ( matchMedia( '(pointer: coarse)' ).matches ) {
pathTracer.frameBudget = 50000;
renderer.setPixelRatio( window.devicePixelRatio / 2 );
}
pathTracer.maxSamples = 32;
pathTracer.setScene( scene, camera );
controls.addEventListener( 'change', () => pathTracer.updateCamera() );
window.addEventListener( 'resize', () => pathTracer.updateCamera() );
renderer.setAnimationLoop( () => pathTracer.renderSample() );On this page
- Properties
- maxBounces
- frameBudget
- maxTransparentBounces
- maxSamples
- transmissiveBackground
- filterGlossyFactor
- multipleImportanceSampling
- clampDirect
- clampIndirect
- target
- fadeState
- lowResTarget
- lowResMode
- textureAtlas
- minSamples
- renderDelay
- fadeDuration
- dynamicLowRes
- lowResScale
- renderScale
- synchronizeRenderSize
- generateMissingAttributes
- commonAttributes
- stableNoise
- pause
- Methods
- constructor
- getSampleCountsAsync
- setMultipleImportanceSampling
- setDenoiser
- setUpscaler
- setScene
- setRandom
- setCamera
- updateMaterials
- updateTransforms
- updateCamera
- updateEnvironment
- updateLights
- setSize
- reset
- renderSample
- renderDebugBounds
- renderTextureAtlas
- renderSampleDensity
- dispose
- getRenderTime
Properties
maxBounces: number = 15
Maximum number of times a ray can scatter before the path is terminated. Higher values resolve more indirect light at the cost of speed.
frameBudget: number = 250000
Number of path slots dispatched per WebGPUPathTracer#renderSample call,
independent of resolution. Raising it trades frame rate for convergence speed.
maxTransparentBounces: number = 5
Maximum number of alpha tested surfaces a ray can pass through. Counted separately from
WebGPUPathTracer#maxBounces so foliage and cutouts cannot exhaust the bounce budget.
maxSamples: number = 0
Stops accumulating once every pixel reaches this many samples. A denoiser only runs once the
render has stopped, so it has no effect while this is 0.
transmissiveBackground: number = TRANSMISSIVE_BACKGROUND_OVERLAY
How the background is treated behind transmissive surfaces. One of
TRANSMISSIVE_BACKGROUND_OVERLAY, TRANSMISSIVE_BACKGROUND_ENVIRONMENT, or
TRANSMISSIVE_BACKGROUND_TRANSPARENT.
filterGlossyFactor: number = 1
Blurs sharp reflections seen through rough surfaces to suppress fireflies. Higher values remove more noise and more detail.
multipleImportanceSampling: boolean = true
Whether to combine light sampling and bsdf sampling with MIS. Disabling it makes lights noticeably noisier.
clampDirect: number = 0
Upper bound on the contribution of a directly lit path segment, for suppressing fireflies.
clampIndirect: number = 10
Upper bound on the contribution of an indirectly lit path segment, for suppressing fireflies.
The render target the accumulated samples are written into.
Progress of the fade from the low resolution preview to the full render, from 0 to 1.
The low resolution preview rendered while WebGPUPathTracer#renderDelay elapses.
Whether the tracer is currently rendering the low resolution preview.
The atlas every scene texture is packed into for the kernels to sample.
minSamples: number = 1
Samples every pixel must reach before the full resolution render is faded in.
renderDelay: number = 500
Milliseconds to show the low resolution preview for after a reset.
fadeDuration: number = 500
Milliseconds taken to cross fade from the preview to the full render.
dynamicLowRes: boolean = true
Whether to render a low resolution preview while the camera is moving.
lowResScale: number = 0.1
Resolution of the low resolution preview, as a fraction of the render size.
renderScale: number = 1
Resolution the path tracer renders at, as a fraction of the canvas size. Lowering it converges faster at the cost of detail, and pairs with an upscaler.
synchronizeRenderSize: boolean = true
Whether to track the canvas size automatically. Set to false to drive the render
size with WebGPUPathTracer#setSize.
generateMissingAttributes: boolean = true
Whether to generate the attributes in WebGPUPathTracer#commonAttributes on
geometry that is missing them when the scene is set.
commonAttributes: Array<string> = [ 'normal', 'tangent' ]
Vertex attributes every geometry is expected to provide.
stableNoise: boolean = true
Whether to restart the random sequence on reset so a given camera always produces the same image.
pause: boolean = false
Whether to stop accumulating samples. The last image keeps being presented.
Methods
new WebGPUPathTracer( renderer: WebGPURenderer )async getSampleCountsAsync(): Promise<SampleCounts>Measures the per pixel sample counts. Use min for convergence checks and avg to display.
setMultipleImportanceSampling( value: boolean ): voidsetDenoiser( denoiser: OIDNDenoiser | null ): voidAttaches a denoiser, run once the render settles and displayed in place of the raw image. Settings live on the instance. Pass null to remove it.
setUpscaler( upscaler: FSRUpscaler | null ): voidAttaches an upscaler, run before the image is presented so the render can happen below the canvas resolution. Settings live on the instance. Pass null to remove it.
setScene( scene: Scene, camera: Camera ): voidCaptures the scene and camera and builds the acceleration structures the kernels trace
against. Call this once, then use the update* functions for later changes.
setRandom(
// One of the Random Strategies constants, such as `RANDOM_BLUE_DITHER`.
random: Object
): voidReplaces the random number generator and recompiles the kernels.
setCamera( camera: Camera ): voidReplaces the camera the rays are generated from. Cameras with a getCameraRayFn, such as
PhysicalCamera and EquirectCamera, provide their own ray generation.
updateMaterials(): voidRe-reads the material properties and textures from the scene. Call after changing any material.
updateTransforms(): voidRe-reads the world matrices from the scene. Call after moving any object.
updateCamera(): voidRe-reads the camera transform and projection. Call after moving the camera.
updateEnvironment(): voidRe-reads scene.environment and scene.background, along with their intensity, rotation,
and blur.
updateLights(): voidRe-collects the lights in the scene. Call after adding, removing, or changing one.
setSize( x: number, y: number ): voidSets the resolution the path tracer renders at. Only used when
WebGPUPathTracer#synchronizeRenderSize is false.
reset(): voidDiscards the accumulated image and starts over. The update* functions call this for you.
renderSample(): voidAccumulates one round of samples and presents the result to the canvas. Call once per frame.
renderDebugBounds(
options?: {
// Count the top level object bounding boxes.
displayTLAS: boolean = true,
// Count the per object geometry bounding boxes.
displayBLAS: boolean = true,
// Only count boxes in front of the nearest hit, so occluded boxes do not contribute. Culling and material transparency are honored.
stopAtSurface: boolean = false,
// Node count that saturates to full heat.
saturationCount: number = 64
}
): voidDraws a heatmap of how many BVH bounding boxes each camera ray intersects, for diagnosing box overlap and traversal cost. Hotter pixels traverse more nodes.
renderTextureAtlas( layer: number = 0 ): voidDraws one layer of the texture atlas to the canvas, for debugging texture packing.
renderSampleDensity(): voidDraws a heatmap of the per pixel sample counts, for spotting pixels that converge slowly.
dispose(): voidFrees every GPU resource held by the tracer, including any attached denoiser and upscaler.
getRenderTime(): numberMilliseconds elapsed since the last reset.