|
| 1 | +<html lang="en"> |
| 2 | + <head> |
| 3 | + <title>three.js - WebGPU - Compute Ping/Pong Texture</title> |
| 4 | + <meta charset="utf-8"> |
| 5 | + <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> |
| 6 | + <link type="text/css" rel="stylesheet" href="main.css"> |
| 7 | + </head> |
| 8 | + <body> |
| 9 | + |
| 10 | + <div id="info"> |
| 11 | + <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> WebGPU - Compute Ping/Pong Texture |
| 12 | + <br>Texture generated using GPU Compute. |
| 13 | + </div> |
| 14 | + |
| 15 | + <script async src=" https://unpkg.com/[email protected]/dist/es-module-shims.js" ></script> |
| 16 | + |
| 17 | + <script type="importmap"> |
| 18 | + { |
| 19 | + "imports": { |
| 20 | + "three": "../build/three.module.js", |
| 21 | + "three/addons/": "./jsm/", |
| 22 | + "three/nodes": "./jsm/nodes/Nodes.js" |
| 23 | + } |
| 24 | + } |
| 25 | + </script> |
| 26 | + |
| 27 | + <script type="module"> |
| 28 | + |
| 29 | + import * as THREE from 'three'; |
| 30 | + import { texture, textureStore, wgslFn, code, instanceIndex } from 'three/nodes'; |
| 31 | + |
| 32 | + import WebGPU from 'three/addons/capabilities/WebGPU.js'; |
| 33 | + import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js'; |
| 34 | + import StorageTexture from 'three/addons/renderers/common/StorageTexture.js'; |
| 35 | + |
| 36 | + let camera, scene, renderer; |
| 37 | + let computeToPing, computeToPong; |
| 38 | + let pingTexture, pongTexture; |
| 39 | + let material; |
| 40 | + let phase = true; |
| 41 | + |
| 42 | + init(); |
| 43 | + render(); |
| 44 | + |
| 45 | + function init() { |
| 46 | + |
| 47 | + if ( WebGPU.isAvailable() === false ) { |
| 48 | + |
| 49 | + document.body.appendChild( WebGPU.getErrorMessage() ); |
| 50 | + |
| 51 | + throw new Error( 'No WebGPU support' ); |
| 52 | + |
| 53 | + } |
| 54 | + |
| 55 | + const aspect = window.innerWidth / window.innerHeight; |
| 56 | + camera = new THREE.OrthographicCamera( - aspect, aspect, 1, - 1, 0, 2 ); |
| 57 | + camera.position.z = 1; |
| 58 | + |
| 59 | + scene = new THREE.Scene(); |
| 60 | + |
| 61 | + // texture |
| 62 | + |
| 63 | + const width = 512, height = 512; |
| 64 | + |
| 65 | + pingTexture = new StorageTexture( width, height ); |
| 66 | + pongTexture = new StorageTexture( width, height ); |
| 67 | + |
| 68 | + // compute init |
| 69 | + |
| 70 | + const rand2 = code( ` |
| 71 | + fn rand2( n: vec2f ) -> f32 { |
| 72 | +
|
| 73 | + return fract( sin( dot( n, vec2f( 12.9898, 4.1414 ) ) ) * 43758.5453 ); |
| 74 | +
|
| 75 | + } |
| 76 | + ` ); |
| 77 | + |
| 78 | + const computeInitWGSL = wgslFn( ` |
| 79 | + fn computeInitWGSL( writeTex: texture_storage_2d<rgba8unorm, write>, index: u32 ) -> void { |
| 80 | +
|
| 81 | + let posX = index % ${ width }; |
| 82 | + let posY = index / ${ width }; |
| 83 | + let indexUV = vec2u( posX, posY ); |
| 84 | + let uv = getUV( posX, posY ); |
| 85 | +
|
| 86 | + textureStore( writeTex, indexUV, vec4f( vec3f( rand2( uv ) ), 1 ) ); |
| 87 | +
|
| 88 | + } |
| 89 | +
|
| 90 | + fn getUV( posX: u32, posY: u32 ) -> vec2f { |
| 91 | +
|
| 92 | + let uv = vec2f( f32( posX ) / ${ width }.0, f32( posY ) / ${ height }.0 ); |
| 93 | +
|
| 94 | + return uv; |
| 95 | +
|
| 96 | + } |
| 97 | + `, [ rand2 ] ); |
| 98 | + |
| 99 | + const computeInitNode = computeInitWGSL( { writeTex: textureStore( pingTexture ), index: instanceIndex } ).compute( width * height ); |
| 100 | + |
| 101 | + // compute loop |
| 102 | + |
| 103 | + const computePingPongWGSL = wgslFn( ` |
| 104 | + fn computePingPongWGSL( readTex: texture_2d<f32>, writeTex: texture_storage_2d<rgba8unorm, write>, index: u32 ) -> void { |
| 105 | +
|
| 106 | + let posX = index % ${ width }; |
| 107 | + let posY = index / ${ width }; |
| 108 | + let indexUV = vec2u( posX, posY ); |
| 109 | +
|
| 110 | + let color = vec3f( rand2( textureLoad( readTex, indexUV, 0 ).xy ) ); |
| 111 | +
|
| 112 | + textureStore( writeTex, indexUV, vec4f( color, 1 ) ); |
| 113 | +
|
| 114 | + } |
| 115 | + `, [ rand2 ] ); |
| 116 | + |
| 117 | + computeToPong = computePingPongWGSL( { readTex: texture( pingTexture ), writeTex: textureStore( pongTexture ), index: instanceIndex } ).compute( width * height ); |
| 118 | + computeToPing = computePingPongWGSL( { readTex: texture( pongTexture ), writeTex: textureStore( pingTexture ), index: instanceIndex } ).compute( width * height ); |
| 119 | + |
| 120 | + // |
| 121 | + |
| 122 | + material = new THREE.MeshBasicMaterial( { color: 0xffffff, map: pongTexture } ); |
| 123 | + |
| 124 | + const plane = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), material ); |
| 125 | + scene.add( plane ); |
| 126 | + |
| 127 | + renderer = new WebGPURenderer( { antialias: true } ); |
| 128 | + renderer.setPixelRatio( window.devicePixelRatio ); |
| 129 | + renderer.setSize( window.innerWidth, window.innerHeight ); |
| 130 | + renderer.setAnimationLoop( render ); |
| 131 | + document.body.appendChild( renderer.domElement ); |
| 132 | + |
| 133 | + window.addEventListener( 'resize', onWindowResize ); |
| 134 | + |
| 135 | + // compute init |
| 136 | + |
| 137 | + renderer.compute( computeInitNode ); |
| 138 | + |
| 139 | + } |
| 140 | + |
| 141 | + function onWindowResize() { |
| 142 | + |
| 143 | + renderer.setSize( window.innerWidth, window.innerHeight ); |
| 144 | + |
| 145 | + const aspect = window.innerWidth / window.innerHeight; |
| 146 | + |
| 147 | + const frustumHeight = camera.top - camera.bottom; |
| 148 | + |
| 149 | + camera.left = - frustumHeight * aspect / 2; |
| 150 | + camera.right = frustumHeight * aspect / 2; |
| 151 | + |
| 152 | + camera.updateProjectionMatrix(); |
| 153 | + |
| 154 | + render(); |
| 155 | + |
| 156 | + } |
| 157 | + |
| 158 | + function render() { |
| 159 | + |
| 160 | + // compute step |
| 161 | + |
| 162 | + renderer.compute( phase ? computeToPong : computeToPing ); |
| 163 | + |
| 164 | + material.map = phase ? pongTexture : pingTexture; |
| 165 | + |
| 166 | + phase = ! phase; |
| 167 | + |
| 168 | + // render step |
| 169 | + |
| 170 | + // update material texture node |
| 171 | + |
| 172 | + renderer.render( scene, camera ); |
| 173 | + |
| 174 | + } |
| 175 | + |
| 176 | + </script> |
| 177 | + </body> |
| 178 | +</html> |
0 commit comments