webgpu_hdr.html 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
  6. <meta property="og:title" content="three.js webgpu - HDR Draw">
  7. <meta property="og:type" content="website">
  8. <meta property="og:url" content="https://threejs.org/examples/webgpu_hdr.html">
  9. <meta property="og:image" content="https://threejs.org/examples/screenshots/webgpu_hdr.jpg">
  10. <title>three.js webgpu - HDR Draw</title>
  11. <link type="text/css" rel="stylesheet" href="example.css">
  12. <style>
  13. body {
  14. background-color: #fff;
  15. }
  16. #no-hdr {
  17. position: absolute;
  18. font-family: monospace;
  19. font-size: 11px;
  20. font-weight: normal;
  21. text-align: center;
  22. background: #000;
  23. color: #fff;
  24. left: 50%;
  25. transform: translateX(-50%);
  26. padding: 1.5em;
  27. max-width: 600px;
  28. margin: 5em auto 0;
  29. }
  30. </style>
  31. </head>
  32. <body>
  33. <div id="info" class="invert">
  34. <a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>
  35. <div class="title-wrapper">
  36. <a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>HDR</span>
  37. </div>
  38. <small>
  39. The demo produces a color output intended for HDR monitors.
  40. </small>
  41. </div>
  42. <div id="no-hdr" style="display: none">
  43. <div>
  44. The browser says your device or monitor doesn't support HDR.<br />
  45. If you're on a laptop using an external monitor, try the built in
  46. monitor<br />
  47. or, try this site on your phone. Most phones support HDR.
  48. </div>
  49. </div>
  50. <script type="importmap">
  51. {
  52. "imports": {
  53. "three": "../build/three.webgpu.js",
  54. "three/webgpu": "../build/three.webgpu.js",
  55. "three/tsl": "../build/three.tsl.js",
  56. "three/addons/": "./jsm/"
  57. }
  58. }
  59. </script>
  60. <script type="module">
  61. import * as THREE from 'three/webgpu';
  62. import { pass, uv, uniform } from 'three/tsl';
  63. import WebGPU from 'three/addons/capabilities/WebGPU.js';
  64. import { afterImage } from 'three/addons/tsl/display/AfterImageNode.js';
  65. import { Inspector } from 'three/addons/inspector/Inspector.js';
  66. import { ExtendedSRGBColorSpace, ExtendedSRGBColorSpaceImpl } from 'three/addons/math/ColorSpaces.js';
  67. const params = {
  68. intensity: uniform( 4.0, 'float' ).setName( 'intensity' ),
  69. hardness: uniform( 0.4, 'float' ).setName( 'hardness' ),
  70. radius: uniform( 0.5, 'float' ).setName( 'radius' ),
  71. afterImageDecay: uniform( 0.985, 'float' ).setName( 'afterImageDecay' ),
  72. };
  73. const hdrMediaQuery = window.matchMedia( '(dynamic-range: high)' );
  74. function updateHDRWarning() {
  75. const displayIsHDR = hdrMediaQuery.matches;
  76. document.querySelector( '#no-hdr' ).style.display = displayIsHDR ? 'none' : '';
  77. }
  78. hdrMediaQuery.addEventListener( 'change', updateHDRWarning );
  79. updateHDRWarning();
  80. if ( WebGPU.isAvailable() === false ) {
  81. document.body.appendChild( WebGPU.getErrorMessage() );
  82. throw new Error( 'No WebGPU support' );
  83. }
  84. // Enable Extended sRGB output color space for HDR presentation
  85. THREE.ColorManagement.define( { [ ExtendedSRGBColorSpace ]: ExtendedSRGBColorSpaceImpl } );
  86. // Renderer (HalfFloat output + Extended sRGB)
  87. const renderer = new THREE.WebGPURenderer( {
  88. antialias: true,
  89. outputType: THREE.HalfFloatType,
  90. } );
  91. renderer.outputColorSpace = ExtendedSRGBColorSpace;
  92. renderer.setPixelRatio( window.devicePixelRatio );
  93. renderer.setSize( window.innerWidth, window.innerHeight );
  94. renderer.inspector = new Inspector();
  95. document.body.appendChild( renderer.domElement );
  96. const camera = new THREE.OrthographicCamera( 0, window.innerWidth, window.innerHeight, 0, 1, 2 );
  97. camera.position.z = 1;
  98. // Brush scene (rendered into drawTarget)
  99. const brushScene = new THREE.Scene();
  100. brushScene.background = new THREE.Color( 0xffffff );
  101. const brushMat = new THREE.MeshBasicNodeMaterial();
  102. brushMat.transparent = true;
  103. brushMat.depthTest = false;
  104. brushMat.depthWrite = false;
  105. brushMat.blending = THREE.AdditiveBlending; // additive to build HDR energy
  106. const renderPipeline = new THREE.RenderPipeline( renderer );
  107. const brushPass = pass( brushScene, camera, { type: THREE.HalfFloatType } );
  108. brushPass.renderTarget.texture.colorSpace = ExtendedSRGBColorSpace;
  109. renderPipeline.outputNode = afterImage( brushPass, params.afterImageDecay );
  110. // HDR brush uniforms
  111. const uColor = params.intensity;
  112. const uHard = params.hardness;
  113. const uRadius = params.radius;
  114. // Radial falloff in TSL
  115. const d = uv().sub( 0.5 ).length();
  116. const t = d.div( uRadius );
  117. const a = t.clamp().oneMinus().pow( uHard.mul( 8.0 ).add( 1.0 ) );
  118. brushMat.colorNode = uColor.mul( a );
  119. brushMat.opacityNode = a; // premultiplied style with additive blending
  120. const brushMesh = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), brushMat );
  121. brushMesh.scale.set( 300, 300, 1 ); // ~300px default brush size
  122. brushScene.add( brushMesh );
  123. function onPointerMove( e ) {
  124. const rect = renderer.domElement.getBoundingClientRect();
  125. const x = e.clientX - rect.left;
  126. const y = e.clientY - rect.top;
  127. // camera has origin at bottom-left (0,0)
  128. brushMesh.position.set( x, window.innerHeight - y, 0 );
  129. }
  130. window.addEventListener( 'pointermove', onPointerMove, { passive: false } );
  131. // Prevent mobile scroll on touch
  132. renderer.domElement.addEventListener( 'touchstart', ( e ) => e.preventDefault(), { passive: false } );
  133. renderer.domElement.addEventListener( 'touchmove', ( e ) => e.preventDefault(), { passive: false } );
  134. renderer.domElement.addEventListener( 'touchend', ( e ) => e.preventDefault(), { passive: false } );
  135. // GUI setup
  136. const gui = renderer.inspector.createParameters( 'Settings' );
  137. const colorFolder = gui.addFolder( 'HDR' );
  138. colorFolder.add( params.intensity, 'value', 0, 10, 0.1 ).name( 'Intensity' );
  139. const brushFolder = gui.addFolder( 'Brush Settings' );
  140. brushFolder.add( params.hardness, 'value', 0, 0.99, 0.01 ).name( 'Hardness' );
  141. brushFolder.add( params.radius, 'value', 0.1, 2.0, 0.01 ).name( 'Radius' );
  142. const effectFolder = gui.addFolder( 'Effects' );
  143. effectFolder.add( params.afterImageDecay, 'value', 0.9, 0.999, 0.001 ).name( 'After Image Decay' );
  144. // Resize handling
  145. function onResize() {
  146. renderer.setSize( window.innerWidth, window.innerHeight );
  147. camera.right = window.innerWidth;
  148. camera.top = window.innerHeight;
  149. camera.updateProjectionMatrix();
  150. }
  151. window.addEventListener( 'resize', onResize );
  152. // Main loop
  153. renderer.setAnimationLoop( async () => {
  154. renderPipeline.render();
  155. } );
  156. </script>
  157. </body>
  158. </html>
粤ICP备19079148号