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
- Events
- complete
- progress
- Properties
- maxBounces
- frameBudget
- frameTime
- maxTransparentBounces
- maxSubsurfaceBounces
- maxSamples
- complete
- transmissiveBackground
- filterGlossyFactor
- multipleImportanceSampling
- clampDirect
- clampIndirect
- target
- fadeState
- lowResTarget
- lowResMode
- compiling
- textureAtlas
- minSamples
- renderDelay
- fadeDuration
- dynamicLowRes
- lowResScale
- adaptiveDynamicRes
- adaptiveFrameBudget
- targetFrameTime
- renderScale
- synchronizeRenderSize
- renderToCanvas
- asyncCompile
- generateMissingAttributes
- commonAttributes
- stableNoise
- pause
- Methods
- constructor
- getSampleCountsAsync
- setMultipleImportanceSampling
- setDenoiser
- setUpscaler
- setScene
- setBVHWorker
- setSceneAsync
- setRandom
- setCamera
- updateMaterials
- updateTransforms
- updateCamera
- updateEnvironment
- updateLights
- setSize
- reset
- renderSample
- renderDebugBounds
- renderTextureAtlas
- renderSampleDensity
- renderAuxBuffer
- dispose
- getRenderTime
Events
{ type: 'complete' }Fired once every pixel has reached maxSamples and an attached denoiser has finished. Fires
again after a reset, or after maxSamples is raised, once the render has caught up.
{ type: 'progress' }Fired as the render advances. stage is "building", "compiling", "rendering" or "denoising", and
progress is the fraction of it done, or null when unknown. "rendering" also gives the sample
counts as samples, reported a few times a second.
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 steps dispatched per WebGPUPathTracer#renderSample call,
independent of resolution. Raising it trades frame rate for convergence speed.
GPU time in milliseconds of a recent WebGPUPathTracer#renderSample call. Only measured
when the renderer is created with trackTimestamp: true.
maxTransparentBounces: number = 15
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.
maxSubsurfaceBounces: number = 256
Maximum number of scatter events a path can take inside a subsurface scattering volume. Counted
separately from WebGPUPathTracer#maxBounces. Paths that reach it end, darkening dense volumes.
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.
complete: boolean
Whether every pixel has reached WebGPUPathTracer#maxSamples and an attached denoiser
has finished, as of the last measurement. Always false while rendering indefinitely.
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.
Whether the kernels are compiling, such as after the scene or material changes. Nothing new renders until they finish and the canvas keeps the last frame, so an app may draw its own preview in the meantime.
The atlas every scene texture is packed into for the kernels to sample.
minSamples: number = 1
Average samples per pixel the full resolution render must reach before it is faded in.
renderDelay: number = 16
Milliseconds to show the low resolution preview for after a reset.
fadeDuration: number = 250
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. Adjusted when
WebGPUPathTracer#adaptiveDynamicRes is set. The preview never has more pixels than a
frame of WebGPUPathTracer#frameBudget can render.
adaptiveDynamicRes: boolean = true
Whether WebGPUPathTracer#lowResScale is adjusted to the pixels a frame of
WebGPUPathTracer#frameBudget can render.
adaptiveFrameBudget: boolean = true
Whether WebGPUPathTracer#frameBudget is adjusted to keep each
WebGPUPathTracer#renderSample call near WebGPUPathTracer#targetFrameTime of
GPU time. Requires a renderer created with trackTimestamp: true.
targetFrameTime: number = 16
GPU time in milliseconds per frame targeted by WebGPUPathTracer#adaptiveFrameBudget.
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.
renderToCanvas: boolean = true
Whether WebGPUPathTracer#renderSample draws the result to the current render
target, the canvas by default. Set to false to only accumulate, as when baking to a
texture read through WebGPUPathTracer#target.
asyncCompile: boolean = false
Whether to compile the kernels asynchronously so a compile doesn't stall the page. Nothing
new renders while WebGPUPathTracer#compiling is true.
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. The render restarts when the auxiliary buffers it needs are added or dropped.
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.
setBVHWorker( WorkerConstructor: function ): voidSets the worker class WebGPUPathTracer#setSceneAsync builds the mesh geometry BVHs
with, such as GenerateMeshBVHWorker from three-mesh-bvh. Several are created and disposed
per call so geometries build in parallel.
async setSceneAsync( scene: Scene, camera: Camera ): Promise<void>Like WebGPUPathTracer#setScene but builds the mesh geometry BVHs in the workers set
with WebGPUPathTracer#setBVHWorker so the page is not blocked. Skinned meshes are
still built synchronously.
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.
renderAuxBuffer( buffer: 'albedo' | 'normal' = 'albedo' ): voidDraws the albedo or normal auxiliary buffer accumulated for the denoiser, for checking what it is given. Nothing is drawn until a denoiser that uses them is attached.
dispose(): voidFrees every GPU resource held by the tracer, including any attached denoiser and upscaler.
getRenderTime(): numberMilliseconds elapsed since the last reset.