webgpu_rendertarget_2d-array_3d.html 9.4 KB

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