webgpu_rendertarget_2d-array_3d.html 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>three.js webgpu - RenderTargetArray and RenderTarget3D</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. <link type="text/css" rel="stylesheet" href="main.css">
  8. <style>
  9. .viewport-label {
  10. position: absolute;
  11. color: white;
  12. background-color: rgba(0, 0, 0, 0.7);
  13. padding: 5px 10px;
  14. border-radius: 4px;
  15. font-family: monospace;
  16. pointer-events: none;
  17. z-index: 1000;
  18. user-select: none;
  19. }
  20. </style>
  21. </head>
  22. <body>
  23. <div id="info">
  24. <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - WebGPU - RenderTargetArray and RenderTarget3D<br />
  25. </div>
  26. <div class="viewport-label" style="bottom: 2%; left: 2%;">DataArrayTexture</div>
  27. <div class="viewport-label" style="bottom: 2%; left: 52%;">Data3DTexture</div>
  28. <div class="viewport-label" style="bottom: 52%; left: 52%;">RenderTarget3D</div>
  29. <div class="viewport-label" style="bottom: 52%; left: 2%;">RenderTargetArray</div>
  30. <script type="importmap">
  31. {
  32. "imports": {
  33. "three": "../build/three.webgpu.js",
  34. "three/webgpu": "../build/three.webgpu.js",
  35. "three/tsl": "../build/three.tsl.js",
  36. "three/addons/": "./jsm/"
  37. }
  38. }
  39. </script>
  40. <script type="module">
  41. import * as THREE from 'three';
  42. import { vec2, uniform, screenUV, color, texture, diffuseColor, attribute, vec3, vec4 } from 'three/tsl';
  43. import Stats from 'three/addons/libs/stats.module.js';
  44. import { TextureHelper } from 'three/addons/helpers/TextureHelperGPU.js';
  45. import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
  46. import { unzipSync } from 'three/addons/libs/fflate.module.js';
  47. let renderer, stats;
  48. let views = [];
  49. class View {
  50. constructor( left, top, width, height ) {
  51. this.left = left;
  52. this.top = top;
  53. this.width = width;
  54. this.height = height;
  55. const aspect = ( window.innerWidth * width ) / ( window.innerHeight * height );
  56. // Set up perspective camera
  57. this.camera = new THREE.PerspectiveCamera( 50, aspect, 0.1, 100 );
  58. this.camera.position.set( - 7, 0, 10 );
  59. this.camera.lookAt( 0, 0, 0 );
  60. this.camera.updateProjectionMatrix();
  61. this.scene = new THREE.Scene();
  62. const normalizedUV = screenUV.mul( vec2( 1, - 1 ) ).add( vec2( 0, 1 ) ); // Flip Y and offset
  63. // Calculate viewport center in normalized coordinates
  64. const viewportCenter = vec2(
  65. this.left + this.width * 0.5,
  66. this.top + this.height * 0.5 // Invert Y coordinate for proper alignment
  67. );
  68. const distanceEffect = normalizedUV.distance( viewportCenter ).smoothstep( 0, 0.2 );
  69. const backgroundEffect = color( this.top > 0 ? 0x212121 : 0x616161 ).sub( distanceEffect.pow( 0.3 ).mul( 0.1 ) );
  70. this.scene.backgroundNode = backgroundEffect;
  71. }
  72. // Method to handle viewport resize
  73. updateSize( left, top, width, height ) {
  74. this.left = left;
  75. this.top = top;
  76. this.width = width;
  77. this.height = height;
  78. const aspect = ( window.innerWidth * width ) / ( window.innerHeight * height );
  79. this.camera.aspect = aspect;
  80. this.camera.updateProjectionMatrix();
  81. }
  82. }
  83. async function init() {
  84. const container = document.createElement( 'div' );
  85. document.body.appendChild( container );
  86. renderer = new THREE.WebGPURenderer();
  87. renderer.setPixelRatio( window.devicePixelRatio );
  88. renderer.setSize( window.innerWidth, window.innerHeight );
  89. renderer.autoClear = false;
  90. renderer.setAnimationLoop( animate );
  91. container.appendChild( renderer.domElement );
  92. await renderer.init();
  93. // Create views after renderer initialization
  94. views = [
  95. new View( 0.0, 0.0, 0.5, 0.5 ),
  96. new View( 0.5, 0.0, 0.5, 0.5 ),
  97. new View( 0.0, 0.5, 0.5, 0.5 ),
  98. new View( 0.5, 0.5, 0.5, 0.5 )
  99. ];
  100. // Add OrbitControls after views and renderer are created
  101. views.forEach( view => {
  102. view.controls = new OrbitControls( view.camera, renderer.domElement );
  103. view.controls.minDistance = 1;
  104. view.controls.maxDistance = 20;
  105. view.controls.minAzimuthAngle = - Math.PI / 3;
  106. view.controls.maxAzimuthAngle = Math.PI / 3;
  107. view.controls.minPolarAngle = Math.PI / 4;
  108. view.controls.maxPolarAngle = Math.PI / 1.25;
  109. view.controls.enableDamping = true;
  110. } );
  111. const size = {
  112. width: 256,
  113. height: 256,
  114. depth: 109
  115. };
  116. new THREE.FileLoader()
  117. .setResponseType( 'arraybuffer' )
  118. .load( 'textures/3d/head256x256x109.zip', function ( data ) {
  119. const zip = unzipSync( new Uint8Array( data ) );
  120. const array = new Uint8Array( zip[ 'head256x256x109' ].buffer );
  121. const map3D = new THREE.Data3DTexture( array, size.width, size.height, size.depth );
  122. map3D.name = 'Data3DTexture';
  123. map3D.format = THREE.RedFormat;
  124. map3D.minFilter = THREE.LinearFilter;
  125. map3D.magFilter = THREE.LinearFilter;
  126. map3D.unpackAlignment = 1;
  127. map3D.needsUpdate = true;
  128. const depth = size.depth / 20;
  129. // 3D
  130. const helper3D = new TextureHelper( map3D, 10, 10, depth );
  131. helper3D.material.outputNode = vec4(
  132. vec3( diffuseColor.r.mul( attribute( 'uvw' ).z.mul( diffuseColor.r ) ) ),
  133. diffuseColor.r.mul( diffuseColor.a )
  134. );
  135. views[ 1 ].scene.add( helper3D );
  136. const fbo3D = new THREE.RenderTarget3D( size.width, size.height, size.depth, {
  137. depthBuffer: false,
  138. } );
  139. fbo3D.texture.name = 'RenderTarget3D';
  140. const fbo3DHelper = new TextureHelper( fbo3D.texture, 10, 10, depth );
  141. fbo3DHelper.material.outputNode = vec4(
  142. vec3( diffuseColor.r ),
  143. diffuseColor.r
  144. );
  145. views[ 3 ].scene.add( fbo3DHelper );
  146. // 2D Array
  147. const mapArray = new THREE.DataArrayTexture( array, size.width, size.height, size.depth );
  148. mapArray.name = 'DataArrayTexture';
  149. mapArray.format = THREE.RedFormat;
  150. mapArray.minFilter = THREE.LinearFilter;
  151. mapArray.magFilter = THREE.LinearFilter;
  152. mapArray.unpackAlignment = 1;
  153. mapArray.needsUpdate = true;
  154. const helperArray = new TextureHelper( mapArray, 10, 10, depth );
  155. helperArray.material.outputNode = vec4(
  156. vec3( diffuseColor.r.mul( attribute( 'uvw' ).z.div( size.depth ).mul( diffuseColor.r ) ) ),
  157. diffuseColor.r.mul( diffuseColor.a )
  158. );
  159. views[ 0 ].scene.add( helperArray );
  160. // Setup render targets
  161. const materialQuad = new THREE.NodeMaterial();
  162. const uZCoord = uniform( 0 );
  163. materialQuad.depthTest = false;
  164. materialQuad.outputNode = vec4( texture( mapArray ).depth( uZCoord ).rgb, 1 );
  165. const fboArray = new THREE.RenderTarget( size.width, size.height, {
  166. depthBuffer: false,
  167. depth: size.depth
  168. } );
  169. fboArray.texture.name = 'RenderTargetArray';
  170. const fboArrayHelper = new TextureHelper( fboArray.texture, 10, 10, depth );
  171. fboArrayHelper.material.outputNode = vec4(
  172. vec3( diffuseColor.r ),
  173. diffuseColor.r
  174. );
  175. views[ 2 ].scene.add( fboArrayHelper );
  176. const quadMesh = new THREE.QuadMesh( materialQuad );
  177. // In WebGPU we need to clear all the layers of the 3D render target before rendering to it (WebGPU limitation?)
  178. if ( renderer.backend.isWebGPUBackend ) {
  179. const materialClear = new THREE.NodeMaterial();
  180. materialClear.outputNode = vec4( 0 );
  181. const clearQuadMesh = new THREE.QuadMesh( materialClear );
  182. for ( let i = 0; i < size.depth; i ++ ) {
  183. renderer.setRenderTarget( fbo3D, i );
  184. clearQuadMesh.render( renderer );
  185. }
  186. }
  187. let j = 0;
  188. const loop = () => {
  189. if ( j === size.depth ) {
  190. clearInterval( interval );
  191. return;
  192. }
  193. // Disable viewport and scissor for FBO rendering
  194. renderer.setViewport( 0, 0, size.width, size.height );
  195. renderer.setScissor( 0, 0, size.width, size.height );
  196. renderer.setScissorTest( false );
  197. uZCoord.value = j;
  198. renderer.setRenderTarget( fboArray, j );
  199. renderer.clear();
  200. quadMesh.render( renderer );
  201. renderer.setRenderTarget( fbo3D, j );
  202. renderer.clear();
  203. quadMesh.render( renderer );
  204. renderer.setRenderTarget( null );
  205. j = ( j + 1 ) % size.depth;
  206. };
  207. const interval = setInterval( loop, 50 );
  208. loop();
  209. } );
  210. stats = new Stats();
  211. container.appendChild( stats.dom );
  212. window.addEventListener( 'resize', onWindowResize );
  213. }
  214. function onWindowResize() {
  215. const width = window.innerWidth;
  216. const height = window.innerHeight;
  217. renderer.setSize( width, height );
  218. views.forEach( view => {
  219. view.updateSize(
  220. view.left,
  221. view.top,
  222. view.width,
  223. view.height
  224. );
  225. } );
  226. }
  227. function animate() {
  228. views.forEach( view => {
  229. view.controls.update();
  230. const left = Math.floor( view.left * window.innerWidth );
  231. const bottom = Math.floor( ( 1 - view.top - view.height ) * window.innerHeight );
  232. const width = Math.floor( view.width * window.innerWidth );
  233. const height = Math.floor( view.height * window.innerHeight );
  234. renderer.setViewport( left, bottom, width, height );
  235. renderer.setScissor( left, bottom, width, height );
  236. renderer.setScissorTest( true );
  237. renderer.clear();
  238. renderer.render( view.scene, view.camera );
  239. } );
  240. stats.update();
  241. }
  242. init();
  243. </script>
  244. </body>
  245. </html>
粤ICP备19079148号