webgpu_postprocessing_ssr_denoise.html 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>three.js webgpu - postprocessing - Screen Space Reflections (SSR) + denoise</title>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  7. <meta property="og:title" content="three.js webgpu - postprocessing - Screen Space Reflections (SSR) + denoise">
  8. <meta property="og:type" content="website">
  9. <meta property="og:url" content="https://threejs.org/examples/webgpu_postprocessing_ssr_denoise.html">
  10. <meta property="og:image" content="https://threejs.org/examples/screenshots/webgpu_postprocessing_ssr_denoise.jpg">
  11. <link type="text/css" rel="stylesheet" href="example.css">
  12. <style>
  13. #compare-hint {
  14. position: fixed;
  15. bottom: 20px;
  16. left: 50%;
  17. transform: translateX(-50%);
  18. z-index: 1001;
  19. color: #e0e0e0;
  20. font: 400 13px 'Inter', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  21. text-shadow: 1px 1px 5px rgba(0, 0, 0, 0.7);
  22. pointer-events: none;
  23. opacity: 0.85;
  24. white-space: nowrap;
  25. }
  26. </style>
  27. </head>
  28. <body>
  29. <div id="compare-hint" hidden>Hold Shift and move the mouse to scrub the comparison</div>
  30. <div id="info">
  31. <a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>
  32. <div class="title-wrapper">
  33. <a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>SSR + Denoising</span>
  34. </div>
  35. <small>
  36. Screen Space Reflections with Spatiotemporal Denoising by <a href="https://x.com/0beqz" target="_blank" rel="noopener">0beqz</a>.<br />
  37. Dungeon - Low Poly Game Level Challenge by
  38. <a href="https://sketchfab.com/warkarma" target="_blank" rel="noopener">Warkarma</a>.<br />
  39. </small>
  40. </div>
  41. <script type="importmap">
  42. {
  43. "imports": {
  44. "three": "../build/three.webgpu.js",
  45. "three/webgpu": "../build/three.webgpu.js",
  46. "three/tsl": "../build/three.tsl.js",
  47. "three/addons/": "./jsm/"
  48. }
  49. }
  50. </script>
  51. <script type="module">
  52. import * as THREE from 'three/webgpu';
  53. import { pass, mrt, output, normalView, materialMetalness, materialRoughness, screenUV, sample, packNormalToRGB, unpackRGBToNormal, vec2, velocity, diffuseColor, vec3, vec4, uniform, mix, step, abs, float, renderOutput, saturation } from 'three/tsl';
  54. import { ssr } from 'three/addons/tsl/display/SSRNode.js';
  55. import { temporalReproject } from 'three/addons/tsl/display/TemporalReprojectNode.js';
  56. import { recurrentDenoise } from 'three/addons/tsl/display/RecurrentDenoiseNode.js';
  57. import { sharpen } from 'three/addons/tsl/display/SharpenNode.js';
  58. import { traa } from 'three/addons/tsl/display/TRAANode.js';
  59. import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
  60. import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
  61. import { Inspector } from 'three/addons/inspector/Inspector.js';
  62. import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
  63. // Disable env map specular (radiance and clearcoat radiance) for every PBR material
  64. // in this scene, since SSR provides the specular reflections instead. Diffuse env
  65. // (irradiance) is left untouched. Equivalent to zeroing EnvironmentNode contributions,
  66. // but scoped to this example rather than modifying the core library.
  67. const _indirectSpecular = THREE.PhysicalLightingModel.prototype.indirectSpecular;
  68. THREE.PhysicalLightingModel.prototype.indirectSpecular = function ( builder ) {
  69. builder.context.radiance = vec3( 0 );
  70. if ( this.clearcoatRadiance ) {
  71. this.clearcoatRadiance.assign( vec3( 0 ) );
  72. }
  73. _indirectSpecular.call( this, builder );
  74. };
  75. const OUTPUT_COMPARE_DENOISE = 8;
  76. const OUTPUT_COMPARE_SSR = 9;
  77. const GRADED_OUTPUTS = new Set( [ 0, 1, 3, 4, OUTPUT_COMPARE_DENOISE, OUTPUT_COMPARE_SSR ] );
  78. const compareHint = document.getElementById( 'compare-hint' );
  79. function isCompareMode( output ) {
  80. return output === OUTPUT_COMPARE_DENOISE || output === OUTPUT_COMPARE_SSR;
  81. }
  82. function updateCompareHint( output ) {
  83. compareHint.hidden = ! isCompareMode( output );
  84. }
  85. const compareSplit = uniform( 0.5 );
  86. const compareResolution = uniform( new THREE.Vector2( window.innerWidth, window.innerHeight ) );
  87. function buildCompareNode( noisy, denoised ) {
  88. const compareColor = mix( noisy, denoised, step( compareSplit, screenUV.x ) );
  89. const lineWidth = float( 1 ).div( compareResolution.x );
  90. const onLine = float( 1 ).sub( step( lineWidth, abs( screenUV.x.sub( compareSplit ) ) ) );
  91. return mix( compareColor, vec4( 1 ), onLine );
  92. }
  93. const params = {
  94. output: 0,
  95. roughness: 0.3,
  96. ssr: {
  97. resolutionScale: 1,
  98. quality: 0.25,
  99. mirrorBias: 0.5,
  100. maxDistance: 0.4,
  101. intensity: 1,
  102. thickness: 0.1,
  103. maxLuminance: 35,
  104. binaryRefine: false,
  105. stepExponent: 3,
  106. envImportanceSampling: false,
  107. screenEdgeFade: 0.2,
  108. screenEdgeFadeBlack: false, // for indoor scenes, set to true
  109. environmentIntensity: 3.14, // not too sure why exactly, but multiplying by ~PI makes the env map reflections match Blender more
  110. },
  111. temporalReproject: {
  112. maxFrames: 16,
  113. clampIntensity: 0.25,
  114. flickerSuppression: 1,
  115. hitPointReprojection: true,
  116. },
  117. denoise: {
  118. enabled: true,
  119. lumaPhi: 0.75,
  120. depthPhi: 20,
  121. normalPhi: 0.3,
  122. roughnessPhi: 100,
  123. radius: 1.5,
  124. alphaPhi: 5,
  125. strength: 0.725,
  126. adapt: 0.5,
  127. smoothDisocclusions: true,
  128. flickerSuppression: 1,
  129. adaptiveTrust: 1
  130. },
  131. post: {
  132. grading: { toneMapping: 'AgX', exposure: 1.57, gamma: 0.89, contrast: 1.31, saturation: 1 },
  133. },
  134. };
  135. let camera, scene, model, renderer, postProcessing, ssrNode, temporalReprojectNode, denoiseNode;
  136. let controls;
  137. let scenePassNode, combinedOutputNode, scenePassDepth, scenePassVelocity;
  138. let gammaUniform, contrastUniform, saturationUniform;
  139. init();
  140. async function init() {
  141. camera = new THREE.PerspectiveCamera( 35, window.innerWidth / window.innerHeight, 0.1, 8 );
  142. camera.position.set( 0.9210513838983053, 0.16195074025403253, 0.431687274895316 );
  143. scene = new THREE.Scene();
  144. const loader = new GLTFLoader();
  145. loader.load( 'models/gltf/dungeon_warkarma.glb', function ( gltf ) {
  146. gltf.scene.scale.multiplyScalar( 0.1 );
  147. gltf.scene.traverse( function ( object ) {
  148. if ( ! object.material ) return;
  149. object.castShadow = true;
  150. object.receiveShadow = true;
  151. object.material.roughness = 0.3;
  152. object.material.normalMap = null;
  153. } );
  154. model = gltf.scene;
  155. scene.add( model );
  156. } );
  157. renderer = new THREE.WebGPURenderer();
  158. renderer.inspector = new Inspector();
  159. renderer.setSize( window.innerWidth, window.innerHeight );
  160. renderer.toneMapping = THREE.AgXToneMapping;
  161. renderer.toneMappingExposure = params.post.grading.exposure;
  162. renderer.shadowMap.enabled = true;
  163. document.body.appendChild( renderer.domElement );
  164. const hdrLoader = new HDRLoader().setPath( 'textures/equirectangular/' );
  165. const hdrTexture = await hdrLoader.loadAsync( 'quarry_01_1k.hdr' );
  166. hdrTexture.mapping = THREE.EquirectangularReflectionMapping;
  167. scene.background = hdrTexture;
  168. scene.environment = hdrTexture;
  169. hdrTexture.generateMipmaps = true;
  170. hdrTexture.needsUpdate = true;
  171. const directionalLight = new THREE.DirectionalLight( '#ffffff', 20 );
  172. directionalLight.position.set( - 10.9, 2.2, 10.75 );
  173. directionalLight.castShadow = true;
  174. directionalLight.shadow.autoUpdate = false;
  175. directionalLight.shadow.needsUpdate = true;
  176. directionalLight.shadow.mapSize.width = 4096;
  177. directionalLight.shadow.mapSize.height = 4096;
  178. directionalLight.shadow.camera.left = - 1.75;
  179. directionalLight.shadow.camera.right = 1.75;
  180. directionalLight.shadow.camera.top = 1.75;
  181. directionalLight.shadow.camera.bottom = - 1.75;
  182. directionalLight.shadow.camera.near = 0.1;
  183. directionalLight.shadow.camera.far = 50;
  184. directionalLight.shadow.bias = - 0.0005;
  185. scene.add( directionalLight );
  186. await renderer.init();
  187. scene.environmentIntensity = 1;
  188. postProcessing = new THREE.RenderPipeline( renderer );
  189. const scenePass = pass( scene, camera );
  190. scenePassNode = scenePass;
  191. scenePass.setMRT( mrt( {
  192. output: output,
  193. // Store base color (albedo) in RGB + metalness in alpha. The albedo is needed for
  194. // metal Fresnel f0; do NOT bake in the (1-metalness) diffuse attenuation here, as
  195. // that zeroes metals and destroys their specular tint (f0 would become 0 → black).
  196. diffuseColor: vec4( diffuseColor.rgb, materialMetalness ),
  197. // Pack roughness into normal alpha channel to save MRT bandwidth
  198. normal: vec4( packNormalToRGB( normalView ).rgb, materialRoughness ),
  199. velocity: velocity
  200. } ) );
  201. const scenePassColor = scenePass.getTextureNode( 'output' ).toInspector( 'Color' );
  202. const scenePassNormal = scenePass.getTextureNode( 'normal' ).toInspector( 'Normal' );
  203. scenePassDepth = scenePass.getTextureNode( 'depth' ).toInspector( 'Depth', () => {
  204. return scenePass.getLinearDepthNode();
  205. } );
  206. scenePassVelocity = scenePass.getTextureNode( 'velocity' ).toInspector( 'Velocity' );
  207. const scenePassDiffuseColor = scenePass.getTextureNode( 'diffuseColor' ).toInspector( 'Diffuse Color' );
  208. const normalTexture = scenePass.getTexture( 'normal' );
  209. normalTexture.type = THREE.UnsignedByteType;
  210. const diffuseTexture = scenePass.getTexture( 'diffuseColor' );
  211. diffuseTexture.type = THREE.UnsignedByteType;
  212. const sceneNormal = sample( ( uv ) => unpackRGBToNormal( scenePassNormal.sample( uv ).rgb ) );
  213. // metalness in diffuseColor.a, roughness in normal.a (no separate metalrough MRT)
  214. const scenePassMetalRough = sample( ( uv ) => vec2(
  215. scenePassDiffuseColor.sample( uv ).a,
  216. scenePassNormal.sample( uv ).a
  217. ) ).toInspector( 'Metalness/Roughness' );
  218. ssrNode = ssr( scenePassColor, scenePassDepth, sceneNormal, {
  219. stochastic: true,
  220. diffuseNode: scenePassDiffuseColor,
  221. metalnessNode: scenePassDiffuseColor.a,
  222. roughnessNode: scenePassNormal.a,
  223. environmentNode: hdrTexture,
  224. envImportanceSampling: params.ssr.envImportanceSampling,
  225. binaryRefine: params.ssr.binaryRefine
  226. } );
  227. ssrNode.setEnvMap( hdrTexture );
  228. ssrNode.toInspector( 'SSR' );
  229. temporalReprojectNode = temporalReproject( ssrNode, scenePassDepth, scenePassNormal, scenePassVelocity, camera, {
  230. mode: 'specular',
  231. accumulate: false
  232. } );
  233. temporalReprojectNode.toInspector( 'Temporal Reproject' );
  234. denoiseNode = recurrentDenoise( temporalReprojectNode, camera, {
  235. depth: scenePassDepth,
  236. normal: scenePassNormal,
  237. raw: ssrNode,
  238. metalRoughness: scenePassMetalRough,
  239. mode: 'specular',
  240. accumulate: true,
  241. } );
  242. denoiseNode.alphaSource = 'raylength'; // SSR alpha channel contains ray length
  243. denoiseNode.toInspector( 'Denoise' );
  244. // feed the denoised result + velocity back into SSR for multi-bounce reflections
  245. ssrNode.setHistory( denoiseNode, scenePassVelocity );
  246. temporalReprojectNode.setHistoryTexture( denoiseNode );
  247. const denoisePassBlend = vec4( denoiseNode.rgb, ssrNode.a.greaterThan( 0 ).toVar() );
  248. gammaUniform = uniform( params.post.grading.gamma );
  249. contrastUniform = uniform( params.post.grading.contrast );
  250. saturationUniform = uniform( params.post.grading.saturation );
  251. const litColor = scenePassColor.rgb.add( denoisePassBlend.rgb );
  252. const outputNode = vec4( litColor, 1 );
  253. outputNode.toInspector( 'Combined SSR' );
  254. combinedOutputNode = outputNode;
  255. postProcessing.outputNode = applyPostProcessing( combinedOutputNode );
  256. postProcessing.outputColorTransform = false;
  257. controls = new OrbitControls( camera, renderer.domElement );
  258. controls.enableDamping = true;
  259. controls.update();
  260. // Initial camera transform
  261. camera.position.set( 1.259878548682251, 0.5391287340899181, - 0.27217301481427114 );
  262. camera.rotation.set( - 0.3158233106804791, 0.26820684188431526, 0.08637696823742165 );
  263. controls.target.set( 1.0258536154689288, 0.2746440590977971, - 1.0815876858987743 );
  264. window.addEventListener( 'resize', onWindowResize );
  265. renderer.domElement.addEventListener( 'pointermove', onComparePointerMove );
  266. applyParams();
  267. applyPost();
  268. const outputTypes = {
  269. Combined: 0,
  270. 'Denoised SSR': 4,
  271. 'Compare (Denoise)': OUTPUT_COMPARE_DENOISE,
  272. 'Compare (Reflections)': OUTPUT_COMPARE_SSR,
  273. 'SSR (Raw)': 1,
  274. 'Ray Length': 7,
  275. 'Accumulation Speed (Alpha)': 6
  276. };
  277. const ssrGui = renderer.inspector.createParameters( 'SSR settings' );
  278. ssrGui.add( params, 'output', outputTypes ).onChange( updateOutputNode );
  279. ssrGui.add( params.ssr, 'quality', 0, 1 ).name( 'quality' ).onChange( applyParams );
  280. ssrGui.add( params.ssr, 'mirrorBias', 0, 1 ).name( 'mirror bias' ).onChange( applyParams );
  281. ssrGui.add( params.ssr, 'stepExponent', 1, 4, 0.5 ).name( 'step exponent' ).onChange( applyParams );
  282. ssrGui.add( params.ssr, 'binaryRefine' ).name( 'binary refine' ).onChange( applyParams );
  283. ssrGui.add( params.ssr, 'maxDistance', 0, 5 ).name( 'max distance' ).onChange( applyParams );
  284. ssrGui.add( params.ssr, 'intensity', 0, 4 ).name( 'intensity' ).onChange( applyParams );
  285. ssrGui.add( params.ssr, 'thickness', 0, 0.25 ).name( 'thickness' ).onChange( applyParams );
  286. ssrGui.add( params.ssr, 'environmentIntensity', 0, 10 ).name( 'env intensity' ).onChange( applyParams );
  287. ssrGui.add( params, 'roughness', 0, 1 ).onChange( ( value ) => {
  288. scene.traverse( ( object ) => {
  289. if ( object.material ) object.material.roughness = value;
  290. } );
  291. } );
  292. const denoiseGui = renderer.inspector.createParameters( 'Denoise settings' );
  293. denoiseGui.add( params.denoise, 'enabled' ).name( 'enabled' ).onChange( applyParams );
  294. denoiseGui.add( params.denoise, 'lumaPhi', 0, 3 ).name( 'luma phi' ).onChange( applyParams );
  295. denoiseGui.add( params.denoise, 'depthPhi', 0, 50 ).name( 'depth phi' ).onChange( applyParams );
  296. denoiseGui.add( params.denoise, 'normalPhi', 0.01, 1, 0.01 ).name( 'normal phi' ).onChange( applyParams );
  297. // We have uniform roughness in the scene, so we don't need to adjust the roughness phi
  298. // denoiseGui.add( params.denoise, 'roughnessPhi', 0, 500 ).name( 'roughness phi' ).onChange( applyParams );
  299. denoiseGui.add( params.denoise, 'alphaPhi', 0, 15 ).name( 'ray length phi' ).onChange( applyParams );
  300. denoiseGui.add( params.denoise, 'radius', 0, 3 ).name( 'radius' ).onChange( applyParams );
  301. denoiseGui.add( params.denoise, 'strength', 0.5, 0.95 ).name( 'strength' ).onChange( applyParams );
  302. denoiseGui.add( params.denoise, 'adapt', 0, 1 ).name( 'adapt' ).onChange( applyParams );
  303. // Extra options that are not used in this example
  304. // denoiseGui.add( params.denoise, 'smoothDisocclusions' ).name( 'smooth disocclusions' ).onChange( applyParams );
  305. // denoiseGui.add( params.denoise, 'flickerSuppression', 0, 1 ).name( 'flicker suppression' ).onChange( applyParams );
  306. // denoiseGui.add( params.denoise, 'adaptiveTrust', 0, 1 ).name( 'adaptive trust' ).onChange( applyParams );
  307. const temporalReprojectGui = renderer.inspector.createParameters( 'Temporal Reproject settings' );
  308. temporalReprojectGui.add( params.temporalReproject, 'maxFrames', 1, 128, 1 ).name( 'max frames' ).onChange( applyParams );
  309. temporalReprojectGui.add( params.temporalReproject, 'clampIntensity', 0, 1 ).name( 'clamp intensity' ).onChange( applyParams );
  310. temporalReprojectGui.add( params.temporalReproject, 'flickerSuppression', 0, 1 ).name( 'flicker suppression' ).onChange( applyParams );
  311. temporalReprojectGui.add( params.temporalReproject, 'hitPointReprojection' ).name( 'hit point reprojection' ).onChange( applyParams );
  312. temporalReprojectGui.close();
  313. // Concise UI for Directional Light controls
  314. const lightGui = renderer.inspector.createParameters( 'Light' ).close();
  315. [ 'x', 'y', 'z' ].forEach( axis => {
  316. lightGui.add( directionalLight.position, axis, - 30, 30 ).name( axis.toUpperCase() )
  317. .onChange( () => directionalLight.shadow.needsUpdate = true );
  318. } );
  319. lightGui.add( directionalLight, 'intensity', 0, 50 ).name( 'Intensity' );
  320. updateOutputNode();
  321. renderer.setAnimationLoop( animate );
  322. }
  323. function applyParams() {
  324. if ( ! ssrNode ) return;
  325. ssrNode.resolutionScale = params.ssr.resolutionScale;
  326. ssrNode.quality.value = params.ssr.quality;
  327. ssrNode.mirrorBias.value = params.ssr.mirrorBias;
  328. // stepExponent / screenEdgeFadeBlack / binaryRefine are build-time constants: assigning
  329. // them recompiles the SSR material (the setters no-op when the value is unchanged).
  330. ssrNode.stepExponent = params.ssr.stepExponent;
  331. ssrNode.binaryRefine = params.ssr.binaryRefine;
  332. ssrNode.maxDistance.value = params.ssr.maxDistance;
  333. ssrNode.intensity.value = params.ssr.intensity;
  334. ssrNode.thickness.value = params.ssr.thickness;
  335. ssrNode.maxLuminance.value = params.ssr.maxLuminance;
  336. ssrNode.screenEdgeFade.value = params.ssr.screenEdgeFade;
  337. ssrNode.screenEdgeFadeBlack = params.ssr.screenEdgeFadeBlack;
  338. ssrNode.environmentIntensity.value = params.ssr.environmentIntensity;
  339. if ( temporalReprojectNode ) {
  340. temporalReprojectNode.maxFrames.value = params.temporalReproject.maxFrames;
  341. temporalReprojectNode.clampIntensity.value = params.temporalReproject.clampIntensity;
  342. temporalReprojectNode.flickerSuppression.value = params.temporalReproject.flickerSuppression;
  343. temporalReprojectNode.hitPointReprojection.value = params.temporalReproject.hitPointReprojection;
  344. }
  345. if ( denoiseNode ) {
  346. denoiseNode.lumaPhi.value = params.denoise.lumaPhi;
  347. denoiseNode.depthPhi.value = params.denoise.depthPhi;
  348. denoiseNode.normalPhi.value = params.denoise.normalPhi;
  349. denoiseNode.roughnessPhi.value = params.denoise.roughnessPhi;
  350. denoiseNode.radius.value = params.denoise.enabled ? params.denoise.radius : 0;
  351. denoiseNode.alphaPhi.value = params.denoise.alphaPhi;
  352. denoiseNode.strength.value = params.denoise.strength;
  353. denoiseNode.adapt.value = params.denoise.adapt;
  354. denoiseNode.smoothDisocclusions.value = params.denoise.smoothDisocclusions;
  355. denoiseNode.flickerSuppression.value = params.denoise.flickerSuppression;
  356. denoiseNode.adaptiveTrust.value = params.denoise.adaptiveTrust;
  357. }
  358. }
  359. function applyGrading( source ) {
  360. let rgb = source.rgb;
  361. rgb = renderOutput( vec4( rgb, 1 ), THREE.AgXToneMapping, THREE.SRGBColorSpace ).rgb;
  362. rgb = rgb.sub( 0.5 ).mul( contrastUniform ).add( 0.5 );
  363. rgb = saturation( rgb, saturationUniform );
  364. rgb = rgb.max( 0.0 ).pow( float( 1 ).div( gammaUniform ) );
  365. return vec4( rgb, 1 );
  366. }
  367. function applyPostProcessing( source ) {
  368. return sharpen( traa( applyGrading( source ), scenePassDepth, scenePassVelocity, camera ), 0 );
  369. }
  370. function applyPost() {
  371. const g = params.post.grading;
  372. gammaUniform.value = g.gamma;
  373. contrastUniform.value = g.contrast;
  374. saturationUniform.value = g.saturation;
  375. renderer.toneMapping = THREE.AgXToneMapping;
  376. renderer.toneMappingExposure = g.exposure;
  377. }
  378. function updateOutputNode() {
  379. if ( ! postProcessing ) return;
  380. let node;
  381. switch ( params.output ) {
  382. case 0: node = applyPostProcessing( combinedOutputNode ); break; // Combined
  383. case 1: node = applyPostProcessing( vec4( ssrNode.rgb, 1 ) ); break; // SSR (Raw)
  384. case 4: node = applyPostProcessing( vec4( denoiseNode.rgb, 1 ) ); break; // Denoised SSR
  385. case 6: node = vec4( denoiseNode.aaa, 1 ); break; // Samples (Alpha)
  386. case 7: node = vec4( ssrNode.aaa, 1 ); break; // Ray Length (Raw)
  387. case OUTPUT_COMPARE_DENOISE: { // Compare (denoised | noisy)
  388. const noisySSR = applyPostProcessing( vec4( ssrNode.rgb, 1 ) );
  389. const denoisedSSR = applyPostProcessing( vec4( denoiseNode.rgb, 1 ) );
  390. node = buildCompareNode( denoisedSSR, noisySSR );
  391. break;
  392. }
  393. case OUTPUT_COMPARE_SSR: { // Compare (combined SSR vs. scene only)
  394. node = buildCompareNode( applyPostProcessing( combinedOutputNode ), applyPostProcessing( scenePassNode ) );
  395. break;
  396. }
  397. }
  398. if ( ! isCompareMode( params.output ) ) controls.enabled = true;
  399. updateCompareHint( params.output );
  400. postProcessing.outputColorTransform = ! GRADED_OUTPUTS.has( params.output );
  401. postProcessing.outputNode = node;
  402. postProcessing.needsUpdate = true;
  403. }
  404. function onWindowResize() {
  405. camera.aspect = window.innerWidth / window.innerHeight;
  406. camera.updateProjectionMatrix();
  407. renderer.setSize( window.innerWidth, window.innerHeight );
  408. compareResolution.value.set( window.innerWidth, window.innerHeight );
  409. }
  410. function onComparePointerMove( event ) {
  411. if ( ! isCompareMode( params.output ) ) return;
  412. if ( event.shiftKey ) {
  413. controls.enabled = false;
  414. const rect = renderer.domElement.getBoundingClientRect();
  415. compareSplit.value = Math.max( 0, Math.min( 1, ( event.clientX - rect.left ) / rect.width ) );
  416. } else {
  417. controls.enabled = true;
  418. }
  419. }
  420. function animate() {
  421. controls.update();
  422. postProcessing.render();
  423. }
  424. </script>
  425. </body>
  426. </html>
粤ICP备19079148号