| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
- <title>three.js webgpu - HDR Draw</title>
- <link type="text/css" rel="stylesheet" href="example.css">
- <style>
- body {
- background-color: #fff;
- }
- #no-hdr {
- position: absolute;
- font-family: monospace;
- font-size: 11px;
- font-weight: normal;
- text-align: center;
- background: #000;
- color: #fff;
- left: 50%;
- transform: translateX(-50%);
- padding: 1.5em;
- max-width: 600px;
- margin: 5em auto 0;
- }
- </style>
- </head>
- <body>
- <div id="info" class="invert">
- <a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>
- <div class="title-wrapper">
- <a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>HDR</span>
- </div>
- <small>
- The demo produces a color output intended for HDR monitors.
- </small>
- </div>
- <div id="no-hdr" style="display: none">
- <div>
- The browser says your device or monitor doesn't support HDR.<br />
- If you're on a laptop using an external monitor, try the built in
- monitor<br />
- or, try this site on your phone. Most phones support HDR.
- </div>
- </div>
- <script type="importmap">
- {
- "imports": {
- "three": "../build/three.webgpu.js",
- "three/webgpu": "../build/three.webgpu.js",
- "three/tsl": "../build/three.tsl.js",
- "three/addons/": "./jsm/"
- }
- }
- </script>
- <script type="module">
- import * as THREE from 'three/webgpu';
- import { pass, uv, uniform } from 'three/tsl';
- import WebGPU from 'three/addons/capabilities/WebGPU.js';
- import { afterImage } from 'three/addons/tsl/display/AfterImageNode.js';
- import { Inspector } from 'three/addons/inspector/Inspector.js';
- import { ExtendedSRGBColorSpace, ExtendedSRGBColorSpaceImpl } from 'three/addons/math/ColorSpaces.js';
- const params = {
- intensity: uniform( 4.0, 'float' ).setName( 'intensity' ),
- hardness: uniform( 0.4, 'float' ).setName( 'hardness' ),
- radius: uniform( 0.5, 'float' ).setName( 'radius' ),
- afterImageDecay: uniform( 0.985, 'float' ).setName( 'afterImageDecay' ),
- };
- const hdrMediaQuery = window.matchMedia( '(dynamic-range: high)' );
- function updateHDRWarning() {
- const displayIsHDR = hdrMediaQuery.matches;
- document.querySelector( '#no-hdr' ).style.display = displayIsHDR ? 'none' : '';
- }
- hdrMediaQuery.addEventListener( 'change', updateHDRWarning );
- updateHDRWarning();
- if ( WebGPU.isAvailable() === false ) {
- document.body.appendChild( WebGPU.getErrorMessage() );
- throw new Error( 'No WebGPU support' );
- }
- // Enable Extended sRGB output color space for HDR presentation
- THREE.ColorManagement.define( { [ ExtendedSRGBColorSpace ]: ExtendedSRGBColorSpaceImpl } );
- // Renderer (HalfFloat output + Extended sRGB)
- const renderer = new THREE.WebGPURenderer( {
- antialias: true,
- outputType: THREE.HalfFloatType,
- } );
- renderer.outputColorSpace = ExtendedSRGBColorSpace;
- renderer.setPixelRatio( window.devicePixelRatio );
- renderer.setSize( window.innerWidth, window.innerHeight );
- renderer.inspector = new Inspector();
- document.body.appendChild( renderer.domElement );
- const camera = new THREE.OrthographicCamera( 0, window.innerWidth, window.innerHeight, 0, 1, 2 );
- camera.position.z = 1;
- // Brush scene (rendered into drawTarget)
- const brushScene = new THREE.Scene();
- brushScene.background = new THREE.Color( 0xffffff );
- const brushMat = new THREE.MeshBasicNodeMaterial();
- brushMat.transparent = true;
- brushMat.depthTest = false;
- brushMat.depthWrite = false;
- brushMat.blending = THREE.AdditiveBlending; // additive to build HDR energy
- const postProcessing = new THREE.PostProcessing( renderer );
- const brushPass = pass( brushScene, camera, { type: THREE.HalfFloatType } );
- brushPass.renderTarget.texture.colorSpace = ExtendedSRGBColorSpace;
- postProcessing.outputNode = afterImage( brushPass, params.afterImageDecay );
- // HDR brush uniforms
- const uColor = params.intensity;
- const uHard = params.hardness;
- const uRadius = params.radius;
- // Radial falloff in TSL
- const d = uv().sub( 0.5 ).length();
- const t = d.div( uRadius );
- const a = t.clamp().oneMinus().pow( uHard.mul( 8.0 ).add( 1.0 ) );
- brushMat.colorNode = uColor.mul( a );
- brushMat.opacityNode = a; // premultiplied style with additive blending
- const brushMesh = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), brushMat );
- brushMesh.scale.set( 300, 300, 1 ); // ~300px default brush size
- brushScene.add( brushMesh );
- function onPointerMove( e ) {
- const rect = renderer.domElement.getBoundingClientRect();
- const x = e.clientX - rect.left;
- const y = e.clientY - rect.top;
- // camera has origin at bottom-left (0,0)
- brushMesh.position.set( x, window.innerHeight - y, 0 );
- }
- window.addEventListener( 'pointermove', onPointerMove, { passive: false } );
- // Prevent mobile scroll on touch
- renderer.domElement.addEventListener( 'touchstart', ( e ) => e.preventDefault(), { passive: false } );
- renderer.domElement.addEventListener( 'touchmove', ( e ) => e.preventDefault(), { passive: false } );
- renderer.domElement.addEventListener( 'touchend', ( e ) => e.preventDefault(), { passive: false } );
- // GUI setup
- const gui = renderer.inspector.createParameters( 'Settings' );
- const colorFolder = gui.addFolder( 'HDR' );
- colorFolder.add( params.intensity, 'value', 0, 10, 0.1 ).name( 'Intensity' );
- const brushFolder = gui.addFolder( 'Brush Settings' );
- brushFolder.add( params.hardness, 'value', 0, 0.99, 0.01 ).name( 'Hardness' );
- brushFolder.add( params.radius, 'value', 0.1, 2.0, 0.01 ).name( 'Radius' );
- const effectFolder = gui.addFolder( 'Effects' );
- effectFolder.add( params.afterImageDecay, 'value', 0.9, 0.999, 0.001 ).name( 'After Image Decay' );
- // Resize handling
- function onResize() {
- renderer.setSize( window.innerWidth, window.innerHeight );
- camera.right = window.innerWidth;
- camera.top = window.innerHeight;
- camera.updateProjectionMatrix();
- }
- window.addEventListener( 'resize', onResize );
- // Main loop
- renderer.setAnimationLoop( async () => {
- postProcessing.render();
- } );
- </script>
- </body>
- </html>
|