main.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js';
  2. import {OrbitControls} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/controls/OrbitControls.js';
  3. const _VS = `
  4. uniform float pointMultiplier;
  5. attribute float size;
  6. attribute float angle;
  7. attribute float blend;
  8. attribute vec4 colour;
  9. varying vec4 vColour;
  10. varying vec2 vAngle;
  11. varying float vBlend;
  12. void main() {
  13. vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
  14. gl_Position = projectionMatrix * mvPosition;
  15. gl_PointSize = size * pointMultiplier / gl_Position.w;
  16. vAngle = vec2(cos(angle), sin(angle));
  17. vColour = colour;
  18. vBlend = blend;
  19. }`;
  20. const _FS = `
  21. uniform sampler2D diffuseTexture;
  22. varying vec4 vColour;
  23. varying vec2 vAngle;
  24. varying float vBlend;
  25. void main() {
  26. vec2 coords = (gl_PointCoord - 0.5) * mat2(vAngle.x, vAngle.y, -vAngle.y, vAngle.x) + 0.5;
  27. gl_FragColor = texture2D(diffuseTexture, coords) * vColour;
  28. gl_FragColor.xyz *= gl_FragColor.w;
  29. gl_FragColor.w *= vBlend;
  30. }`;
  31. const _VS_2 = `
  32. uniform float pointMultiplier;
  33. attribute float size;
  34. attribute float angle;
  35. attribute vec4 colour;
  36. varying vec4 vColour;
  37. varying vec2 vAngle;
  38. void main() {
  39. vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
  40. gl_Position = projectionMatrix * mvPosition;
  41. gl_PointSize = size * pointMultiplier / gl_Position.w;
  42. vAngle = vec2(cos(angle), sin(angle));
  43. vColour = colour;
  44. }`;
  45. const _FS_2 = `
  46. uniform sampler2D diffuseTexture;
  47. varying vec4 vColour;
  48. varying vec2 vAngle;
  49. void main() {
  50. vec2 coords = (gl_PointCoord - 0.5) * mat2(vAngle.x, vAngle.y, -vAngle.y, vAngle.x) + 0.5;
  51. gl_FragColor = texture2D(diffuseTexture, coords) * vColour;
  52. }`;
  53. class LinearSpline {
  54. constructor(lerp) {
  55. this._points = [];
  56. this._lerp = lerp;
  57. }
  58. AddPoint(t, d) {
  59. this._points.push([t, d]);
  60. }
  61. Get(t) {
  62. let p1 = 0;
  63. for (let i = 0; i < this._points.length; i++) {
  64. if (this._points[i][0] >= t) {
  65. break;
  66. }
  67. p1 = i;
  68. }
  69. const p2 = Math.min(this._points.length - 1, p1 + 1);
  70. if (p1 == p2) {
  71. return this._points[p1][1];
  72. }
  73. return this._lerp(
  74. (t - this._points[p1][0]) / (
  75. this._points[p2][0] - this._points[p1][0]),
  76. this._points[p1][1], this._points[p2][1]);
  77. }
  78. }
  79. class ParticleSystem {
  80. constructor(params) {
  81. const uniforms = {
  82. diffuseTexture: {
  83. value: new THREE.TextureLoader().load('./resources/fire.png')
  84. },
  85. pointMultiplier: {
  86. value: window.innerHeight / (2.0 * Math.tan(0.5 * 60.0 * Math.PI / 180.0))
  87. }
  88. };
  89. this._material = new THREE.ShaderMaterial({
  90. uniforms: uniforms,
  91. vertexShader: _VS,
  92. fragmentShader: _FS,
  93. blending: THREE.CustomBlending,
  94. blendEquation: THREE.AddEquation,
  95. blendSrc: THREE.OneFactor,
  96. blendDst: THREE.OneMinusSrcAlphaFactor,
  97. depthTest: true,
  98. depthWrite: false,
  99. transparent: true,
  100. vertexColors: true
  101. });
  102. this._camera = params.camera;
  103. this._particles = [];
  104. this._geometry = new THREE.BufferGeometry();
  105. this._geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3));
  106. this._geometry.setAttribute('size', new THREE.Float32BufferAttribute([], 1));
  107. this._geometry.setAttribute('colour', new THREE.Float32BufferAttribute([], 4));
  108. this._geometry.setAttribute('angle', new THREE.Float32BufferAttribute([], 1));
  109. this._geometry.setAttribute('blend', new THREE.Float32BufferAttribute([], 1));
  110. this._points = new THREE.Points(this._geometry, this._material);
  111. params.parent.add(this._points);
  112. // Declare a few splines for different sets of particles. This isn't structured that well, we should
  113. // instead separate these out into new particle system instances with customizable parameters. But
  114. // for the purposes of a demo, more than enough.
  115. this._alphaSplineF = new LinearSpline((t, a, b) => {
  116. return a + t * (b - a);
  117. });
  118. this._alphaSplineF.AddPoint(0.0, 0.0);
  119. this._alphaSplineF.AddPoint(0.1, 1.0);
  120. this._alphaSplineF.AddPoint(0.5, 1.0);
  121. this._alphaSplineF.AddPoint(1.0, 0.0);
  122. this._colourSplineF = new LinearSpline((t, a, b) => {
  123. const c = a.clone();
  124. return c.lerp(b, t);
  125. });
  126. this._colourSplineF.AddPoint(0.0, new THREE.Color(0xFFFF80));
  127. this._colourSplineF.AddPoint(1.0, new THREE.Color(0xFF8080));
  128. this._sizeSplineF = new LinearSpline((t, a, b) => {
  129. return a + t * (b - a);
  130. });
  131. this._sizeSplineF.AddPoint(0.0, 1.0);
  132. this._sizeSplineF.AddPoint(0.25, 7.0);
  133. this._sizeSplineF.AddPoint(0.5, 2.5);
  134. this._sizeSplineF.AddPoint(1.0, 0.0);
  135. this._alphaSplineS = new LinearSpline((t, a, b) => {
  136. return a + t * (b - a);
  137. });
  138. this._alphaSplineS.AddPoint(0.0, 0.0);
  139. this._alphaSplineS.AddPoint(0.1, 1.0);
  140. this._alphaSplineS.AddPoint(0.5, 1.0);
  141. this._alphaSplineS.AddPoint(1.0, 0.0);
  142. this._colourSplineS = new LinearSpline((t, a, b) => {
  143. const c = a.clone();
  144. return c.lerp(b, t);
  145. });
  146. this._colourSplineS.AddPoint(0.0, new THREE.Color(0x202020));
  147. this._colourSplineS.AddPoint(1.0, new THREE.Color(0x000000));
  148. this._sizeSplineS = new LinearSpline((t, a, b) => {
  149. return a + t * (b - a);
  150. });
  151. this._sizeSplineS.AddPoint(0.0, 1.0);
  152. this._sizeSplineS.AddPoint(0.5, 8.0);
  153. this._sizeSplineS.AddPoint(1.0, 16.0);
  154. this._alphaSplineX = new LinearSpline((t, a, b) => {
  155. return a + t * (b - a);
  156. });
  157. this._alphaSplineX.AddPoint(0.0, 0.0);
  158. this._alphaSplineX.AddPoint(0.1, 1.0);
  159. this._alphaSplineX.AddPoint(0.9, 1.0);
  160. this._alphaSplineX.AddPoint(1.0, 0.0);
  161. this._colourSplineX = new LinearSpline((t, a, b) => {
  162. const c = a.clone();
  163. return c.lerp(b, t);
  164. });
  165. this._colourSplineX.AddPoint(0.0, new THREE.Color(0xFF8080));
  166. this._colourSplineX.AddPoint(1.0, new THREE.Color(0xFFFFFF));
  167. this._sizeSplineX = new LinearSpline((t, a, b) => {
  168. return a + t * (b - a);
  169. });
  170. this._sizeSplineX.AddPoint(0.0, 1.0);
  171. this._sizeSplineX.AddPoint(1.0, 1.0);
  172. this._rateLimiter = 0.0;
  173. this._UpdateGeometry();
  174. }
  175. _CreateParticleF() {
  176. const life = (Math.random() * 0.75 + 0.25) * 10.0;
  177. return {
  178. position: new THREE.Vector3(
  179. (Math.random() * 2 - 1) * 4.0,
  180. (Math.random() * 2 - 1) * 4.0,
  181. (Math.random() * 2 - 1) * 4.0),
  182. size: (Math.random() * 0.5 + 0.5) * 2.0,
  183. colour: new THREE.Color(),
  184. alpha: 1.0,
  185. life: life,
  186. maxLife: life,
  187. rotation: Math.random() * 2.0 * Math.PI,
  188. velocity: new THREE.Vector3(0, 5, 0),
  189. blend: 0.0,
  190. };
  191. }
  192. _CreateParticleX() {
  193. const life = (Math.random() * 0.75 + 0.25) * 2.0;
  194. const dirX = (Math.random() * 2.0 - 1.0) * 3.0;
  195. const dirY = (Math.random() * 2.0 - 1.0) * 3.0;
  196. return {
  197. position: new THREE.Vector3(
  198. (Math.random() * 2 - 1) * 4.0,
  199. 10 + (Math.random() * 2 - 1) * 4.0,
  200. (Math.random() * 2 - 1) * 4.0),
  201. size: (Math.random() * 0.5 + 0.5) * 0.5,
  202. colour: new THREE.Color(),
  203. alpha: 1.0,
  204. life: life,
  205. maxLife: life,
  206. rotation: Math.random() * 2.0 * Math.PI,
  207. velocity: new THREE.Vector3(dirX, 10, dirY),
  208. blend: 0.0,
  209. };
  210. }
  211. _CreateParticleS() {
  212. const life = (Math.random() * 0.75 + 0.25) * 15.0;
  213. return {
  214. position: new THREE.Vector3(
  215. (Math.random() * 2 - 1) * 4.0,
  216. 10 + (Math.random() * 2 - 1) * 4.0,
  217. (Math.random() * 2 - 1) * 4.0),
  218. size: (Math.random() * 0.5 + 0.5) * 2.0,
  219. colour: new THREE.Color(),
  220. alpha: 1.0,
  221. life: life,
  222. maxLife: life,
  223. rotation: Math.random() * 2.0 * Math.PI,
  224. velocity: new THREE.Vector3(0, 5, 0),
  225. blend: 1.0,
  226. };
  227. }
  228. _AddParticles(timeElapsed) {
  229. this._rateLimiter += timeElapsed;
  230. const n = Math.floor(this._rateLimiter * 120.0);
  231. this._rateLimiter -= n / 120.0;
  232. for (let i = 0; i < n; i++) {
  233. const p = this._CreateParticleF();
  234. this._particles.push(p);
  235. }
  236. for (let i = 0; i < n; i++) {
  237. const p = this._CreateParticleS();
  238. this._particles.push(p);
  239. }
  240. for (let i = 0; i < n * 2; i++) {
  241. const p = this._CreateParticleX();
  242. this._particles.push(p);
  243. }
  244. }
  245. _UpdateGeometry() {
  246. const positions = [];
  247. const sizes = [];
  248. const colours = [];
  249. const angles = [];
  250. const blends = [];
  251. const box = new THREE.Box3();
  252. for (let p of this._particles) {
  253. positions.push(p.position.x, p.position.y, p.position.z);
  254. colours.push(p.colour.r, p.colour.g, p.colour.b, p.alpha);
  255. sizes.push(p.currentSize);
  256. angles.push(p.rotation);
  257. blends.push(p.blend);
  258. box.expandByPoint(p.position);
  259. }
  260. this._geometry.setAttribute(
  261. 'position', new THREE.Float32BufferAttribute(positions, 3));
  262. this._geometry.setAttribute(
  263. 'size', new THREE.Float32BufferAttribute(sizes, 1));
  264. this._geometry.setAttribute(
  265. 'colour', new THREE.Float32BufferAttribute(colours, 4));
  266. this._geometry.setAttribute(
  267. 'angle', new THREE.Float32BufferAttribute(angles, 1));
  268. this._geometry.setAttribute(
  269. 'blend', new THREE.Float32BufferAttribute(blends, 1));
  270. this._geometry.attributes.position.needsUpdate = true;
  271. this._geometry.attributes.size.needsUpdate = true;
  272. this._geometry.attributes.colour.needsUpdate = true;
  273. this._geometry.attributes.angle.needsUpdate = true;
  274. this._geometry.attributes.blend.needsUpdate = true;
  275. this._geometry.boundingBox = box;
  276. this._geometry.boundingSphere = new THREE.Sphere();
  277. box.getBoundingSphere(this._geometry.boundingSphere);
  278. }
  279. _UpdateParticles(timeElapsed) {
  280. for (let p of this._particles) {
  281. p.life -= timeElapsed;
  282. }
  283. this._particles = this._particles.filter(p => {
  284. return p.life > 0.0;
  285. });
  286. for (let p of this._particles) {
  287. const t = 1.0 - p.life / p.maxLife;
  288. p.rotation += timeElapsed * 0.5;
  289. if (p.blend == 0.0) {
  290. if (p.velocity.x != 0.0) {
  291. p.alpha = this._alphaSplineX.Get(t);
  292. p.currentSize = p.size * this._sizeSplineX.Get(t);
  293. p.colour.copy(this._colourSplineX.Get(t));
  294. } else {
  295. p.alpha = this._alphaSplineF.Get(t);
  296. p.currentSize = p.size * this._sizeSplineF.Get(t);
  297. p.colour.copy(this._colourSplineF.Get(t));
  298. }
  299. } else {
  300. p.alpha = this._alphaSplineS.Get(t);
  301. p.currentSize = p.size * this._sizeSplineS.Get(t);
  302. p.colour.copy(this._colourSplineS.Get(t));
  303. }
  304. p.position.add(p.velocity.clone().multiplyScalar(timeElapsed));
  305. const drag = p.velocity.clone();
  306. drag.multiplyScalar(timeElapsed * 0.1);
  307. drag.x = Math.sign(p.velocity.x) * Math.min(Math.abs(drag.x), Math.abs(p.velocity.x));
  308. drag.y = Math.sign(p.velocity.y) * Math.min(Math.abs(drag.y), Math.abs(p.velocity.y));
  309. drag.z = Math.sign(p.velocity.z) * Math.min(Math.abs(drag.z), Math.abs(p.velocity.z));
  310. p.velocity.sub(drag);
  311. }
  312. this._particles.sort((a, b) => {
  313. const d1 = this._camera.position.distanceTo(a.position);
  314. const d2 = this._camera.position.distanceTo(b.position);
  315. if (d1 > d2) {
  316. return -1;
  317. }
  318. if (d1 < d2) {
  319. return 1;
  320. }
  321. return 0;
  322. });
  323. }
  324. Step(timeElapsed) {
  325. this._AddParticles(timeElapsed);
  326. this._UpdateParticles(timeElapsed);
  327. this._UpdateGeometry();
  328. }
  329. }
  330. class BlendingDemo {
  331. constructor() {
  332. this._Initialize();
  333. }
  334. _Initialize() {
  335. this._threejs = new THREE.WebGLRenderer({
  336. antialias: true,
  337. });
  338. this._threejs.setPixelRatio(window.devicePixelRatio);
  339. this._threejs.setSize(window.innerWidth, window.innerHeight);
  340. document.body.appendChild(this._threejs.domElement);
  341. window.addEventListener('resize', () => {
  342. this._OnWindowResize();
  343. }, false);
  344. const loader = new THREE.CubeTextureLoader();
  345. const texture = loader.load([
  346. './resources/posx.jpg',
  347. './resources/negx.jpg',
  348. './resources/posy.jpg',
  349. './resources/negy.jpg',
  350. './resources/posz.jpg',
  351. './resources/negz.jpg',
  352. ]);
  353. this._backgroundScene = new THREE.Scene();
  354. this._backgroundScene.background = new THREE.Color(0x3f4f6a);
  355. const fov = 60;
  356. const aspect = 1920 / 1080;
  357. const near = 1.0;
  358. const far = 1000.0;
  359. this._backgroundCamera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  360. this._backgroundCamera.position.set(35, 10, 0);
  361. const controls = new OrbitControls(this._backgroundCamera, this._threejs.domElement);
  362. controls.target.set(0, 10, 0);
  363. controls.update();
  364. this._camera = new THREE.OrthographicCamera(0, 1920, 1280, 0, 0, 1);
  365. const mat = new THREE.MeshBasicMaterial({
  366. map: new THREE.TextureLoader().load('./resources/fire.jpg'),
  367. depthTest: true,
  368. depthWrite: false,
  369. transparent: true,
  370. blending: THREE.CustomBlending,
  371. blendEquation: THREE.AddEquation,
  372. blendSrc: THREE.OneFactor,
  373. blendDst: THREE.OneMinusSrcAlphaFactor,
  374. });
  375. const postPlane = new THREE.PlaneBufferGeometry(800, 800);
  376. const postQuad = new THREE.Mesh(postPlane, mat);
  377. postQuad.position.set(
  378. 1920 - (800 * 0.5 + (1280 - 800) * 0.5),
  379. 1280 * 0.5, 0);
  380. this._scene = new THREE.Scene();
  381. // this._scene.add(postQuad);
  382. this._particles = new ParticleSystem({
  383. parent: this._backgroundScene,
  384. camera: this._backgroundCamera,
  385. });
  386. this._UpdateText();
  387. this._previousRAF = null;
  388. this._RAF();
  389. }
  390. _OnWindowResize() {
  391. this._backgroundCamera.aspect = window.innerWidth / window.innerHeight;
  392. this._backgroundCamera.updateProjectionMatrix();
  393. this._camera.aspect = window.innerWidth / window.innerHeight;
  394. this._camera.updateProjectionMatrix();
  395. this._threejs.setSize(window.innerWidth, window.innerHeight);
  396. }
  397. _UpdateText() {
  398. document.getElementById('overlay').style.display = 'none';
  399. document.getElementById('func').innerText = 'Subtract';
  400. document.getElementById('src').innerText = 'ONE';
  401. document.getElementById('dst').innerText = 'ONE';
  402. }
  403. _RAF() {
  404. requestAnimationFrame((t) => {
  405. if (this._previousRAF === null) {
  406. this._previousRAF = t;
  407. }
  408. this._Step(t - this._previousRAF);
  409. this._threejs.autoClear = true;
  410. this._threejs.render(this._backgroundScene, this._backgroundCamera);
  411. this._threejs.autoClear = false;
  412. this._threejs.render(this._scene, this._camera);
  413. this._RAF();
  414. this._previousRAF = t;
  415. });
  416. }
  417. _Step(timeElapsed) {
  418. const timeElapsedS = timeElapsed * 0.001;
  419. this._particles.Step(timeElapsedS);
  420. }
  421. }
  422. let _APP = null;
  423. window.addEventListener('DOMContentLoaded', () => {
  424. _APP = new BlendingDemo();
  425. });