GPUComputationRenderer.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. import {
  2. ClampToEdgeWrapping,
  3. DataTexture,
  4. FloatType,
  5. NearestFilter,
  6. RGBAFormat,
  7. ShaderMaterial,
  8. WebGLRenderTarget
  9. } from 'three';
  10. import { FullScreenQuad } from '../postprocessing/Pass.js';
  11. /**
  12. * GPUComputationRenderer, based on SimulationRenderer by @zz85.
  13. *
  14. * The GPUComputationRenderer uses the concept of variables. These variables are RGBA float textures that hold 4 floats
  15. * for each compute element (texel).
  16. *
  17. * Each variable has a fragment shader that defines the computation made to obtain the variable in question.
  18. * You can use as many variables you need, and make dependencies so you can use textures of other variables in the shader
  19. * (the sampler uniforms are added automatically) Most of the variables will need themselves as dependency.
  20. *
  21. * The renderer has actually two render targets per variable, to make ping-pong. Textures from the current frame are used
  22. * as inputs to render the textures of the next frame.
  23. *
  24. * The render targets of the variables can be used as input textures for your visualization shaders.
  25. *
  26. * Variable names should be valid identifiers and should not collide with THREE GLSL used identifiers.
  27. * a common approach could be to use 'texture' prefixing the variable name; i.e texturePosition, textureVelocity...
  28. *
  29. * The size of the computation (sizeX * sizeY) is defined as 'resolution' automatically in the shader. For example:
  30. * ```
  31. * #DEFINE resolution vec2( 1024.0, 1024.0 )
  32. * ```
  33. * Basic use:
  34. * ```js
  35. * // Initialization...
  36. *
  37. * // Create computation renderer
  38. * const gpuCompute = new GPUComputationRenderer( 1024, 1024, renderer );
  39. *
  40. * // Create initial state float textures
  41. * const pos0 = gpuCompute.createTexture();
  42. * const vel0 = gpuCompute.createTexture();
  43. * // and fill in here the texture data...
  44. *
  45. * // Add texture variables
  46. * const velVar = gpuCompute.addVariable( "textureVelocity", fragmentShaderVel, vel0 );
  47. * const posVar = gpuCompute.addVariable( "texturePosition", fragmentShaderPos, pos0 );
  48. *
  49. * // Add variable dependencies
  50. * gpuCompute.setVariableDependencies( velVar, [ velVar, posVar ] );
  51. * gpuCompute.setVariableDependencies( posVar, [ velVar, posVar ] );
  52. *
  53. * // Add custom uniforms
  54. * velVar.material.uniforms.time = { value: 0.0 };
  55. *
  56. * // Check for completeness
  57. * const error = gpuCompute.init();
  58. * if ( error !== null ) {
  59. * console.error( error );
  60. * }
  61. *
  62. * // In each frame...
  63. *
  64. * // Compute!
  65. * gpuCompute.compute();
  66. *
  67. * // Update texture uniforms in your visualization materials with the gpu renderer output
  68. * myMaterial.uniforms.myTexture.value = gpuCompute.getCurrentRenderTarget( posVar ).texture;
  69. *
  70. * // Do your rendering
  71. * renderer.render( myScene, myCamera );
  72. * ```
  73. *
  74. * Also, you can use utility functions to create ShaderMaterial and perform computations (rendering between textures)
  75. * Note that the shaders can have multiple input textures.
  76. *
  77. * ```js
  78. * const myFilter1 = gpuCompute.createShaderMaterial( myFilterFragmentShader1, { theTexture: { value: null } } );
  79. * const myFilter2 = gpuCompute.createShaderMaterial( myFilterFragmentShader2, { theTexture: { value: null } } );
  80. *
  81. * const inputTexture = gpuCompute.createTexture();
  82. *
  83. * // Fill in here inputTexture...
  84. *
  85. * myFilter1.uniforms.theTexture.value = inputTexture;
  86. *
  87. * const myRenderTarget = gpuCompute.createRenderTarget();
  88. * myFilter2.uniforms.theTexture.value = myRenderTarget.texture;
  89. *
  90. * const outputRenderTarget = gpuCompute.createRenderTarget();
  91. *
  92. * // Now use the output texture where you want:
  93. * myMaterial.uniforms.map.value = outputRenderTarget.texture;
  94. *
  95. * // And compute each frame, before rendering to screen:
  96. * gpuCompute.doRenderTarget( myFilter1, myRenderTarget );
  97. * gpuCompute.doRenderTarget( myFilter2, outputRenderTarget );
  98. * ```
  99. *
  100. * @three_import import { GPUComputationRenderer } from 'three/addons/misc/GPUComputationRenderer.js';
  101. */
  102. class GPUComputationRenderer {
  103. /**
  104. * Constructs a new GPU computation renderer.
  105. *
  106. * @param {number} sizeX - Computation problem size is always 2d: sizeX * sizeY elements.
  107. * @param {number} sizeY - Computation problem size is always 2d: sizeX * sizeY elements.
  108. * @param {WebGLRenderer} renderer - The renderer.
  109. */
  110. constructor( sizeX, sizeY, renderer ) {
  111. this.variables = [];
  112. this.currentTextureIndex = 0;
  113. let dataType = FloatType;
  114. const passThruUniforms = {
  115. passThruTexture: { value: null }
  116. };
  117. const passThruShader = createShaderMaterial( getPassThroughFragmentShader(), passThruUniforms );
  118. const quad = new FullScreenQuad( passThruShader );
  119. /**
  120. * Sets the data type of the internal textures.
  121. *
  122. * @param {(FloatType|HalfFloatType)} type - The type to set.
  123. * @return {GPUComputationRenderer} A reference to this renderer.
  124. */
  125. this.setDataType = function ( type ) {
  126. dataType = type;
  127. return this;
  128. };
  129. /**
  130. * Adds a compute variable to the renderer.
  131. *
  132. * @param {string} variableName - The variable name.
  133. * @param {string} computeFragmentShader - The compute (fragment) shader source.
  134. * @param {Texture} initialValueTexture - The initial value texture.
  135. * @return {Object} The compute variable.
  136. */
  137. this.addVariable = function ( variableName, computeFragmentShader, initialValueTexture ) {
  138. const material = this.createShaderMaterial( computeFragmentShader );
  139. const variable = {
  140. name: variableName,
  141. initialValueTexture: initialValueTexture,
  142. material: material,
  143. dependencies: null,
  144. renderTargets: [],
  145. wrapS: null,
  146. wrapT: null,
  147. minFilter: NearestFilter,
  148. magFilter: NearestFilter
  149. };
  150. this.variables.push( variable );
  151. return variable;
  152. };
  153. /**
  154. * Sets variable dependencies.
  155. *
  156. * @param {Object} variable - The compute variable.
  157. * @param {Array<Object>} dependencies - Other compute variables that represents the dependencies.
  158. */
  159. this.setVariableDependencies = function ( variable, dependencies ) {
  160. variable.dependencies = dependencies;
  161. };
  162. /**
  163. * Initializes the renderer.
  164. *
  165. * @return {?string} Returns `null` if no errors are detected. Otherwise returns the error message.
  166. */
  167. this.init = function () {
  168. if ( renderer.capabilities.maxVertexTextures === 0 ) {
  169. return 'No support for vertex shader textures.';
  170. }
  171. for ( let i = 0; i < this.variables.length; i ++ ) {
  172. const variable = this.variables[ i ];
  173. // Creates rendertargets and initialize them with input texture
  174. variable.renderTargets[ 0 ] = this.createRenderTarget( sizeX, sizeY, variable.wrapS, variable.wrapT, variable.minFilter, variable.magFilter );
  175. variable.renderTargets[ 1 ] = this.createRenderTarget( sizeX, sizeY, variable.wrapS, variable.wrapT, variable.minFilter, variable.magFilter );
  176. this.renderTexture( variable.initialValueTexture, variable.renderTargets[ 0 ] );
  177. this.renderTexture( variable.initialValueTexture, variable.renderTargets[ 1 ] );
  178. // Adds dependencies uniforms to the ShaderMaterial
  179. const material = variable.material;
  180. const uniforms = material.uniforms;
  181. if ( variable.dependencies !== null ) {
  182. for ( let d = 0; d < variable.dependencies.length; d ++ ) {
  183. const depVar = variable.dependencies[ d ];
  184. if ( depVar.name !== variable.name ) {
  185. // Checks if variable exists
  186. let found = false;
  187. for ( let j = 0; j < this.variables.length; j ++ ) {
  188. if ( depVar.name === this.variables[ j ].name ) {
  189. found = true;
  190. break;
  191. }
  192. }
  193. if ( ! found ) {
  194. return 'Variable dependency not found. Variable=' + variable.name + ', dependency=' + depVar.name;
  195. }
  196. }
  197. uniforms[ depVar.name ] = { value: null };
  198. material.fragmentShader = '\nuniform sampler2D ' + depVar.name + ';\n' + material.fragmentShader;
  199. }
  200. }
  201. }
  202. this.currentTextureIndex = 0;
  203. return null;
  204. };
  205. /**
  206. * Executes the compute. This method is usually called in the animation loop.
  207. */
  208. this.compute = function () {
  209. const currentTextureIndex = this.currentTextureIndex;
  210. const nextTextureIndex = this.currentTextureIndex === 0 ? 1 : 0;
  211. for ( let i = 0, il = this.variables.length; i < il; i ++ ) {
  212. const variable = this.variables[ i ];
  213. // Sets texture dependencies uniforms
  214. if ( variable.dependencies !== null ) {
  215. const uniforms = variable.material.uniforms;
  216. for ( let d = 0, dl = variable.dependencies.length; d < dl; d ++ ) {
  217. const depVar = variable.dependencies[ d ];
  218. uniforms[ depVar.name ].value = depVar.renderTargets[ currentTextureIndex ].texture;
  219. }
  220. }
  221. // Performs the computation for this variable
  222. this.doRenderTarget( variable.material, variable.renderTargets[ nextTextureIndex ] );
  223. }
  224. this.currentTextureIndex = nextTextureIndex;
  225. };
  226. /**
  227. * Returns the current render target for the given compute variable.
  228. *
  229. * @param {Object} variable - The compute variable.
  230. * @return {WebGLRenderTarget} The current render target.
  231. */
  232. this.getCurrentRenderTarget = function ( variable ) {
  233. return variable.renderTargets[ this.currentTextureIndex ];
  234. };
  235. /**
  236. * Returns the alternate render target for the given compute variable.
  237. *
  238. * @param {Object} variable - The compute variable.
  239. * @return {WebGLRenderTarget} The alternate render target.
  240. */
  241. this.getAlternateRenderTarget = function ( variable ) {
  242. return variable.renderTargets[ this.currentTextureIndex === 0 ? 1 : 0 ];
  243. };
  244. /**
  245. * Frees all internal resources. Call this method if you don't need the
  246. * renderer anymore.
  247. */
  248. this.dispose = function () {
  249. quad.dispose();
  250. const variables = this.variables;
  251. for ( let i = 0; i < variables.length; i ++ ) {
  252. const variable = variables[ i ];
  253. if ( variable.initialValueTexture ) variable.initialValueTexture.dispose();
  254. const renderTargets = variable.renderTargets;
  255. for ( let j = 0; j < renderTargets.length; j ++ ) {
  256. const renderTarget = renderTargets[ j ];
  257. renderTarget.dispose();
  258. }
  259. }
  260. };
  261. function addResolutionDefine( materialShader ) {
  262. materialShader.defines.resolution = 'vec2( ' + sizeX.toFixed( 1 ) + ', ' + sizeY.toFixed( 1 ) + ' )';
  263. }
  264. /**
  265. * Adds a resolution defined for the given material shader.
  266. *
  267. * @param {Object} materialShader - The material shader.
  268. */
  269. this.addResolutionDefine = addResolutionDefine;
  270. // The following functions can be used to compute things manually
  271. function createShaderMaterial( computeFragmentShader, uniforms ) {
  272. uniforms = uniforms || {};
  273. const material = new ShaderMaterial( {
  274. name: 'GPUComputationShader',
  275. uniforms: uniforms,
  276. vertexShader: getPassThroughVertexShader(),
  277. fragmentShader: computeFragmentShader
  278. } );
  279. addResolutionDefine( material );
  280. return material;
  281. }
  282. this.createShaderMaterial = createShaderMaterial;
  283. /**
  284. * Creates a new render target from the given parameters.
  285. *
  286. * @param {number} sizeXTexture - The width of the render target.
  287. * @param {number} sizeYTexture - The height of the render target.
  288. * @param {number} wrapS - The wrapS value.
  289. * @param {number} wrapT - The wrapS value.
  290. * @param {number} minFilter - The minFilter value.
  291. * @param {number} magFilter - The magFilter value.
  292. * @return {WebGLRenderTarget} The new render target.
  293. */
  294. this.createRenderTarget = function ( sizeXTexture, sizeYTexture, wrapS, wrapT, minFilter, magFilter ) {
  295. sizeXTexture = sizeXTexture || sizeX;
  296. sizeYTexture = sizeYTexture || sizeY;
  297. wrapS = wrapS || ClampToEdgeWrapping;
  298. wrapT = wrapT || ClampToEdgeWrapping;
  299. minFilter = minFilter || NearestFilter;
  300. magFilter = magFilter || NearestFilter;
  301. const renderTarget = new WebGLRenderTarget( sizeXTexture, sizeYTexture, {
  302. wrapS: wrapS,
  303. wrapT: wrapT,
  304. minFilter: minFilter,
  305. magFilter: magFilter,
  306. format: RGBAFormat,
  307. type: dataType,
  308. depthBuffer: false
  309. } );
  310. return renderTarget;
  311. };
  312. /**
  313. * Creates a new data texture.
  314. *
  315. * @return {DataTexture} The new data texture.
  316. */
  317. this.createTexture = function () {
  318. const data = new Float32Array( sizeX * sizeY * 4 );
  319. const texture = new DataTexture( data, sizeX, sizeY, RGBAFormat, FloatType );
  320. texture.needsUpdate = true;
  321. return texture;
  322. };
  323. /**
  324. * Renders the given texture into the given render target.
  325. *
  326. * @param {Texture} input - The input.
  327. * @param {WebGLRenderTarget} output - The output.
  328. */
  329. this.renderTexture = function ( input, output ) {
  330. passThruUniforms.passThruTexture.value = input;
  331. this.doRenderTarget( passThruShader, output );
  332. passThruUniforms.passThruTexture.value = null;
  333. };
  334. /**
  335. * Renders the given material into the given render target
  336. * with a full-screen pass.
  337. *
  338. * @param {Material} material - The material.
  339. * @param {WebGLRenderTarget} output - The output.
  340. */
  341. this.doRenderTarget = function ( material, output ) {
  342. const currentRenderTarget = renderer.getRenderTarget();
  343. const currentXrEnabled = renderer.xr.enabled;
  344. const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
  345. renderer.xr.enabled = false; // Avoid camera modification
  346. renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows
  347. quad.material = material;
  348. renderer.setRenderTarget( output );
  349. quad.render( renderer );
  350. quad.material = passThruShader;
  351. renderer.xr.enabled = currentXrEnabled;
  352. renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
  353. renderer.setRenderTarget( currentRenderTarget );
  354. };
  355. // Shaders
  356. function getPassThroughVertexShader() {
  357. return 'void main() {\n' +
  358. '\n' +
  359. ' gl_Position = vec4( position, 1.0 );\n' +
  360. '\n' +
  361. '}\n';
  362. }
  363. function getPassThroughFragmentShader() {
  364. return 'uniform sampler2D passThruTexture;\n' +
  365. '\n' +
  366. 'void main() {\n' +
  367. '\n' +
  368. ' vec2 uv = gl_FragCoord.xy / resolution.xy;\n' +
  369. '\n' +
  370. ' gl_FragColor = texture2D( passThruTexture, uv );\n' +
  371. '\n' +
  372. '}\n';
  373. }
  374. }
  375. }
  376. export { GPUComputationRenderer };