后期处理通道

3D扩展允许给layer设置一个后期处理通道,所谓后期处理通道,是指将renderer输出的内容放到FrameBuffer中依次通过若干个后期处理的Program来完成渲染。

实际上我们可以通过多个RenderTarget来实现这个功能,后期处理通道简化了实现的步骤。

这样可以实现很多有趣的效果。

  1. const fragment = /* glsl */ `
  2. precision highp float;
  3. uniform sampler2D tMap;
  4. uniform sampler2D tFluid;
  5. uniform float uTime;
  6. varying vec2 vUv;
  7. void main() {
  8. vec3 fluid = texture2D(tFluid, vUv).rgb;
  9. vec2 uv = vUv - fluid.rg * 0.0002;
  10. gl_FragColor = mix( texture2D(tMap, uv), vec4(fluid * 0.1 + 0.5, 1), step(0.5, vUv.x) ) ;
  11. // Oscillate between fluid values and the distorted scene
  12. // gl_FragColor = mix(texture2D(tMap, uv), vec4(fluid * 0.1 + 0.5, 1), smoothstep(0.0, 0.7, sin(uTime)));
  13. }
  14. `;
  15. const baseVertex = /* glsl */ `
  16. precision highp float;
  17. attribute vec2 position;
  18. attribute vec2 uv;
  19. varying vec2 vUv;
  20. varying vec2 vL;
  21. varying vec2 vR;
  22. varying vec2 vT;
  23. varying vec2 vB;
  24. uniform vec2 texelSize;
  25. void main () {
  26. vUv = uv;
  27. vL = vUv - vec2(texelSize.x, 0.0);
  28. vR = vUv + vec2(texelSize.x, 0.0);
  29. vT = vUv + vec2(0.0, texelSize.y);
  30. vB = vUv - vec2(0.0, texelSize.y);
  31. gl_Position = vec4(position, 0, 1);
  32. }
  33. `;
  34. const clearShader = /* glsl */ `
  35. precision mediump float;
  36. precision mediump sampler2D;
  37. varying highp vec2 vUv;
  38. uniform sampler2D uTexture;
  39. uniform float value;
  40. void main () {
  41. gl_FragColor = value * texture2D(uTexture, vUv);
  42. }
  43. `;
  44. const splatShader = /* glsl */ `
  45. precision highp float;
  46. precision highp sampler2D;
  47. varying vec2 vUv;
  48. uniform sampler2D uTarget;
  49. uniform float aspectRatio;
  50. uniform vec3 color;
  51. uniform vec2 point;
  52. uniform float radius;
  53. void main () {
  54. vec2 p = vUv - point.xy;
  55. p.x *= aspectRatio;
  56. vec3 splat = exp(-dot(p, p) / radius) * color;
  57. vec3 base = texture2D(uTarget, vUv).xyz;
  58. gl_FragColor = vec4(base + splat, 1.0);
  59. }
  60. `;
  61. const advectionManualFilteringShader = /* glsl */ `
  62. precision highp float;
  63. precision highp sampler2D;
  64. varying vec2 vUv;
  65. uniform sampler2D uVelocity;
  66. uniform sampler2D uSource;
  67. uniform vec2 texelSize;
  68. uniform vec2 dyeTexelSize;
  69. uniform float dt;
  70. uniform float dissipation;
  71. vec4 bilerp (sampler2D sam, vec2 uv, vec2 tsize) {
  72. vec2 st = uv / tsize - 0.5;
  73. vec2 iuv = floor(st);
  74. vec2 fuv = fract(st);
  75. vec4 a = texture2D(sam, (iuv + vec2(0.5, 0.5)) * tsize);
  76. vec4 b = texture2D(sam, (iuv + vec2(1.5, 0.5)) * tsize);
  77. vec4 c = texture2D(sam, (iuv + vec2(0.5, 1.5)) * tsize);
  78. vec4 d = texture2D(sam, (iuv + vec2(1.5, 1.5)) * tsize);
  79. return mix(mix(a, b, fuv.x), mix(c, d, fuv.x), fuv.y);
  80. }
  81. void main () {
  82. vec2 coord = vUv - dt * bilerp(uVelocity, vUv, texelSize).xy * texelSize;
  83. gl_FragColor = dissipation * bilerp(uSource, coord, dyeTexelSize);
  84. gl_FragColor.a = 1.0;
  85. }
  86. `;
  87. const advectionShader = /* glsl */ `
  88. precision highp float;
  89. precision highp sampler2D;
  90. varying vec2 vUv;
  91. uniform sampler2D uVelocity;
  92. uniform sampler2D uSource;
  93. uniform vec2 texelSize;
  94. uniform float dt;
  95. uniform float dissipation;
  96. void main () {
  97. vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize;
  98. gl_FragColor = dissipation * texture2D(uSource, coord);
  99. gl_FragColor.a = 1.0;
  100. }
  101. `;
  102. const divergenceShader = /* glsl */ `
  103. precision mediump float;
  104. precision mediump sampler2D;
  105. varying highp vec2 vUv;
  106. varying highp vec2 vL;
  107. varying highp vec2 vR;
  108. varying highp vec2 vT;
  109. varying highp vec2 vB;
  110. uniform sampler2D uVelocity;
  111. void main () {
  112. float L = texture2D(uVelocity, vL).x;
  113. float R = texture2D(uVelocity, vR).x;
  114. float T = texture2D(uVelocity, vT).y;
  115. float B = texture2D(uVelocity, vB).y;
  116. vec2 C = texture2D(uVelocity, vUv).xy;
  117. if (vL.x < 0.0) { L = -C.x; }
  118. if (vR.x > 1.0) { R = -C.x; }
  119. if (vT.y > 1.0) { T = -C.y; }
  120. if (vB.y < 0.0) { B = -C.y; }
  121. float div = 0.5 * (R - L + T - B);
  122. gl_FragColor = vec4(div, 0.0, 0.0, 1.0);
  123. }
  124. `;
  125. const curlShader = /* glsl */ `
  126. precision mediump float;
  127. precision mediump sampler2D;
  128. varying highp vec2 vUv;
  129. varying highp vec2 vL;
  130. varying highp vec2 vR;
  131. varying highp vec2 vT;
  132. varying highp vec2 vB;
  133. uniform sampler2D uVelocity;
  134. void main () {
  135. float L = texture2D(uVelocity, vL).y;
  136. float R = texture2D(uVelocity, vR).y;
  137. float T = texture2D(uVelocity, vT).x;
  138. float B = texture2D(uVelocity, vB).x;
  139. float vorticity = R - L - T + B;
  140. gl_FragColor = vec4(0.5 * vorticity, 0.0, 0.0, 1.0);
  141. }
  142. `;
  143. const vorticityShader = /* glsl */ `
  144. precision highp float;
  145. precision highp sampler2D;
  146. varying vec2 vUv;
  147. varying vec2 vL;
  148. varying vec2 vR;
  149. varying vec2 vT;
  150. varying vec2 vB;
  151. uniform sampler2D uVelocity;
  152. uniform sampler2D uCurl;
  153. uniform float curl;
  154. uniform float dt;
  155. void main () {
  156. float L = texture2D(uCurl, vL).x;
  157. float R = texture2D(uCurl, vR).x;
  158. float T = texture2D(uCurl, vT).x;
  159. float B = texture2D(uCurl, vB).x;
  160. float C = texture2D(uCurl, vUv).x;
  161. vec2 force = 0.5 * vec2(abs(T) - abs(B), abs(R) - abs(L));
  162. force /= length(force) + 0.0001;
  163. force *= curl * C;
  164. force.y *= -1.0;
  165. vec2 vel = texture2D(uVelocity, vUv).xy;
  166. gl_FragColor = vec4(vel + force * dt, 0.0, 1.0);
  167. }
  168. `;
  169. const pressureShader = /* glsl */ `
  170. precision mediump float;
  171. precision mediump sampler2D;
  172. varying highp vec2 vUv;
  173. varying highp vec2 vL;
  174. varying highp vec2 vR;
  175. varying highp vec2 vT;
  176. varying highp vec2 vB;
  177. uniform sampler2D uPressure;
  178. uniform sampler2D uDivergence;
  179. void main () {
  180. float L = texture2D(uPressure, vL).x;
  181. float R = texture2D(uPressure, vR).x;
  182. float T = texture2D(uPressure, vT).x;
  183. float B = texture2D(uPressure, vB).x;
  184. float C = texture2D(uPressure, vUv).x;
  185. float divergence = texture2D(uDivergence, vUv).x;
  186. float pressure = (L + R + B + T - divergence) * 0.25;
  187. gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0);
  188. }
  189. `;
  190. const gradientSubtractShader = /* glsl */ `
  191. precision mediump float;
  192. precision mediump sampler2D;
  193. varying highp vec2 vUv;
  194. varying highp vec2 vL;
  195. varying highp vec2 vR;
  196. varying highp vec2 vT;
  197. varying highp vec2 vB;
  198. uniform sampler2D uPressure;
  199. uniform sampler2D uVelocity;
  200. void main () {
  201. float L = texture2D(uPressure, vL).x;
  202. float R = texture2D(uPressure, vR).x;
  203. float T = texture2D(uPressure, vT).x;
  204. float B = texture2D(uPressure, vB).x;
  205. vec2 velocity = texture2D(uVelocity, vUv).xy;
  206. velocity.xy -= vec2(R - L, T - B);
  207. gl_FragColor = vec4(velocity, 0.0, 1.0);
  208. }
  209. `;
  210. const {Scene} = spritejs;
  211. const {RenderTarget, Mesh3d, Geometry, Cube, shaders} = spritejs.ext3d;
  212. const container = document.getElementById('container');
  213. const scene = new Scene({
  214. container,
  215. displayRatio: 2,
  216. });
  217. const layer = scene.layer3d('fglayer', {
  218. // autoRender: false,
  219. camera: {
  220. fov: 35,
  221. },
  222. post: true,
  223. });
  224. layer.camera.attributes.pos = [0, 1, 5];
  225. layer.camera.lookAt([0, 0, 0]);
  226. const post = layer.post;
  227. // Helper functions for larger device support
  228. function getSupportedFormat(gl, internalFormat, format, type) {
  229. if(!supportRenderTextureFormat(gl, internalFormat, format, type)) {
  230. switch (internalFormat) {
  231. case gl.R16F:
  232. return getSupportedFormat(gl, gl.RG16F, gl.RG, type);
  233. case gl.RG16F:
  234. return getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, type);
  235. default:
  236. return null;
  237. }
  238. }
  239. return {internalFormat, format};
  240. }
  241. function supportRenderTextureFormat(gl, internalFormat, format, type) {
  242. const texture = gl.createTexture();
  243. gl.bindTexture(gl.TEXTURE_2D, texture);
  244. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  245. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  246. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  247. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  248. gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, 4, 4, 0, format, type, null);
  249. const fbo = gl.createFramebuffer();
  250. gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
  251. gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
  252. const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
  253. if(status !== gl.FRAMEBUFFER_COMPLETE) return false;
  254. return true;
  255. }
  256. // Resolution of simulation
  257. const simRes = 128;
  258. const dyeRes = 512;
  259. // Main inputs to control look and feel of fluid
  260. const iterations = 3;
  261. const densityDissipation = 0.97;
  262. const velocityDissipation = 0.98;
  263. const pressureDissipation = 0.8;
  264. const curlStrength = 20;
  265. const radius = 0.2;
  266. // Common uniform
  267. const texelSize = {value: [1 / simRes, 1 / simRes]};
  268. const renderer = layer.renderer;
  269. const gl = layer.gl;
  270. // Get supported formats and types for FBOs
  271. const supportLinearFiltering = renderer.extensions[`OES_texture_${renderer.isWebgl2 ? '' : 'half_'}float_linear`];
  272. const halfFloat = renderer.isWebgl2 ? gl.HALF_FLOAT // eslint-disable-line no-nested-ternary
  273. : renderer.extensions.OES_texture_half_float ? renderer.extensions.OES_texture_half_float.HALF_FLOAT_OES
  274. : gl.UNSIGNED_BYTE;
  275. const filtering = supportLinearFiltering ? gl.LINEAR : gl.NEAREST;
  276. let rgba,
  277. rg,
  278. r;
  279. if(renderer.isWebgl2) {
  280. rgba = getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, halfFloat);
  281. rg = getSupportedFormat(gl, gl.RG16F, gl.RG, halfFloat);
  282. r = getSupportedFormat(gl, gl.R16F, gl.RED, halfFloat);
  283. } else {
  284. rgba = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloat);
  285. rg = rgba;
  286. r = rgba;
  287. }
  288. // Create fluid simulation FBOs
  289. const density = new RenderTarget(gl, {
  290. width: dyeRes,
  291. height: dyeRes,
  292. type: halfFloat,
  293. format: rgba.format,
  294. internalFormat: rgba.internalFormat,
  295. minFilter: filtering,
  296. depth: false,
  297. buffer: true,
  298. });
  299. const velocity = new RenderTarget(gl, {
  300. width: simRes,
  301. height: simRes,
  302. type: halfFloat,
  303. format: rg.format,
  304. internalFormat: rg.internalFormat,
  305. minFilter: filtering,
  306. depth: false,
  307. buffer: true,
  308. });
  309. const pressure = new RenderTarget(gl, {
  310. width: simRes,
  311. height: simRes,
  312. type: halfFloat,
  313. format: r.format,
  314. internalFormat: r.internalFormat,
  315. minFilter: gl.NEAREST,
  316. depth: false,
  317. buffer: true,
  318. });
  319. const divergence = new RenderTarget(gl, {
  320. width: simRes,
  321. height: simRes,
  322. type: halfFloat,
  323. format: r.format,
  324. internalFormat: r.internalFormat,
  325. minFilter: gl.NEAREST,
  326. depth: false,
  327. });
  328. const curl = new RenderTarget(gl, {
  329. width: simRes,
  330. height: simRes,
  331. type: halfFloat,
  332. format: r.format,
  333. internalFormat: r.internalFormat,
  334. minFilter: gl.NEAREST,
  335. depth: false,
  336. });
  337. const triangle = new Geometry(gl, {
  338. position: {size: 2, data: new Float32Array([-1, -1, 3, -1, -1, 3])},
  339. uv: {size: 2, data: new Float32Array([0, 0, 2, 0, 0, 2])},
  340. });
  341. // Create fluid simulation programs
  342. const clearProgram = new Mesh3d(layer.createProgram({
  343. vertex: baseVertex,
  344. fragment: clearShader,
  345. uniforms: {
  346. texelSize,
  347. uTexture: {value: null},
  348. value: {value: pressureDissipation},
  349. },
  350. depthTest: false,
  351. depthWrite: false,
  352. }), {
  353. model: triangle,
  354. });
  355. const splatProgram = new Mesh3d(layer.createProgram({
  356. vertex: baseVertex,
  357. fragment: splatShader,
  358. uniforms: {
  359. texelSize,
  360. uTarget: {value: null},
  361. aspectRatio: {value: 1},
  362. color: {value: [0, 0, 0]},
  363. point: {value: [0, 0]},
  364. radius: {value: 1},
  365. },
  366. depthTest: false,
  367. depthWrite: false,
  368. }), {
  369. model: triangle,
  370. });
  371. const advectionProgram = new Mesh3d(layer.createProgram({
  372. vertex: baseVertex,
  373. fragment: supportLinearFiltering ? advectionShader : advectionManualFilteringShader,
  374. uniforms: {
  375. texelSize,
  376. dyeTexelSize: {value: [1 / dyeRes, 1 / dyeRes]},
  377. uVelocity: {value: null},
  378. uSource: {value: null},
  379. dt: {value: 0.016},
  380. dissipation: {value: 1.0},
  381. },
  382. depthTest: false,
  383. depthWrite: false,
  384. }), {
  385. model: triangle,
  386. });
  387. const divergenceProgram = new Mesh3d(layer.createProgram({
  388. vertex: baseVertex,
  389. fragment: divergenceShader,
  390. uniforms: {
  391. texelSize,
  392. uVelocity: {value: null},
  393. },
  394. depthTest: false,
  395. depthWrite: false,
  396. }), {
  397. model: triangle,
  398. });
  399. const curlProgram = new Mesh3d(layer.createProgram({
  400. vertex: baseVertex,
  401. fragment: curlShader,
  402. uniforms: {
  403. texelSize,
  404. uVelocity: {value: null},
  405. },
  406. depthTest: false,
  407. depthWrite: false,
  408. }), {
  409. model: triangle,
  410. });
  411. const vorticityProgram = new Mesh3d(layer.createProgram({
  412. vertex: baseVertex,
  413. fragment: vorticityShader,
  414. uniforms: {
  415. texelSize,
  416. uVelocity: {value: null},
  417. uCurl: {value: null},
  418. curl: {value: curlStrength},
  419. dt: {value: 0.016},
  420. },
  421. depthTest: false,
  422. depthWrite: false,
  423. }), {
  424. model: triangle,
  425. });
  426. const pressureProgram = new Mesh3d(layer.createProgram({
  427. vertex: baseVertex,
  428. fragment: pressureShader,
  429. uniforms: {
  430. texelSize,
  431. uPressure: {value: null},
  432. uDivergence: {value: null},
  433. },
  434. depthTest: false,
  435. depthWrite: false,
  436. }), {
  437. model: triangle,
  438. });
  439. const gradienSubtractProgram = new Mesh3d(layer.createProgram({
  440. vertex: baseVertex,
  441. fragment: gradientSubtractShader,
  442. uniforms: {
  443. texelSize,
  444. uPressure: {value: null},
  445. uVelocity: {value: null},
  446. },
  447. depthTest: false,
  448. depthWrite: false,
  449. }), {
  450. model: triangle,
  451. });
  452. const splats = [];
  453. // Create handlers to get mouse position and velocity
  454. const isTouchCapable = 'ontouchstart' in window;
  455. if(isTouchCapable) {
  456. window.addEventListener('touchstart', updateMouse, false);
  457. window.addEventListener('touchmove', updateMouse, false);
  458. } else {
  459. window.addEventListener('mousemove', updateMouse, false);
  460. }
  461. const lastMouse = {x: 0, y: 0};
  462. function updateMouse(e) {
  463. if(e.changedTouches && e.changedTouches.length) {
  464. e.x = e.changedTouches[0].pageX;
  465. e.y = e.changedTouches[0].pageY;
  466. }
  467. if(e.x === undefined) {
  468. e.x = e.pageX;
  469. e.y = e.pageY;
  470. }
  471. if(!lastMouse.isInit) {
  472. lastMouse.isInit = true;
  473. // First input
  474. lastMouse.x = e.x;
  475. lastMouse.y = e.y;
  476. }
  477. const deltaX = e.x - lastMouse.x;
  478. const deltaY = e.y - lastMouse.y;
  479. lastMouse.x = e.x;
  480. lastMouse.y = e.y;
  481. // console.log(deltaX, deltaY);
  482. // Add if the mouse is moving
  483. if(Math.abs(deltaX) || Math.abs(deltaY)) {
  484. splats.push({
  485. // Get mouse value in 0 to 1 range, with y flipped
  486. x: e.x / renderer.width,
  487. y: 1.0 - e.y / renderer.height,
  488. dx: deltaX * 5.0,
  489. dy: deltaY * -5.0,
  490. });
  491. }
  492. }
  493. // Function to draw number of interactions onto input render target
  494. function splat({x, y, dx, dy}) {
  495. splatProgram.program.uniforms.uTarget.value = velocity.texture;
  496. splatProgram.program.uniforms.aspectRatio.value = renderer.width / renderer.height;
  497. splatProgram.program.uniforms.point.value = [x, y];
  498. splatProgram.program.uniforms.color.value = [dx, dy, 1.0];
  499. splatProgram.program.uniforms.radius.value = radius / 100.0;
  500. layer.renderTarget(velocity, {
  501. root: splatProgram,
  502. sort: false,
  503. update: false,
  504. });
  505. velocity.swap();
  506. splatProgram.program.uniforms.uTarget.value = density.texture;
  507. layer.renderTarget(density, {
  508. root: splatProgram,
  509. sort: false,
  510. update: false,
  511. });
  512. density.swap();
  513. }
  514. const normalProgram = layer.createProgram({
  515. ...shaders.NORMAL,
  516. });
  517. const mesh = new Cube(normalProgram);
  518. layer.append(mesh);
  519. for(let i = 0; i < 20; i++) {
  520. const m = mesh.cloneNode();
  521. m.attributes.pos = [
  522. Math.random() * 3 - 1.5,
  523. Math.random() * 3 - 1.5,
  524. Math.random() * 3 - 1.5,
  525. ];
  526. m.attributes.rotate = [
  527. Math.random() * 360 - 180,
  528. Math.random() * 360 - 180,
  529. 0,
  530. ];
  531. m.attributes.scale = Math.random() * 0.5 + 0.1;
  532. mesh.append(m);
  533. }
  534. const pass = post.addPass({
  535. fragment,
  536. uniforms: {
  537. tFluid: {value: null},
  538. uTime: {value: 0},
  539. },
  540. });
  541. layer.bindTime(pass);
  542. // requestAnimationFrame(update);
  543. layer.tick(() => {
  544. // Perform all of the fluid simulation renders
  545. // No need to clear during sim, saving a number of GL calls.
  546. renderer.autoClear = false;
  547. // Render all of the inputs since last frame
  548. for(let i = splats.length - 1; i >= 0; i--) {
  549. const s = splats.splice(i, 1)[0];
  550. splat(s);
  551. }
  552. curlProgram.program.uniforms.uVelocity.value = velocity.texture;
  553. layer.renderTarget(curl, {
  554. root: curlProgram,
  555. sort: false,
  556. update: false,
  557. });
  558. vorticityProgram.program.uniforms.uVelocity.value = velocity.texture;
  559. vorticityProgram.program.uniforms.uCurl.value = curl.texture;
  560. layer.renderTarget(velocity, {
  561. root: vorticityProgram,
  562. sort: false,
  563. update: false,
  564. });
  565. velocity.swap();
  566. divergenceProgram.program.uniforms.uVelocity.value = velocity.texture;
  567. layer.renderTarget(divergence, {
  568. root: divergenceProgram,
  569. sort: false,
  570. update: false,
  571. });
  572. clearProgram.program.uniforms.uTexture.value = pressure.texture;
  573. clearProgram.program.uniforms.value.value = pressureDissipation;
  574. layer.renderTarget(pressure, {
  575. root: clearProgram,
  576. sort: false,
  577. update: false,
  578. });
  579. pressure.swap();
  580. pressureProgram.program.uniforms.uDivergence.value = divergence.texture;
  581. for(let i = 0; i < iterations; i++) {
  582. pressureProgram.program.uniforms.uPressure.value = pressure.texture;
  583. layer.renderTarget(pressure, {
  584. root: pressureProgram,
  585. sort: false,
  586. update: false,
  587. });
  588. pressure.swap();
  589. }
  590. gradienSubtractProgram.program.uniforms.uPressure.value = pressure.texture;
  591. gradienSubtractProgram.program.uniforms.uVelocity.value = velocity.texture;
  592. layer.renderTarget(velocity, {
  593. root: gradienSubtractProgram,
  594. sort: false,
  595. update: false,
  596. });
  597. velocity.swap();
  598. advectionProgram.program.uniforms.dyeTexelSize.value = 1 / simRes;
  599. advectionProgram.program.uniforms.uVelocity.value = velocity.texture;
  600. advectionProgram.program.uniforms.uSource.value = velocity.texture;
  601. advectionProgram.program.uniforms.dissipation.value = velocityDissipation;
  602. layer.renderTarget(velocity, {
  603. root: advectionProgram,
  604. sort: false,
  605. update: false,
  606. });
  607. velocity.swap();
  608. advectionProgram.program.uniforms.dyeTexelSize.value = 1 / dyeRes;
  609. advectionProgram.program.uniforms.uVelocity.value = velocity.texture;
  610. advectionProgram.program.uniforms.uSource.value = density.texture;
  611. advectionProgram.program.uniforms.dissipation.value = densityDissipation;
  612. layer.renderTarget(density, {
  613. root: advectionProgram,
  614. sort: false,
  615. update: false,
  616. });
  617. density.swap();
  618. // Set clear back to default
  619. renderer.autoClear = true;
  620. // Update post pass uniform with the simulation output
  621. pass.uniforms.tFluid.value = density.texture;
  622. mesh.attributes.rotateY -= 0.15;
  623. mesh.attributes.rotateX -= 0.3;
  624. });