GPUComputationRenderer.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  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. class GPUComputationRenderer {
  101. /**
  102. * Constructs a new GPU computation renderer.
  103. *
  104. * @param {number} sizeX - Computation problem size is always 2d: sizeX * sizeY elements.
  105. * @param {number} sizeY - Computation problem size is always 2d: sizeX * sizeY elements.
  106. * @param {WebGLRenderer} renderer - The renderer.
  107. */
  108. constructor( sizeX, sizeY, renderer ) {
  109. this.variables = [];
  110. this.currentTextureIndex = 0;
  111. let dataType = FloatType;
  112. const passThruUniforms = {
  113. passThruTexture: { value: null }
  114. };
  115. const passThruShader = createShaderMaterial( getPassThroughFragmentShader(), passThruUniforms );
  116. const quad = new FullScreenQuad( passThruShader );
  117. /**
  118. * Sets the data type of the internal textures.
  119. *
  120. * @param {(FloatType|HalfFloatType)} type - The type to set.
  121. * @return {GPUComputationRenderer} A reference to this renderer.
  122. */
  123. this.setDataType = function ( type ) {
  124. dataType = type;
  125. return this;
  126. };
  127. /**
  128. * Adds a compute variable to the renderer.
  129. *
  130. * @param {string} variableName - The variable name.
  131. * @param {string} computeFragmentShader - The compute (fragment) shader source.
  132. * @param {Texture} initialValueTexture - The initial value texture.
  133. * @return {Object} The compute variable.
  134. */
  135. this.addVariable = function ( variableName, computeFragmentShader, initialValueTexture ) {
  136. const material = this.createShaderMaterial( computeFragmentShader );
  137. const variable = {
  138. name: variableName,
  139. initialValueTexture: initialValueTexture,
  140. material: material,
  141. dependencies: null,
  142. renderTargets: [],
  143. wrapS: null,
  144. wrapT: null,
  145. minFilter: NearestFilter,
  146. magFilter: NearestFilter
  147. };
  148. this.variables.push( variable );
  149. return variable;
  150. };
  151. /**
  152. * Sets variable dependencies.
  153. *
  154. * @param {Object} variable - The compute variable.
  155. * @param {Array<Object>} dependencies - Other compute variables that represents the dependencies.
  156. */
  157. this.setVariableDependencies = function ( variable, dependencies ) {
  158. variable.dependencies = dependencies;
  159. };
  160. /**
  161. * Initializes the renderer.
  162. *
  163. * @return {?string} Returns `null` if no errors are detected. Otherwise returns the error message.
  164. */
  165. this.init = function () {
  166. if ( renderer.capabilities.maxVertexTextures === 0 ) {
  167. return 'No support for vertex shader textures.';
  168. }
  169. for ( let i = 0; i < this.variables.length; i ++ ) {
  170. const variable = this.variables[ i ];
  171. // Creates rendertargets and initialize them with input texture
  172. variable.renderTargets[ 0 ] = this.createRenderTarget( sizeX, sizeY, variable.wrapS, variable.wrapT, variable.minFilter, variable.magFilter );
  173. variable.renderTargets[ 1 ] = this.createRenderTarget( sizeX, sizeY, variable.wrapS, variable.wrapT, variable.minFilter, variable.magFilter );
  174. this.renderTexture( variable.initialValueTexture, variable.renderTargets[ 0 ] );
  175. this.renderTexture( variable.initialValueTexture, variable.renderTargets[ 1 ] );
  176. // Adds dependencies uniforms to the ShaderMaterial
  177. const material = variable.material;
  178. const uniforms = material.uniforms;
  179. if ( variable.dependencies !== null ) {
  180. for ( let d = 0; d < variable.dependencies.length; d ++ ) {
  181. const depVar = variable.dependencies[ d ];
  182. if ( depVar.name !== variable.name ) {
  183. // Checks if variable exists
  184. let found = false;
  185. for ( let j = 0; j < this.variables.length; j ++ ) {
  186. if ( depVar.name === this.variables[ j ].name ) {
  187. found = true;
  188. break;
  189. }
  190. }
  191. if ( ! found ) {
  192. return 'Variable dependency not found. Variable=' + variable.name + ', dependency=' + depVar.name;
  193. }
  194. }
  195. uniforms[ depVar.name ] = { value: null };
  196. material.fragmentShader = '\nuniform sampler2D ' + depVar.name + ';\n' + material.fragmentShader;
  197. }
  198. }
  199. }
  200. this.currentTextureIndex = 0;
  201. return null;
  202. };
  203. /**
  204. * Executes the compute. This method is usually called in the animation loop.
  205. */
  206. this.compute = function () {
  207. const currentTextureIndex = this.currentTextureIndex;
  208. const nextTextureIndex = this.currentTextureIndex === 0 ? 1 : 0;
  209. for ( let i = 0, il = this.variables.length; i < il; i ++ ) {
  210. const variable = this.variables[ i ];
  211. // Sets texture dependencies uniforms
  212. if ( variable.dependencies !== null ) {
  213. const uniforms = variable.material.uniforms;
  214. for ( let d = 0, dl = variable.dependencies.length; d < dl; d ++ ) {
  215. const depVar = variable.dependencies[ d ];
  216. uniforms[ depVar.name ].value = depVar.renderTargets[ currentTextureIndex ].texture;
  217. }
  218. }
  219. // Performs the computation for this variable
  220. this.doRenderTarget( variable.material, variable.renderTargets[ nextTextureIndex ] );
  221. }
  222. this.currentTextureIndex = nextTextureIndex;
  223. };
  224. /**
  225. * Returns the current render target for the given compute variable.
  226. *
  227. * @param {Object} variable - The compute variable.
  228. * @return {WebGLRenderTarget} The current render target.
  229. */
  230. this.getCurrentRenderTarget = function ( variable ) {
  231. return variable.renderTargets[ this.currentTextureIndex ];
  232. };
  233. /**
  234. * Returns the alternate render target for the given compute variable.
  235. *
  236. * @param {Object} variable - The compute variable.
  237. * @return {WebGLRenderTarget} The alternate render target.
  238. */
  239. this.getAlternateRenderTarget = function ( variable ) {
  240. return variable.renderTargets[ this.currentTextureIndex === 0 ? 1 : 0 ];
  241. };
  242. /**
  243. * Frees all internal resources. Call this method if you don't need the
  244. * renderer anymore.
  245. */
  246. this.dispose = function () {
  247. quad.dispose();
  248. const variables = this.variables;
  249. for ( let i = 0; i < variables.length; i ++ ) {
  250. const variable = variables[ i ];
  251. if ( variable.initialValueTexture ) variable.initialValueTexture.dispose();
  252. const renderTargets = variable.renderTargets;
  253. for ( let j = 0; j < renderTargets.length; j ++ ) {
  254. const renderTarget = renderTargets[ j ];
  255. renderTarget.dispose();
  256. }
  257. }
  258. };
  259. function addResolutionDefine( materialShader ) {
  260. materialShader.defines.resolution = 'vec2( ' + sizeX.toFixed( 1 ) + ', ' + sizeY.toFixed( 1 ) + ' )';
  261. }
  262. /**
  263. * Adds a resolution defined for the given material shader.
  264. *
  265. * @param {Object} materialShader - The material shader.
  266. */
  267. this.addResolutionDefine = addResolutionDefine;
  268. // The following functions can be used to compute things manually
  269. function createShaderMaterial( computeFragmentShader, uniforms ) {
  270. uniforms = uniforms || {};
  271. const material = new ShaderMaterial( {
  272. name: 'GPUComputationShader',
  273. uniforms: uniforms,
  274. vertexShader: getPassThroughVertexShader(),
  275. fragmentShader: computeFragmentShader
  276. } );
  277. addResolutionDefine( material );
  278. return material;
  279. }
  280. this.createShaderMaterial = createShaderMaterial;
  281. /**
  282. * Creates a new render target from the given parameters.
  283. *
  284. * @param {number} sizeXTexture - The width of the render target.
  285. * @param {number} sizeYTexture - The height of the render target.
  286. * @param {number} wrapS - The wrapS value.
  287. * @param {number} wrapT - The wrapS value.
  288. * @param {number} minFilter - The minFilter value.
  289. * @param {number} magFilter - The magFilter value.
  290. * @return {WebGLRenderTarget} The new render target.
  291. */
  292. this.createRenderTarget = function ( sizeXTexture, sizeYTexture, wrapS, wrapT, minFilter, magFilter ) {
  293. sizeXTexture = sizeXTexture || sizeX;
  294. sizeYTexture = sizeYTexture || sizeY;
  295. wrapS = wrapS || ClampToEdgeWrapping;
  296. wrapT = wrapT || ClampToEdgeWrapping;
  297. minFilter = minFilter || NearestFilter;
  298. magFilter = magFilter || NearestFilter;
  299. const renderTarget = new WebGLRenderTarget( sizeXTexture, sizeYTexture, {
  300. wrapS: wrapS,
  301. wrapT: wrapT,
  302. minFilter: minFilter,
  303. magFilter: magFilter,
  304. format: RGBAFormat,
  305. type: dataType,
  306. depthBuffer: false
  307. } );
  308. return renderTarget;
  309. };
  310. /**
  311. * Creates a new data texture.
  312. *
  313. * @return {DataTexture} The new data texture.
  314. */
  315. this.createTexture = function () {
  316. const data = new Float32Array( sizeX * sizeY * 4 );
  317. const texture = new DataTexture( data, sizeX, sizeY, RGBAFormat, FloatType );
  318. texture.needsUpdate = true;
  319. return texture;
  320. };
  321. /**
  322. * Renders the given texture into the given render target.
  323. *
  324. * @param {Texture} input - The input.
  325. * @param {WebGLRenderTarget} output - The output.
  326. */
  327. this.renderTexture = function ( input, output ) {
  328. passThruUniforms.passThruTexture.value = input;
  329. this.doRenderTarget( passThruShader, output );
  330. passThruUniforms.passThruTexture.value = null;
  331. };
  332. /**
  333. * Renders the given material into the given render target
  334. * with a full-screen pass.
  335. *
  336. * @param {Material} material - The material.
  337. * @param {WebGLRenderTarget} output - The output.
  338. */
  339. this.doRenderTarget = function ( material, output ) {
  340. const currentRenderTarget = renderer.getRenderTarget();
  341. const currentXrEnabled = renderer.xr.enabled;
  342. const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
  343. renderer.xr.enabled = false; // Avoid camera modification
  344. renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows
  345. quad.material = material;
  346. renderer.setRenderTarget( output );
  347. quad.render( renderer );
  348. quad.material = passThruShader;
  349. renderer.xr.enabled = currentXrEnabled;
  350. renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
  351. renderer.setRenderTarget( currentRenderTarget );
  352. };
  353. // Shaders
  354. function getPassThroughVertexShader() {
  355. return 'void main() {\n' +
  356. '\n' +
  357. ' gl_Position = vec4( position, 1.0 );\n' +
  358. '\n' +
  359. '}\n';
  360. }
  361. function getPassThroughFragmentShader() {
  362. return 'uniform sampler2D passThruTexture;\n' +
  363. '\n' +
  364. 'void main() {\n' +
  365. '\n' +
  366. ' vec2 uv = gl_FragCoord.xy / resolution.xy;\n' +
  367. '\n' +
  368. ' gl_FragColor = texture2D( passThruTexture, uv );\n' +
  369. '\n' +
  370. '}\n';
  371. }
  372. }
  373. }
  374. export { GPUComputationRenderer };
粤ICP备19079148号