webgpu_compute_rasterizer.html 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>three.js webgpu - compute rasterizer</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. <meta property="og:title" content="three.js webgpu - compute rasterizer">
  8. <meta property="og:type" content="website">
  9. <meta property="og:url" content="https://threejs.org/examples/webgpu_compute_rasterizer.html">
  10. <meta property="og:image" content="https://threejs.org/examples/screenshots/webgpu_compute_rasterizer.jpg">
  11. <link type="text/css" rel="stylesheet" href="example.css">
  12. </head>
  13. <body>
  14. <div id="info">
  15. <a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>
  16. <div class="title-wrapper">
  17. <a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>GPU-Driven Compute Rasterizer</span>
  18. </div>
  19. <small>Rendering <span id="triangleCount"></span> triangles.</small>
  20. </div>
  21. <script type="importmap">
  22. {
  23. "imports": {
  24. "three": "../build/three.webgpu.js",
  25. "three/webgpu": "../build/three.webgpu.js",
  26. "three/tsl": "../build/three.tsl.js",
  27. "three/addons/": "./jsm/"
  28. }
  29. }
  30. </script>
  31. <script type="module">
  32. import * as THREE from 'three/webgpu';
  33. import { Fn, If, Loop, vec4, vec2, uvec4, mat4, uint, float, int, min, max, atomicMax, atomicAdd, atomicStore, atomicLoad, floor, cos, sin, dot, bool, storage, uniform, uniformArray, uv, instanceIndex, vertexIndex, distance, screenSize, time, texture, varyingProperty, sqrt } from 'three/tsl';
  34. import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
  35. import { TeapotGeometry } from 'three/addons/geometries/TeapotGeometry.js';
  36. import { Inspector } from 'three/addons/inspector/Inspector.js';
  37. import WebGPU from 'three/addons/capabilities/WebGPU.js';
  38. if ( WebGPU.isAvailable() === false ) {
  39. document.body.appendChild( WebGPU.getErrorMessage() );
  40. throw new Error( 'No WebGPU support' );
  41. }
  42. let camera, renderer, controls, timer;
  43. let computeRasterize, computeClear, computeFrustum, computeDispatch, computeHWArgs;
  44. let quadMesh, hwScene, hwMesh;
  45. let cameraPos, projScreenMatrixUniform, frustumPlanesUniform, cotHalfFovUniform;
  46. let screenTriAttr, screenTriAtomic, screenTriRead;
  47. let screenInstAttr, screenInstAtomic, screenInstRead;
  48. let maxPixels;
  49. const rows = 400;
  50. const cols = 400;
  51. const instanceCount = rows * cols;
  52. const MAX_RASTER_SIZE = 16;
  53. const options = { Mode: 'Meshlet Debug', Rasterizer: 'Both' };
  54. // Buffer visibility packaging configuration
  55. const TRIANGLE_INDEX_BITS = 14; // Bits allocated for triangle index (2^14 = 16384 max triangles)
  56. const TRIANGLE_INDEX_MASK = 0x3FFF; // Bitmask to extract triangle index (14 bits)
  57. const INSTANCE_INDEX_BITS = 18; // Bits allocated for instance id (2^18 = 262144 max instances)
  58. const INSTANCE_INDEX_MASK = 0x3FFFF; // Bitmask to extract instance id (18 bits)
  59. const DEPTH_TRI_MAX = 262143.0; // Maximum 18-bit depth packed above the triangle index
  60. const DEPTH_INST_MAX = 16383.0; // Maximum 14-bit depth packed above the instance id
  61. const background = new THREE.Color( .1, .1, .1 );
  62. init();
  63. async function init() {
  64. renderer = new THREE.WebGPURenderer();
  65. renderer.setPixelRatio( window.devicePixelRatio );
  66. renderer.setSize( window.innerWidth, window.innerHeight );
  67. renderer.setAnimationLoop( animate );
  68. renderer.inspector = new Inspector();
  69. document.body.appendChild( renderer.domElement );
  70. await renderer.init();
  71. camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, .25, 1000000 );
  72. camera.position.set( 0, 15, 50 );
  73. timer = new THREE.Timer();
  74. controls = new FirstPersonControls( camera, renderer.domElement );
  75. controls.movementSpeed = 30;
  76. controls.lookSpeed = 0.2;
  77. controls.lookAt( 0, - 1.5, 0 );
  78. // Generate LOD Geometries
  79. const lods = [
  80. { geometry: new TeapotGeometry( 1, 10 ), error: 0.0 },
  81. { geometry: new TeapotGeometry( 1, 8 ), error: 0.005 },
  82. { geometry: new TeapotGeometry( 1, 6 ), error: 0.015 },
  83. { geometry: new TeapotGeometry( 1, 5 ), error: 0.03 },
  84. { geometry: new TeapotGeometry( 1, 4 ), error: 0.06 },
  85. { geometry: new TeapotGeometry( 1, 3 ), error: 0.1 },
  86. { geometry: new TeapotGeometry( 1, 2 ), error: 0.2 }
  87. ];
  88. let totalVertices = 0;
  89. let totalIndices = 0;
  90. for ( const lod of lods ) {
  91. const geom = lod.geometry;
  92. const pos = geom.attributes.position;
  93. const uvs = geom.attributes.uv;
  94. const idx = geom.index ? Array.from( geom.index.array ) : Array.from( { length: pos.count }, ( _, i ) => i );
  95. lod.numVertices = pos.count;
  96. lod.numTriangles = idx.length / 3;
  97. lod.vertexOffset = totalVertices;
  98. lod.indexOffset = totalIndices;
  99. lod.positions = pos;
  100. lod.uvs = uvs;
  101. lod.indices = idx;
  102. totalVertices += pos.count;
  103. totalIndices += idx.length;
  104. }
  105. const maxTrianglesPerInstance = lods[ 0 ].numTriangles;
  106. const totalTriangles = rows * cols * maxTrianglesPerInstance;
  107. document.getElementById( 'triangleCount' ).innerText = new Intl.NumberFormat().format( totalTriangles );
  108. const vertexArray = new Float32Array( totalVertices * 4 ); // vec4 padded
  109. const uvArray = new Float32Array( totalVertices * 2 );
  110. const indexArray = new Uint32Array( totalIndices );
  111. const meshletTriangleArray = new Uint32Array( totalIndices / 3 ); // 1 meshlet ID per triangle
  112. let currentMeshletId = 1;
  113. for ( const lod of lods ) {
  114. for ( let i = 0; i < lod.numVertices; i ++ ) {
  115. const vIdx = lod.vertexOffset + i;
  116. vertexArray[ vIdx * 4 + 0 ] = lod.positions.getX( i );
  117. vertexArray[ vIdx * 4 + 1 ] = lod.positions.getY( i );
  118. vertexArray[ vIdx * 4 + 2 ] = lod.positions.getZ( i );
  119. vertexArray[ vIdx * 4 + 3 ] = 1.0;
  120. if ( lod.uvs ) {
  121. uvArray[ vIdx * 2 + 0 ] = lod.uvs.getX( i );
  122. uvArray[ vIdx * 2 + 1 ] = lod.uvs.getY( i );
  123. }
  124. }
  125. let currentTriCount = 0;
  126. for ( let i = 0; i < lod.numTriangles; i ++ ) {
  127. const triIdx = ( lod.indexOffset / 3 ) + i;
  128. indexArray[ triIdx * 3 + 0 ] = lod.vertexOffset + lod.indices[ i * 3 + 0 ];
  129. indexArray[ triIdx * 3 + 1 ] = lod.vertexOffset + lod.indices[ i * 3 + 1 ];
  130. indexArray[ triIdx * 3 + 2 ] = lod.vertexOffset + lod.indices[ i * 3 + 2 ];
  131. if ( currentTriCount >= 126 ) {
  132. currentMeshletId ++;
  133. currentTriCount = 0;
  134. }
  135. meshletTriangleArray[ triIdx ] = currentMeshletId;
  136. currentTriCount ++;
  137. }
  138. currentMeshletId ++;
  139. }
  140. // Precompute Bounding Spheres for each 64-triangle Chunk (Cluster)
  141. let totalChunks = 0;
  142. for ( const lod of lods ) {
  143. lod.numChunks = Math.ceil( lod.numTriangles / 64 );
  144. lod.chunkStart = totalChunks;
  145. totalChunks += lod.numChunks;
  146. }
  147. const chunkBoundsData = new Float32Array( totalChunks * 4 ); // vec4: cx, cy, cz, radius
  148. let currentChunkId = 0;
  149. for ( const lod of lods ) {
  150. const positions = lod.positions;
  151. const indices = lod.indices;
  152. for ( let c = 0; c < lod.numChunks; c ++ ) {
  153. const startTri = c * 64;
  154. const endTri = Math.min( startTri + 64, lod.numTriangles );
  155. // 1. Calculate Center
  156. let cx = 0, cy = 0, cz = 0;
  157. const vertCount = ( endTri - startTri ) * 3;
  158. for ( let t = startTri; t < endTri; t ++ ) {
  159. for ( let v = 0; v < 3; v ++ ) {
  160. const idx = indices[ t * 3 + v ];
  161. cx += positions.getX( idx );
  162. cy += positions.getY( idx );
  163. cz += positions.getZ( idx );
  164. }
  165. }
  166. cx /= vertCount;
  167. cy /= vertCount;
  168. cz /= vertCount;
  169. // 2. Calculate Radius
  170. let maxDistSq = 0;
  171. for ( let t = startTri; t < endTri; t ++ ) {
  172. for ( let v = 0; v < 3; v ++ ) {
  173. const idx = indices[ t * 3 + v ];
  174. const dx = positions.getX( idx ) - cx;
  175. const dy = positions.getY( idx ) - cy;
  176. const dz = positions.getZ( idx ) - cz;
  177. const distSq = dx * dx + dy * dy + dz * dz;
  178. if ( distSq > maxDistSq ) maxDistSq = distSq;
  179. }
  180. }
  181. const radius = Math.sqrt( maxDistSq );
  182. chunkBoundsData[ currentChunkId * 4 + 0 ] = cx;
  183. chunkBoundsData[ currentChunkId * 4 + 1 ] = cy;
  184. chunkBoundsData[ currentChunkId * 4 + 2 ] = cz;
  185. chunkBoundsData[ currentChunkId * 4 + 3 ] = radius;
  186. currentChunkId ++;
  187. }
  188. }
  189. // Upload LOD offsets to GPU (uvec4: triangleStart, numTriangles, chunkStart, 0)
  190. const lodOffsetsData = new Uint32Array( lods.length * 4 );
  191. for ( let i = 0; i < lods.length; i ++ ) {
  192. lodOffsetsData[ i * 4 + 0 ] = lods[ i ].indexOffset / 3;
  193. lodOffsetsData[ i * 4 + 1 ] = lods[ i ].numTriangles;
  194. lodOffsetsData[ i * 4 + 2 ] = lods[ i ].chunkStart;
  195. }
  196. const lodOffsetsBuffer = storage( new THREE.StorageBufferAttribute( lodOffsetsData, 4 ), 'uvec4', lods.length ).toReadOnly();
  197. const chunkBoundsBuffer = storage( new THREE.StorageBufferAttribute( chunkBoundsData, 4 ), 'vec4', totalChunks ).toReadOnly();
  198. // Storage Buffers
  199. const vertexBuffer = storage( new THREE.StorageBufferAttribute( vertexArray, 4 ), 'vec4', totalVertices ).toReadOnly();
  200. const uvBuffer = storage( new THREE.StorageBufferAttribute( uvArray, 2 ), 'vec2', totalVertices ).toReadOnly();
  201. const indexBuffer = storage( new THREE.StorageBufferAttribute( indexArray, 1 ), 'uint', totalIndices ).toReadOnly();
  202. const meshletIdBuffer = storage( new THREE.StorageBufferAttribute( meshletTriangleArray, 1 ), 'uint', totalIndices / 3 ).toReadOnly();
  203. const materialModeUniform = uniform( 0, 'uint' );
  204. const textureMap = new THREE.TextureLoader().load( 'textures/uv_grid_directx.jpg' );
  205. textureMap.colorSpace = THREE.SRGBColorSpace;
  206. textureMap.wrapS = THREE.RepeatWrapping;
  207. textureMap.wrapT = THREE.RepeatWrapping;
  208. const timeScale = uniform( 1.0 );
  209. const parameterGroup = renderer.inspector.createParameters( 'Parameters' );
  210. parameterGroup.add( options, 'Mode', { 'Meshlet Debug': 'Meshlet Debug', 'Texture': 'Texture' } ).addEventListener( 'change', ( e ) => {
  211. materialModeUniform.value = e.value === 'Texture' ? 1 : 0;
  212. } );
  213. parameterGroup.add( options, 'Rasterizer', { 'SW Only': 'SW Only', 'HW Only': 'HW Only', 'Both': 'Both' } );
  214. parameterGroup.add( timeScale, 'value', 0.0, 1.0 ).name( 'Animation Speed' );
  215. // Packed visibility buffers — depth in the high bits, payload in the low bits,
  216. // so a single atomicMax resolves the depth test and the payload write together
  217. // and the winner is order-independent (no frame-to-frame flicker).
  218. // screenTri: depth(18) | megaTriangleIndex(14)
  219. // screenInst: depth(14) | instId(18)
  220. createScreenBuffers();
  221. const staticInstanceData = new Float32Array( instanceCount * 4 );
  222. let dataIndex = 0;
  223. for ( let i = 0; i < rows; i ++ ) {
  224. for ( let j = 0; j < cols; j ++ ) {
  225. staticInstanceData[ dataIndex ++ ] = ( i - rows / 2 ) * 4.0;
  226. staticInstanceData[ dataIndex ++ ] = - 1;
  227. staticInstanceData[ dataIndex ++ ] = ( j - cols / 2 ) * 4.0;
  228. staticInstanceData[ dataIndex ++ ] = 1.0; // scale
  229. }
  230. }
  231. const instanceDataBuffer = storage( new THREE.StorageBufferAttribute( staticInstanceData, 4 ), 'vec4', instanceCount );
  232. const instanceWorldData = new Float32Array( instanceCount * 16 );
  233. const instanceMvpData = new Float32Array( instanceCount * 16 );
  234. const instanceWorldAttr = new THREE.StorageBufferAttribute( instanceWorldData, 16 );
  235. const instanceMvpAttr = new THREE.StorageBufferAttribute( instanceMvpData, 16 );
  236. const instanceWorldBuffer = storage( instanceWorldAttr, 'mat4', instanceCount );
  237. const instanceMvpBuffer = storage( instanceMvpAttr, 'mat4', instanceCount );
  238. const instanceWorldRead = storage( instanceWorldAttr, 'mat4', instanceCount ).toReadOnly();
  239. const workQueueCountData = new Uint32Array( 1 );
  240. const workQueueCountAttr = new THREE.StorageBufferAttribute( workQueueCountData, 1 );
  241. const workQueueCountAtomic = storage( workQueueCountAttr, 'uint', 1 ).toAtomic();
  242. const workQueueCountRead = storage( workQueueCountAttr, 'uint', 1 ).toReadOnly();
  243. const dispatchData = new Uint32Array( 3 );
  244. const dispatchAttr = new THREE.IndirectStorageBufferAttribute( dispatchData, 3 );
  245. const dispatchBuffer = storage( dispatchAttr, 'uint', 3 );
  246. // Work queue budget — one item is a 64-triangle chunk of one visible instance
  247. const MAX_WORK_ITEMS = 2820000;
  248. const workQueueData = new Uint32Array( MAX_WORK_ITEMS * 4 );
  249. const workQueueBuffer = storage( new THREE.StorageBufferAttribute( workQueueData, 4 ), 'uvec4', MAX_WORK_ITEMS );
  250. // HW Rasterizer Buffers (for large triangles that exceed SW raster budget)
  251. const MAX_HW_TRIANGLES = 100000;
  252. // HW queue: index 0 is atomic counter, indices 1..MAX store payload32
  253. const hwQueueData = new Uint32Array( MAX_HW_TRIANGLES + 1 );
  254. const hwQueueAttr = new THREE.StorageBufferAttribute( hwQueueData, 1 );
  255. const hwQueueAtomic = storage( hwQueueAttr, 'uint', MAX_HW_TRIANGLES + 1 ).toAtomic();
  256. const hwQueueRead = storage( hwQueueAttr, 'uint', MAX_HW_TRIANGLES + 1 ).toReadOnly();
  257. // Draw indirect buffer: vertexCount, instanceCount, firstVertex, firstInstance
  258. const hwDrawData = new Uint32Array( 4 );
  259. const hwDrawAttr = new THREE.IndirectStorageBufferAttribute( hwDrawData, 4 );
  260. const hwDrawBuffer = storage( hwDrawAttr, 'uint', 4 );
  261. projScreenMatrixUniform = uniform( new THREE.Matrix4() );
  262. frustumPlanesUniform = uniformArray( [
  263. new THREE.Vector4(), new THREE.Vector4(), new THREE.Vector4(),
  264. new THREE.Vector4(), new THREE.Vector4(), new THREE.Vector4()
  265. ], 'vec4' );
  266. cameraPos = uniform( new THREE.Vector3() );
  267. cotHalfFovUniform = uniform( 1.0 );
  268. const pixelErrorThresholdUniform = uniform( 4.0 );
  269. const maxRasterSizeUniform = uniform( MAX_RASTER_SIZE, 'int' ); // Max bounding box size in pixels for SW rasterizer
  270. // Compute Clear
  271. computeClear = Fn( () => {
  272. atomicStore( screenTriAtomic.element( instanceIndex ), uint( 0 ) );
  273. atomicStore( screenInstAtomic.element( instanceIndex ), uint( 0 ) );
  274. If( instanceIndex.equal( 0 ), () => {
  275. atomicStore( workQueueCountAtomic.element( 0 ), uint( 0 ) );
  276. atomicStore( hwQueueAtomic.element( 0 ), uint( 0 ) );
  277. } );
  278. } )().compute( maxPixels, [ 256 ] ).setName( 'Compute Clear' );
  279. // Compute Frustum (GPU Culling, LOD & Work Allocation)
  280. computeFrustum = Fn( () => {
  281. const data = instanceDataBuffer.element( instanceIndex );
  282. const pos = data.xyz;
  283. const scale = data.w;
  284. const i = float( instanceIndex );
  285. // Rotation
  286. const rotY = time.mul( timeScale ).add( i );
  287. const c = cos( rotY );
  288. const s = sin( rotY );
  289. // Compose MatrixWorld
  290. const matrixWorld = mat4(
  291. vec4( c.mul( scale ), 0.0, s.mul( scale ), 0.0 ),
  292. vec4( 0.0, scale, 0.0, 0.0 ),
  293. vec4( s.negate().mul( scale ), 0.0, c.mul( scale ), 0.0 ),
  294. vec4( pos, 1.0 )
  295. );
  296. const visible = bool( true ).toVar();
  297. const radius = scale.mul( 2.0 ); // bounding sphere radius
  298. // Frustum culling using the 6 extracted world-space planes
  299. Loop( { start: 0, end: 6 }, ( { i: planeIndex } ) => {
  300. const plane = frustumPlanesUniform.element( planeIndex );
  301. const dist = dot( plane.xyz, pos ).add( plane.w );
  302. If( dist.lessThan( radius.negate() ), () => {
  303. visible.assign( false );
  304. } );
  305. } );
  306. If( visible, () => {
  307. const distToCamera = distance( cameraPos, pos );
  308. // Precompute projection factor once (Screen-Space Projected Error)
  309. // pixelError = cotHalfFov * errorWorld / dist * screenH / 2
  310. const pixelFactor = cotHalfFovUniform.div( max( 0.01, distToCamera ) ).mul( float( screenSize.y ) ).div( 2.0 );
  311. const lodLevel = uint( 0 ).toVar();
  312. let lodSelection = null;
  313. for ( let i = lods.length - 1; i > 0; i -- ) {
  314. const checkLod = float( lods[ i ].error ).mul( scale ).mul( pixelFactor ).lessThanEqual( pixelErrorThresholdUniform );
  315. if ( lodSelection === null ) {
  316. lodSelection = If( checkLod, () => {
  317. lodLevel.assign( i );
  318. } );
  319. } else {
  320. lodSelection = lodSelection.ElseIf( checkLod, () => {
  321. lodLevel.assign( i );
  322. } );
  323. }
  324. }
  325. const lodData = lodOffsetsBuffer.element( lodLevel );
  326. const lodTriStart = lodData.x;
  327. const lodNumTriangles = lodData.y;
  328. const lodChunkStart = lodData.z;
  329. // Calculate Work Items (64 triangles per item)
  330. const workItems = lodNumTriangles.add( 63 ).div( 64 );
  331. // Evaluate each Chunk (Cluster)
  332. Loop( { name: 'cIdx', type: 'uint', start: uint( 0 ), end: workItems, condition: '<' }, ( { cIdx: chunkIndex } ) => {
  333. const globalChunkId = lodChunkStart.add( uint( chunkIndex ) );
  334. const chunkBounds = chunkBoundsBuffer.element( globalChunkId );
  335. const chunkCenterLocal = chunkBounds.xyz;
  336. const chunkRadiusLocal = chunkBounds.w;
  337. // Transform chunk bounding sphere to world space and store as var to prevent inlining
  338. const chunkCenterWorld = matrixWorld.mul( vec4( chunkCenterLocal, 1.0 ) ).xyz.toVar();
  339. const chunkRadiusWorld = chunkRadiusLocal.mul( scale ).toVar();
  340. const chunkVisible = bool( true ).toVar();
  341. // Frustum cull the chunk
  342. Loop( { name: 'pIdx', start: 0, end: 6 }, ( { pIdx: planeIndex } ) => {
  343. const plane = frustumPlanesUniform.element( planeIndex );
  344. const chunkDist = dot( plane.xyz, chunkCenterWorld ).add( plane.w );
  345. If( chunkDist.lessThan( chunkRadiusWorld.negate() ), () => {
  346. chunkVisible.assign( false );
  347. } );
  348. } );
  349. If( chunkVisible, () => {
  350. const itemIndex = atomicAdd( workQueueCountAtomic.element( 0 ), 1 );
  351. // uvec4( instanceIndex, triangleStart, lodNumTriangles, chunkIndex )
  352. workQueueBuffer.element( itemIndex ).assign(
  353. uvec4( instanceIndex, lodTriStart, lodNumTriangles, uint( chunkIndex ) )
  354. );
  355. } );
  356. } );
  357. // Store transform for this instance
  358. instanceWorldBuffer.element( instanceIndex ).assign( matrixWorld );
  359. instanceMvpBuffer.element( instanceIndex ).assign( projScreenMatrixUniform.mul( matrixWorld ) );
  360. } );
  361. } )().compute( instanceCount ).setName( 'Compute Frustum' );
  362. // Compute Dispatch (Indirect arguments)
  363. computeDispatch = Fn( () => {
  364. const totalWorkgroups = workQueueCountRead.element( 0 );
  365. const maxDim = uint( 65535 );
  366. // Split totalWorkgroups into 2D dispatch if it exceeds 65535
  367. const dispatchX = min( totalWorkgroups, maxDim );
  368. const dispatchY = totalWorkgroups.add( maxDim ).sub( 1 ).div( maxDim );
  369. dispatchBuffer.element( 0 ).assign( dispatchX );
  370. dispatchBuffer.element( 1 ).assign( dispatchY );
  371. dispatchBuffer.element( 2 ).assign( 1 );
  372. } )().compute( 1 ).setName( 'Compute Dispatch' );
  373. // Edge function for barycentric coordinates
  374. const edgeFunction = Fn( ( [ a, b, c ] ) => {
  375. // (c.y - a.y) * (b.x - a.x) - (c.x - a.x) * (b.y - a.y)
  376. return c.y.sub( a.y ).mul( b.x.sub( a.x ) ).sub( c.x.sub( a.x ).mul( b.y.sub( a.y ) ) );
  377. } );
  378. // Compute Rasterizer
  379. computeRasterize = Fn( () => {
  380. const totalWorkgroups = workQueueCountRead.element( 0 );
  381. const totalThreads = totalWorkgroups.mul( 64 );
  382. If( instanceIndex.lessThan( totalThreads ), () => {
  383. const workItemId = instanceIndex.div( 64 );
  384. const localTriangleIndex = instanceIndex.mod( 64 );
  385. const workItem = workQueueBuffer.element( workItemId );
  386. const instId = workItem.x;
  387. const lodTriStart = workItem.y;
  388. const lodNumTriangles = workItem.z;
  389. const chunkIndex = workItem.w;
  390. const globalTriangleIndex = chunkIndex.mul( 64 ).add( localTriangleIndex );
  391. If( globalTriangleIndex.lessThan( lodNumTriangles ), () => {
  392. const megaTriangleIndex = lodTriStart.add( globalTriangleIndex );
  393. const indexOffset = megaTriangleIndex.mul( 3 );
  394. const i0 = indexBuffer.element( indexOffset );
  395. const i1 = indexBuffer.element( indexOffset.add( 1 ) );
  396. const i2 = indexBuffer.element( indexOffset.add( 2 ) );
  397. const v0 = vertexBuffer.element( i0 );
  398. const v1 = vertexBuffer.element( i1 );
  399. const v2 = vertexBuffer.element( i2 );
  400. const instMvpMatrix = instanceMvpBuffer.element( instId );
  401. // MVP
  402. const p0 = instMvpMatrix.mul( v0 );
  403. const p1 = instMvpMatrix.mul( v1 );
  404. const p2 = instMvpMatrix.mul( v2 );
  405. // Near plane clipping
  406. If( p0.w.greaterThan( 0.0 ).and( p1.w.greaterThan( 0.0 ) ).and( p2.w.greaterThan( 0.0 ) ), () => {
  407. const ndc0 = p0.xyz.div( p0.w );
  408. const ndc1 = p1.xyz.div( p1.w );
  409. const ndc2 = p2.xyz.div( p2.w );
  410. // Early Backface Culling in NDC
  411. const areaNdc = edgeFunction( ndc0, ndc1, ndc2 );
  412. If( areaNdc.greaterThan( 0.0 ), () => {
  413. // NDC guard: skip triangles entirely outside clip volume
  414. const ndcMinX = min( ndc0.x, min( ndc1.x, ndc2.x ) );
  415. const ndcMaxX = max( ndc0.x, max( ndc1.x, ndc2.x ) );
  416. const ndcMinY = min( ndc0.y, min( ndc1.y, ndc2.y ) );
  417. const ndcMaxY = max( ndc0.y, max( ndc1.y, ndc2.y ) );
  418. If( ndcMaxX.greaterThan( - 1.0 ).and( ndcMinX.lessThan( 1.0 ) ).and( ndcMaxY.greaterThan( - 1.0 ) ).and( ndcMinY.lessThan( 1.0 ) ), () => {
  419. // Map to screen coordinates
  420. const w = screenSize.x;
  421. const h = screenSize.y;
  422. const s0 = ndc0.xy.add( 1.0 ).mul( 0.5 ).mul( vec2( w, h ) );
  423. const s1 = ndc1.xy.add( 1.0 ).mul( 0.5 ).mul( vec2( w, h ) );
  424. const s2 = ndc2.xy.add( 1.0 ).mul( 0.5 ).mul( vec2( w, h ) );
  425. // Bounding Box
  426. const minX = max( 0.0, min( s0.x, min( s1.x, s2.x ) ) );
  427. const maxX = min( w.sub( 1.0 ), max( s0.x, max( s1.x, s2.x ) ) );
  428. const minY = max( 0.0, min( s0.y, min( s1.y, s2.y ) ) );
  429. const maxY = min( h.sub( 1.0 ), max( s0.y, max( s1.y, s2.y ) ) );
  430. const startX = int( floor( minX ) );
  431. const endX = int( floor( maxX ) );
  432. const startY = int( floor( minY ) );
  433. const endY = int( floor( maxY ) );
  434. // Big triangle guard: skip triangles larger than maxRasterSize
  435. // This is the key performance safeguard — software rasterizers
  436. // should only handle small triangles. Large triangles cause O(n²)
  437. // pixel iteration per thread, which kills performance when close.
  438. const bbWidth = endX.sub( startX );
  439. const bbHeight = endY.sub( startY );
  440. // Compute payload32 for HW path (full precision)
  441. // payload32: instId (18 bits) | megaTriangleIndex (14 bits)
  442. const payload32 = instId.shiftLeft( TRIANGLE_INDEX_BITS ).bitOr( megaTriangleIndex.bitAnd( TRIANGLE_INDEX_MASK ) );
  443. // Sub-pixel / Valid bounds rejection + big triangle guard
  444. If( startX.lessThanEqual( endX ).and( startY.lessThanEqual( endY ) ).and( bbWidth.lessThanEqual( maxRasterSizeUniform ) ).and( bbHeight.lessThanEqual( maxRasterSizeUniform ) ), () => {
  445. const area = edgeFunction( s0, s1, s2 );
  446. const stepX_w0 = s1.y.sub( s2.y );
  447. const stepY_w0 = s2.x.sub( s1.x );
  448. const stepX_w1 = s2.y.sub( s0.y );
  449. const stepY_w1 = s0.x.sub( s2.x );
  450. const stepX_w2 = s0.y.sub( s1.y );
  451. const stepY_w2 = s1.x.sub( s0.x );
  452. // Top-Left rule check for each edge to guarantee watertightness
  453. const isTopLeft0 = stepX_w0.lessThan( 0.0 ).or( stepX_w0.equal( 0.0 ).and( stepY_w0.greaterThan( 0.0 ) ) );
  454. const isTopLeft1 = stepX_w1.lessThan( 0.0 ).or( stepX_w1.equal( 0.0 ).and( stepY_w1.greaterThan( 0.0 ) ) );
  455. const isTopLeft2 = stepX_w2.lessThan( 0.0 ).or( stepX_w2.equal( 0.0 ).and( stepY_w2.greaterThan( 0.0 ) ) );
  456. const bias0 = isTopLeft0.select( 0.0, - 1e-5 );
  457. const bias1 = isTopLeft1.select( 0.0, - 1e-5 );
  458. const bias2 = isTopLeft2.select( 0.0, - 1e-5 );
  459. const pStart = vec2( float( startX ).add( 0.5 ), float( startY ).add( 0.5 ) );
  460. const row_w0 = edgeFunction( s1, s2, pStart ).toVar();
  461. const row_w1 = edgeFunction( s2, s0, pStart ).toVar();
  462. const row_w2 = edgeFunction( s0, s1, pStart ).toVar();
  463. row_w0.addAssign( bias0 );
  464. row_w1.addAssign( bias1 );
  465. row_w2.addAssign( bias2 );
  466. // Incremental Z Math (ALU Optimization)
  467. const b0_start = row_w0.div( area );
  468. const b1_start = row_w1.div( area );
  469. const b2_start = row_w2.div( area );
  470. const row_z = b0_start.mul( ndc0.z ).add( b1_start.mul( ndc1.z ) ).add( b2_start.mul( ndc2.z ) ).toVar();
  471. const stepX_z = stepX_w0.div( area ).mul( ndc0.z ).add( stepX_w1.div( area ).mul( ndc1.z ) ).add( stepX_w2.div( area ).mul( ndc2.z ) );
  472. const stepY_z = stepY_w0.div( area ).mul( ndc0.z ).add( stepY_w1.div( area ).mul( ndc1.z ) ).add( stepY_w2.div( area ).mul( ndc2.z ) );
  473. Loop( { name: 'y', type: 'int', start: startY, end: endY, condition: '<=' }, ( { y } ) => {
  474. const w0 = row_w0.toVar();
  475. const w1 = row_w1.toVar();
  476. const w2 = row_w2.toVar();
  477. const z = row_z.toVar();
  478. Loop( { name: 'x', type: 'int', start: startX, end: endX, condition: '<=' }, ( { x } ) => {
  479. If( w0.greaterThanEqual( 0.0 ).and( w1.greaterThanEqual( 0.0 ) ).and( w2.greaterThanEqual( 0.0 ) ), () => {
  480. If( z.greaterThanEqual( 0.0 ).and( z.lessThanEqual( 1.0 ) ), () => {
  481. // Depth (fourth-root distribution) packed above each payload's bits
  482. const zEncoded = sqrt( sqrt( float( 1.0 ).sub( z ) ) );
  483. const depthTri = uint( zEncoded.mul( DEPTH_TRI_MAX ) );
  484. const depthInst = uint( zEncoded.mul( DEPTH_INST_MAX ) );
  485. const packedTri = depthTri.shiftLeft( TRIANGLE_INDEX_BITS ).bitOr( megaTriangleIndex.bitAnd( TRIANGLE_INDEX_MASK ) );
  486. const packedInst = depthInst.shiftLeft( INSTANCE_INDEX_BITS ).bitOr( instId );
  487. const pixelIndex = uint( y ).mul( uint( screenSize.x ) ).add( uint( x ) );
  488. // Early depth pre-check: skip the atomics if the pixel already has a closer fragment
  489. const currentDepth = atomicLoad( screenTriAtomic.element( pixelIndex ) ).shiftRight( TRIANGLE_INDEX_BITS );
  490. If( depthTri.greaterThanEqual( currentDepth ), () => {
  491. // Depth occupies the high bits, so atomicMax resolves the depth
  492. // test and the payload write in one order-independent step
  493. atomicMax( screenTriAtomic.element( pixelIndex ), packedTri );
  494. atomicMax( screenInstAtomic.element( pixelIndex ), packedInst );
  495. } );
  496. } );
  497. } );
  498. w0.addAssign( stepX_w0 );
  499. w1.addAssign( stepX_w1 );
  500. w2.addAssign( stepX_w2 );
  501. z.addAssign( stepX_z );
  502. } );
  503. row_w0.addAssign( stepY_w0 );
  504. row_w1.addAssign( stepY_w1 );
  505. row_w2.addAssign( stepY_w2 );
  506. row_z.addAssign( stepY_z );
  507. } );
  508. } ).Else( () => {
  509. // Big triangle → enqueue for HW rasterization
  510. If( startX.lessThanEqual( endX ).and( startY.lessThanEqual( endY ) ), () => {
  511. const hwCount = atomicAdd( hwQueueAtomic.element( 0 ), 1 );
  512. const hwSlot = hwCount.add( 1 );
  513. atomicStore( hwQueueAtomic.element( hwSlot ), payload32 );
  514. } );
  515. } );
  516. } );
  517. } ); // End Early Backface Culling
  518. } ); // End Near Plane Clipping
  519. } ); // End globalTriangleIndex bounds check
  520. } ); // End instanceIndex bounds check
  521. } )().compute( dispatchAttr ).setName( 'Compute Rasterize' );
  522. // Compute HW Draw Indirect Args
  523. computeHWArgs = Fn( () => {
  524. const hwCount = atomicLoad( hwQueueAtomic.element( 0 ) );
  525. // Non-indexed draw: vertexCount = hwCount * 3 (3 verts per triangle)
  526. hwDrawBuffer.element( 0 ).assign( hwCount.mul( 3 ) ); // vertexCount
  527. hwDrawBuffer.element( 1 ).assign( uint( 1 ) ); // instanceCount
  528. hwDrawBuffer.element( 2 ).assign( uint( 0 ) ); // firstVertex
  529. hwDrawBuffer.element( 3 ).assign( uint( 0 ) ); // firstInstance
  530. } )().compute( 1 ).setName( 'Compute HW Args' );
  531. // Hash function for meshlet colors (shared between HW mesh and fullscreen quad)
  532. const hashColor = Fn( ( [ id_in ] ) => {
  533. let id = uint( id_in ).toVar();
  534. id = id.mul( uint( 747796405 ) ).add( uint( 289559509 ) );
  535. id = id.shiftRight( 16 ).bitXor( id ).mul( uint( 277803737 ) );
  536. id = id.shiftRight( 16 ).bitXor( id );
  537. const r = float( id.bitAnd( uint( 255 ) ) ).div( 255.0 );
  538. const g = float( id.shiftRight( 8 ).bitAnd( uint( 255 ) ) ).div( 255.0 );
  539. const b = float( id.shiftRight( 16 ).bitAnd( uint( 255 ) ) ).div( 255.0 );
  540. return vec4( r.mul( 0.8 ).add( 0.2 ), g.mul( 0.8 ).add( 0.2 ), b.mul( 0.8 ).add( 0.2 ), 1.0 );
  541. } );
  542. // HW Rasterizer Mesh (renders big triangles via GPU hardware pipeline)
  543. // Unlike the SW rasterizer which writes to an atomic screen buffer,
  544. // the HW mesh renders directly with real colors and hardware depth testing.
  545. // It renders AFTER the fullscreen quad, overlaying HW-rasterized triangles.
  546. {
  547. // Geometry: dummy positions, vertex count driven by indirect draw
  548. const hwGeometry = new THREE.BufferGeometry();
  549. hwGeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( new Float32Array( MAX_HW_TRIANGLES * 3 * 3 ), 3 ) );
  550. hwGeometry.setIndirect( hwDrawAttr );
  551. hwGeometry.boundingSphere = new THREE.Sphere().set( new THREE.Vector3(), Infinity );
  552. // Varying to pass payload and UVs from vertex to fragment
  553. const vPayload = varyingProperty( 'uint', 'vPayload' );
  554. const vUv = varyingProperty( 'vec2', 'vUv' );
  555. const hwMaterial = new THREE.NodeMaterial();
  556. hwMaterial.depthWrite = true;
  557. hwMaterial.depthTest = true;
  558. // Vertex shader: vertex pulling from HW queue
  559. hwMaterial.positionNode = Fn( () => {
  560. // vertexIndex: 0,1,2, 3,4,5, 6,7,8, ...
  561. const triIndex = vertexIndex.div( 3 ); // which triangle in HW queue
  562. const localVert = vertexIndex.mod( 3 ); // which vertex (0, 1, 2)
  563. const payload32 = hwQueueRead.element( triIndex.add( 1 ) );
  564. const instId = payload32.shiftRight( TRIANGLE_INDEX_BITS );
  565. const megaTriIdx = payload32.bitAnd( TRIANGLE_INDEX_MASK );
  566. // Fetch actual vertex index from the mega index buffer
  567. const vertGlobalIdx = indexBuffer.element( megaTriIdx.mul( 3 ).add( localVert ) );
  568. const v = vertexBuffer.element( vertGlobalIdx );
  569. // Transform to world space
  570. const worldPos = instanceWorldRead.element( instId ).mul( v );
  571. const uvVal = uvBuffer.element( vertGlobalIdx );
  572. vUv.assign( uvVal );
  573. vPayload.assign( payload32 );
  574. return worldPos.xyz;
  575. } )();
  576. // Fragment shader: directly output final color (no storage buffer writes)
  577. hwMaterial.fragmentNode = Fn( () => {
  578. const payload32 = vPayload;
  579. const instId = payload32.shiftRight( TRIANGLE_INDEX_BITS );
  580. const megaTriangleIndex = payload32.bitAnd( TRIANGLE_INDEX_MASK );
  581. const outColor = vec4( 0.0 ).toVar();
  582. If( materialModeUniform.equal( 0 ), () => {
  583. const meshletId = meshletIdBuffer.element( megaTriangleIndex ).add( instId.mul( 1000 ) );
  584. outColor.assign( hashColor( meshletId ) );
  585. } ).Else( () => {
  586. // Hardware interpolated UV!
  587. outColor.assign( texture( textureMap, vUv ) );
  588. } );
  589. return outColor;
  590. } )();
  591. hwMesh = new THREE.Mesh( hwGeometry, hwMaterial );
  592. hwMesh.frustumCulled = false;
  593. hwScene = new THREE.Scene();
  594. hwScene.add( hwMesh );
  595. }
  596. // Fullscreen Presentation Pass
  597. const material = new THREE.NodeMaterial();
  598. material.depthWrite = true;
  599. // Shared screen-coordinate helper
  600. const getPixelIndex = () => {
  601. const screenX = uint( floor( uv().x.mul( screenSize.x ) ) );
  602. const screenY = uint( floor( uv().y.oneMinus().mul( screenSize.y ) ) );
  603. return screenY.mul( uint( screenSize.x ) ).add( screenX );
  604. };
  605. // Output depth from the SW rasterizer so HW mesh can depth test against it
  606. material.depthNode = Fn( () => {
  607. const pixelIndex = getPixelIndex();
  608. // Depth lives in the high 18 bits of the packed value
  609. const depthTri = screenTriRead.element( pixelIndex ).shiftRight( TRIANGLE_INDEX_BITS );
  610. // Reconstruct NDC Z from non-linear depth (fourth-root distribution)
  611. const y = float( depthTri ).div( DEPTH_TRI_MAX );
  612. const y2 = y.mul( y );
  613. const v = y2.mul( y2 ); // raise to the fourth power (y^4) to get original v
  614. return float( 1.0 ).sub( v );
  615. } )();
  616. material.colorNode = Fn( () => {
  617. const pixelIndex = getPixelIndex();
  618. // Check for background immediately (depth in the high bits)
  619. const packedTri = screenTriRead.element( pixelIndex );
  620. // Background color for pixels with no geometry
  621. const outColor = vec4( background, 1.0 ).toVar();
  622. If( packedTri.shiftRight( TRIANGLE_INDEX_BITS ).greaterThan( 0 ), () => {
  623. // Unpack the two payloads from their depth-packed buffers
  624. const megaTriangleIndex = packedTri.bitAnd( TRIANGLE_INDEX_MASK );
  625. const instId = screenInstRead.element( pixelIndex ).bitAnd( INSTANCE_INDEX_MASK );
  626. // Visibility Buffer: Fetch exact vertices and UVs
  627. const i0 = indexBuffer.element( megaTriangleIndex.mul( 3 ).add( 0 ) );
  628. const i1 = indexBuffer.element( megaTriangleIndex.mul( 3 ).add( 1 ) );
  629. const i2 = indexBuffer.element( megaTriangleIndex.mul( 3 ).add( 2 ) );
  630. const v0 = vertexBuffer.element( i0 );
  631. const v1 = vertexBuffer.element( i1 );
  632. const v2 = vertexBuffer.element( i2 );
  633. const t_uv0 = uvBuffer.element( i0 );
  634. const t_uv1 = uvBuffer.element( i1 );
  635. const t_uv2 = uvBuffer.element( i2 );
  636. // Project Vertices to Screen Space
  637. const mvpMatrix = instanceMvpBuffer.element( instId );
  638. const p0 = mvpMatrix.mul( v0 );
  639. const p1 = mvpMatrix.mul( v1 );
  640. const p2 = mvpMatrix.mul( v2 );
  641. const ndc0 = p0.xyz.div( p0.w );
  642. const ndc1 = p1.xyz.div( p1.w );
  643. const ndc2 = p2.xyz.div( p2.w );
  644. const w = screenSize.x;
  645. const h = screenSize.y;
  646. const s0 = ndc0.xy.add( 1.0 ).mul( 0.5 ).mul( vec2( w, h ) );
  647. const s1 = ndc1.xy.add( 1.0 ).mul( 0.5 ).mul( vec2( w, h ) );
  648. const s2 = ndc2.xy.add( 1.0 ).mul( 0.5 ).mul( vec2( w, h ) );
  649. const p = vec2( uv().x.mul( screenSize.x ), uv().y.oneMinus().mul( screenSize.y ) );
  650. // Compute Barycentrics
  651. const area = edgeFunction( s0, s1, s2 );
  652. const w0 = edgeFunction( s1, s2, p );
  653. const w1 = edgeFunction( s2, s0, p );
  654. const w2 = edgeFunction( s0, s1, p );
  655. // Guard against division by zero for safe execution
  656. const safeArea = area.equal( 0.0 ).select( 1.0, area );
  657. const b0 = w0.div( safeArea );
  658. const b1 = w1.div( safeArea );
  659. const b2 = w2.div( safeArea );
  660. // Perspective correct UV interpolation (32-bit floats!)
  661. const z_inv = b0.div( p0.w ).add( b1.div( p1.w ) ).add( b2.div( p2.w ) );
  662. const safeZInv = z_inv.equal( 0.0 ).select( 1.0, z_inv );
  663. const b0_p = b0.div( p0.w ).div( safeZInv );
  664. const b1_p = b1.div( p1.w ).div( safeZInv );
  665. const b2_p = b2.div( p2.w ).div( safeZInv );
  666. const uv_interp = t_uv0.mul( b0_p ).add( t_uv1.mul( b1_p ) ).add( t_uv2.mul( b2_p ) );
  667. // Compute screen-space derivatives analytically (extremely clean, no helper fragment issues)
  668. const dw0_dx = s2.y.sub( s1.y );
  669. const dw1_dx = s0.y.sub( s2.y );
  670. const dw2_dx = s1.y.sub( s0.y );
  671. const dw0_dy = s1.x.sub( s2.x );
  672. const dw1_dy = s2.x.sub( s0.x );
  673. const dw2_dy = s0.x.sub( s1.x );
  674. const q0 = float( 1.0 ).div( p0.w );
  675. const q1 = float( 1.0 ).div( p1.w );
  676. const q2 = float( 1.0 ).div( p2.w );
  677. const sum_w_q = w0.mul( q0 ).add( w1.mul( q1 ) ).add( w2.mul( q2 ) );
  678. const safe_sum_w_q = sum_w_q.equal( 0.0 ).select( 1.0, sum_w_q );
  679. const dUvDx = (
  680. dw0_dx.mul( q0 ).mul( t_uv0.sub( uv_interp ) )
  681. .add( dw1_dx.mul( q1 ).mul( t_uv1.sub( uv_interp ) ) )
  682. .add( dw2_dx.mul( q2 ).mul( t_uv2.sub( uv_interp ) ) )
  683. ).div( safe_sum_w_q );
  684. const dUvDy = (
  685. dw0_dy.mul( q0 ).mul( t_uv0.sub( uv_interp ) )
  686. .add( dw1_dy.mul( q1 ).mul( t_uv1.sub( uv_interp ) ) )
  687. .add( dw2_dy.mul( q2 ).mul( t_uv2.sub( uv_interp ) ) )
  688. ).div( safe_sum_w_q );
  689. If( materialModeUniform.equal( 0 ), () => {
  690. const meshletId = meshletIdBuffer.element( megaTriangleIndex ).add( instId.mul( 1000 ) );
  691. outColor.assign( hashColor( meshletId ) );
  692. } ).Else( () => {
  693. outColor.assign( texture( textureMap, uv_interp ).grad( dUvDx, dUvDy ) );
  694. } );
  695. } );
  696. return outColor;
  697. } )();
  698. quadMesh = new THREE.QuadMesh( material );
  699. window.addEventListener( 'resize', onWindowResize );
  700. }
  701. function createScreenBuffers() {
  702. const size = new THREE.Vector2();
  703. renderer.getDrawingBufferSize( size );
  704. const newMaxPixels = size.x * size.y;
  705. if ( newMaxPixels === maxPixels ) return;
  706. maxPixels = newMaxPixels;
  707. if ( screenTriAttr ) screenTriAttr.dispose();
  708. if ( screenInstAttr ) screenInstAttr.dispose();
  709. const screenTriData = new Uint32Array( maxPixels );
  710. screenTriAttr = new THREE.StorageBufferAttribute( screenTriData, 1 );
  711. const screenInstData = new Uint32Array( maxPixels );
  712. screenInstAttr = new THREE.StorageBufferAttribute( screenInstData, 1 );
  713. if ( screenTriAtomic === undefined ) {
  714. screenTriAtomic = storage( screenTriAttr, 'uint', maxPixels ).toAtomic();
  715. screenTriRead = storage( screenTriAttr, 'uint', maxPixels ).toReadOnly();
  716. screenInstAtomic = storage( screenInstAttr, 'uint', maxPixels ).toAtomic();
  717. screenInstRead = storage( screenInstAttr, 'uint', maxPixels ).toReadOnly();
  718. } else {
  719. screenTriAtomic.value = screenTriAttr;
  720. screenTriAtomic.bufferCount = maxPixels;
  721. screenTriRead.value = screenTriAttr;
  722. screenTriRead.bufferCount = maxPixels;
  723. screenInstAtomic.value = screenInstAttr;
  724. screenInstAtomic.bufferCount = maxPixels;
  725. screenInstRead.value = screenInstAttr;
  726. screenInstRead.bufferCount = maxPixels;
  727. computeClear.count = maxPixels;
  728. computeClear.dispose();
  729. computeRasterize.dispose();
  730. computeFrustum.dispose();
  731. computeDispatch.dispose();
  732. computeHWArgs.dispose();
  733. quadMesh.material.dispose();
  734. hwMesh.material.dispose();
  735. }
  736. }
  737. function onWindowResize() {
  738. camera.aspect = window.innerWidth / window.innerHeight;
  739. camera.updateProjectionMatrix();
  740. renderer.setSize( window.innerWidth, window.innerHeight );
  741. createScreenBuffers();
  742. }
  743. const frustum = new THREE.Frustum();
  744. const projScreenMatrix = new THREE.Matrix4();
  745. const cameraInverse = new THREE.Matrix4();
  746. function animate() {
  747. timer.update();
  748. controls.update( timer.getDelta() );
  749. camera.updateMatrixWorld();
  750. cameraInverse.copy( camera.matrixWorld ).invert();
  751. projScreenMatrix.multiplyMatrices( camera.projectionMatrix, cameraInverse );
  752. frustum.setFromProjectionMatrix( projScreenMatrix );
  753. // Update GPU uniforms
  754. projScreenMatrixUniform.value.copy( projScreenMatrix );
  755. cameraPos.value.copy( camera.position );
  756. cotHalfFovUniform.value = camera.projectionMatrix.elements[ 5 ];
  757. // Pack frustum planes into the uniform array
  758. const planes = frustum.planes;
  759. const planesArray = frustumPlanesUniform.array;
  760. for ( let i = 0; i < 6; i ++ ) {
  761. const p = planes[ i ];
  762. planesArray[ i ].set( p.normal.x, p.normal.y, p.normal.z, p.constant );
  763. }
  764. // Compute & Render
  765. renderer.compute( computeClear );
  766. renderer.compute( computeFrustum );
  767. renderer.compute( computeDispatch );
  768. renderer.compute( computeRasterize );
  769. renderer.compute( computeHWArgs );
  770. const rasterMode = options.Rasterizer;
  771. // SW presentation (fullscreen quad reads atomic buffer)
  772. if ( rasterMode === 'SW Only' || rasterMode === 'Both' ) {
  773. quadMesh.render( renderer );
  774. }
  775. // HW mesh renders with real depth testing + colors
  776. if ( rasterMode === 'HW Only' || rasterMode === 'Both' ) {
  777. hwScene.background = ( rasterMode === 'HW Only' ) ? background : null;
  778. renderer.autoClear = ( rasterMode === 'HW Only' );
  779. renderer.render( hwScene, camera );
  780. renderer.autoClear = true;
  781. }
  782. }
  783. </script>
  784. </body>
  785. </html>
粤ICP备19079148号