-
-
Notifications
You must be signed in to change notification settings - Fork 125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: Temporal Resolving #241
base: main
Are you sure you want to change the base?
Changes from all commits
b057534
306771b
2ce7688
77dfe49
80b5e85
3e57f8e
63d58c0
b375544
274e6e4
57b6b40
4fea685
f85758c
f83b49b
3006989
273a115
5bcb882
97eadd9
379685f
02753c3
21e659c
88aa135
c5a0d43
0abc321
3279fa8
ee5b621
60f8e55
e0a9a0a
fc044fd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -391,6 +391,64 @@ _extends THREE.Camera_ | |
|
||
A class indicating that the path tracer should render an equirectangular view. Does not work with three.js raster rendering. | ||
|
||
## TemporalResolve | ||
|
||
A class that implements temporal filtering to preserve samples when the camera is moving. This helps reduce noise on camera movement by reprojecting the last frame's samples into the current one. | ||
|
||
### .temporalResolveMix | ||
|
||
```javascript | ||
temporalResolveMix = 0.9 : Number | ||
``` | ||
|
||
How much the last frame should be blended into the current one. Higher values will result in a less noisy look at the cost of more smearing. | ||
|
||
### .clampRadius | ||
|
||
```javascript | ||
clampRadius = 1 : Number | ||
``` | ||
|
||
An integer to set the radius of pixels to be used for neighborhood clamping. Higher values will result in a less noisy look at the cost of more blurring. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does "neighborhood clamping" mean here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You clamp the color of the reprojected pixel by the min and max color of the neighboring pixels from the raytracer's output at the current pixel. This is the most popular method to reduce ghosting for TRAA but since we have a noisy output in the first frames when raytracing, neighborhood clamping will lead to flickering there. So by increasing the number of pixel we use for neighborhood clamping, we reduce flickering but will have more smear. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the explanation! Maybe we can include more information on the docs about the role color plays in this process There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, yeah I'll add a more in-depth explanation for it |
||
|
||
### .newSamplesSmoothing | ||
|
||
```javascript | ||
newSamplesSmoothing = 0.675 : Number | ||
``` | ||
|
||
To reduce noise for pixels that appeared recently, the average of multiple adjacent pixels can be used instead of a pixel itself. This factor determines the influence of the averaged pixel. Higher values will result in less noise but also less sharpness. | ||
|
||
### .newSamplesCorrection | ||
|
||
```javascript | ||
newSamplesCorrection = 1 : Number | ||
``` | ||
|
||
Higher values will make pixels that appeared recently have a greater influence on the output. This will result in more noise but less smearing. | ||
|
||
### .weightTransform | ||
|
||
```javascript | ||
weightTransform = 0 : Number | ||
``` | ||
|
||
This will potentiate the input color by `1 / (1 - weightTransform)` resulting in less contrast and noise when moving the camera. Higher values will highlight darker areas more. | ||
|
||
### .constructor | ||
|
||
```javascript | ||
constructor( ptRenderer : PathTracingRenderer, scene : Object3D, camera : Camera ) | ||
``` | ||
|
||
### .update | ||
|
||
```javascript | ||
update() : void | ||
``` | ||
|
||
Updates the temporal resolve pass for the current frame. | ||
|
||
## PhysicalSpotLight | ||
|
||
_extends THREE.SpotLight_ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ import { PathTracingSceneWorker } from '../src/workers/PathTracingSceneWorker.js | |
import { PhysicalPathTracingMaterial, PathTracingRenderer, MaterialReducer, BlurredEnvMapGenerator } from '../src/index.js'; | ||
import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js'; | ||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; | ||
import { TemporalResolve } from '../src/temporal-resolve/TemporalResolve.js'; | ||
|
||
const envMaps = { | ||
'Royal Esplanade': 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/equirectangular/royal_esplanade_1k.hdr', | ||
|
@@ -71,6 +72,13 @@ const params = { | |
tilesY: 2, | ||
samplesPerFrame: 1, | ||
|
||
temporalResolve: true, | ||
temporalResolveMix: 0.9, | ||
clampRadius: 1, | ||
newSamplesSmoothing: 0.675, | ||
newSamplesCorrection: 1, | ||
weightTransform: 0, | ||
|
||
model: initialModel, | ||
|
||
envMap: envMaps[ 'Royal Esplanade' ], | ||
|
@@ -104,7 +112,7 @@ const params = { | |
let creditEl, loadingEl, samplesEl; | ||
let floorPlane, gui, stats, sceneInfo; | ||
let renderer, orthoCamera, perspectiveCamera, activeCamera; | ||
let ptRenderer, fsQuad, controls, scene; | ||
let ptRenderer, fsQuad, controls, scene, temporalResolve; | ||
let envMap, envMapGenerator; | ||
let loadingModel = false; | ||
let delaySamples = 0; | ||
|
@@ -143,6 +151,13 @@ async function init() { | |
ptRenderer.material.bgGradientTop.set( params.bgGradientTop ); | ||
ptRenderer.material.bgGradientBottom.set( params.bgGradientBottom ); | ||
|
||
temporalResolve = new TemporalResolve( ptRenderer, scene, activeCamera ); | ||
temporalResolve.temporalResolveMix = 0.9; | ||
temporalResolve.clampRadius = 1; | ||
temporalResolve.newSamplesSmoothing = 0.5; | ||
temporalResolve.newSamplesCorrection = 0.75; | ||
temporalResolve.weightTransform = 0; | ||
Comment on lines
+155
to
+159
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It may be best to initialize these parameters from the |
||
|
||
fsQuad = new FullScreenQuad( new MeshBasicMaterial( { | ||
map: ptRenderer.target.texture, | ||
blending: CustomBlending | ||
|
@@ -230,6 +245,17 @@ function animate() { | |
} | ||
|
||
renderer.autoClear = false; | ||
if ( params.temporalResolve ) { | ||
|
||
temporalResolve.update(); | ||
fsQuad.material.map = temporalResolve.target.texture; | ||
|
||
} else { | ||
|
||
fsQuad.material.map = ptRenderer.target.texture; | ||
|
||
} | ||
|
||
fsQuad.render( renderer ); | ||
renderer.autoClear = true; | ||
|
||
|
@@ -245,7 +271,7 @@ function animate() { | |
|
||
function resetRenderer() { | ||
|
||
if ( params.tilesX * params.tilesY !== 1.0 ) { | ||
if ( ! params.temporalResolve && params.tilesX * params.tilesY !== 1.0 ) { | ||
|
||
delaySamples = 1; | ||
|
||
|
@@ -311,6 +337,24 @@ function buildGui() { | |
|
||
} ); | ||
|
||
const trFolder = gui.addFolder( 'Temporal Resolve' ); | ||
trFolder.add( params, 'temporalResolve' ); | ||
trFolder | ||
.add( params, 'temporalResolveMix', 0, 1, 0.025 ) | ||
.onChange( ( value ) => ( temporalResolve.temporalResolveMix = value ) ); | ||
trFolder | ||
.add( params, 'clampRadius', 1, 8, 1 ) | ||
.onChange( ( value ) => ( temporalResolve.clampRadius = value ) ); | ||
trFolder | ||
.add( params, 'newSamplesSmoothing', 0, 1, 0.025 ) | ||
.onChange( ( value ) => ( temporalResolve.newSamplesSmoothing = value ) ); | ||
trFolder | ||
.add( params, 'newSamplesCorrection', 0, 1, 0.025 ) | ||
.onChange( ( value ) => ( temporalResolve.newSamplesCorrection = value ) ); | ||
trFolder | ||
.add( params, 'weightTransform', 0, 0.5, 0.025 ) | ||
.onChange( ( value ) => ( temporalResolve.weightTransform = value ) ); | ||
|
||
const resolutionFolder = gui.addFolder( 'resolution' ); | ||
resolutionFolder.add( params, 'resolutionScale', 0.1, 1.0, 0.01 ).onChange( () => { | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,8 +7,9 @@ import { PathTracingSceneWorker } from '../src/workers/PathTracingSceneWorker.js | |
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'; | ||
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js'; | ||
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'; | ||
import { TemporalResolve } from '../src/temporal-resolve/TemporalResolve.js'; | ||
|
||
let renderer, controls, sceneInfo, ptRenderer, activeCamera, blitQuad, denoiseQuad, materials; | ||
let renderer, controls, sceneInfo, ptRenderer, activeCamera, blitQuad, denoiseQuad, materials, temporalResolve; | ||
let perspectiveCamera, orthoCamera, equirectCamera; | ||
let envMap, envMapGenerator, scene; | ||
let samplesEl; | ||
|
@@ -75,7 +76,6 @@ const params = { | |
matte: false, | ||
castShadow: true, | ||
}, | ||
|
||
multipleImportanceSampling: true, | ||
stableNoise: false, | ||
denoiseEnabled: true, | ||
|
@@ -90,6 +90,12 @@ const params = { | |
samplesPerFrame: 1, | ||
acesToneMapping: true, | ||
resolutionScale: 1 / window.devicePixelRatio, | ||
temporalResolve: true, | ||
temporalResolveMix: 0.9, | ||
clampRadius: 2, | ||
newSamplesSmoothing: 0.675, | ||
newSamplesCorrection: 1, | ||
weightTransform: 0, | ||
transparentTraversals: 20, | ||
filterGlossyFactor: 0.5, | ||
tiles: 1, | ||
|
@@ -132,7 +138,7 @@ async function init() { | |
|
||
const aspect = window.innerWidth / window.innerHeight; | ||
perspectiveCamera = new PhysicalCamera( 75, aspect, 0.025, 500 ); | ||
perspectiveCamera.position.set( - 4, 2, 3 ); | ||
perspectiveCamera.position.set( - 4, 2, 7 ); | ||
Comment on lines
-135
to
+141
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we revert this camera change unless there's a reason to move it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I'll change it back |
||
|
||
const orthoHeight = orthoWidth / aspect; | ||
orthoCamera = new THREE.OrthographicCamera( orthoWidth / - 2, orthoWidth / 2, orthoHeight / 2, orthoHeight / - 2, 0, 100 ); | ||
|
@@ -167,6 +173,13 @@ async function init() { | |
|
||
scene = new THREE.Scene(); | ||
|
||
temporalResolve = new TemporalResolve( ptRenderer, scene, activeCamera ); | ||
temporalResolve.temporalResolveMix = 0.9; | ||
temporalResolve.clampRadius = 2; | ||
temporalResolve.newSamplesSmoothing = 0.675; | ||
temporalResolve.newSamplesCorrection = 1; | ||
temporalResolve.weightTransform = 0; | ||
Comment on lines
+177
to
+181
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above - lets initialize from the params object fields. |
||
|
||
samplesEl = document.getElementById( 'samples' ); | ||
|
||
envMapGenerator = new BlurredEnvMapGenerator( renderer ); | ||
|
@@ -324,6 +337,23 @@ async function init() { | |
|
||
} ); | ||
|
||
const trFolder = gui.addFolder( 'Temporal Resolve' ); | ||
trFolder.add( params, 'temporalResolve' ); | ||
trFolder | ||
.add( params, 'temporalResolveMix', 0, 1, 0.025 ) | ||
.onChange( ( value ) => ( temporalResolve.temporalResolveMix = value ) ); | ||
trFolder | ||
.add( params, 'clampRadius', 1, 8, 1 ) | ||
.onChange( ( value ) => ( temporalResolve.clampRadius = value ) ); | ||
trFolder | ||
.add( params, 'newSamplesSmoothing', 0, 1, 0.025 ) | ||
.onChange( ( value ) => ( temporalResolve.newSamplesSmoothing = value ) ); | ||
trFolder | ||
.add( params, 'newSamplesCorrection', 0, 1, 0.025 ) | ||
.onChange( ( value ) => ( temporalResolve.newSamplesCorrection = value ) ); | ||
trFolder | ||
.add( params, 'weightTransform', 0, 0.5, 0.025 ) | ||
.onChange( ( value ) => ( temporalResolve.weightTransform = value ) ); | ||
const denoiseFolder = gui.addFolder( 'Denoising' ); | ||
denoiseFolder.add( params, 'denoiseEnabled' ); | ||
denoiseFolder.add( params, 'denoiseSigma', 0.01, 12.0 ); | ||
|
@@ -647,6 +677,14 @@ function animate() { | |
|
||
renderer.autoClear = false; | ||
quad.material.map = ptRenderer.target.texture; | ||
|
||
if ( params.temporalResolve ) { | ||
|
||
temporalResolve.update(); | ||
quad.material.map = temporalResolve.target.texture; | ||
|
||
} | ||
|
||
quad.render( renderer ); | ||
renderer.autoClear = true; | ||
|
||
|
@@ -656,4 +694,3 @@ function animate() { | |
|
||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can we use
js
instead ofjavascript
for the syntax keyword