123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js';
- import {OrbitControls} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/controls/OrbitControls.js';
- const _VS = `
- uniform float pointMultiplier;
- attribute float size;
- attribute float angle;
- attribute float blend;
- attribute vec4 colour;
- varying vec4 vColour;
- varying vec2 vAngle;
- varying float vBlend;
- void main() {
- vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
- gl_Position = projectionMatrix * mvPosition;
- gl_PointSize = size * pointMultiplier / gl_Position.w;
- vAngle = vec2(cos(angle), sin(angle));
- vColour = colour;
- vBlend = blend;
- }`;
- const _FS = `
- uniform sampler2D diffuseTexture;
- varying vec4 vColour;
- varying vec2 vAngle;
- varying float vBlend;
- void main() {
- vec2 coords = (gl_PointCoord - 0.5) * mat2(vAngle.x, vAngle.y, -vAngle.y, vAngle.x) + 0.5;
- gl_FragColor = texture2D(diffuseTexture, coords) * vColour;
- gl_FragColor.xyz *= gl_FragColor.w;
- gl_FragColor.w *= vBlend;
- }`;
- const _VS_2 = `
- uniform float pointMultiplier;
- attribute float size;
- attribute float angle;
- attribute vec4 colour;
- varying vec4 vColour;
- varying vec2 vAngle;
- void main() {
- vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
- gl_Position = projectionMatrix * mvPosition;
- gl_PointSize = size * pointMultiplier / gl_Position.w;
- vAngle = vec2(cos(angle), sin(angle));
- vColour = colour;
- }`;
- const _FS_2 = `
- uniform sampler2D diffuseTexture;
- varying vec4 vColour;
- varying vec2 vAngle;
- void main() {
- vec2 coords = (gl_PointCoord - 0.5) * mat2(vAngle.x, vAngle.y, -vAngle.y, vAngle.x) + 0.5;
- gl_FragColor = texture2D(diffuseTexture, coords) * vColour;
- }`;
- class LinearSpline {
- constructor(lerp) {
- this._points = [];
- this._lerp = lerp;
- }
- AddPoint(t, d) {
- this._points.push([t, d]);
- }
- Get(t) {
- let p1 = 0;
- for (let i = 0; i < this._points.length; i++) {
- if (this._points[i][0] >= t) {
- break;
- }
- p1 = i;
- }
- const p2 = Math.min(this._points.length - 1, p1 + 1);
- if (p1 == p2) {
- return this._points[p1][1];
- }
- return this._lerp(
- (t - this._points[p1][0]) / (
- this._points[p2][0] - this._points[p1][0]),
- this._points[p1][1], this._points[p2][1]);
- }
- }
- class ParticleSystem {
- constructor(params) {
- const uniforms = {
- diffuseTexture: {
- value: new THREE.TextureLoader().load('./resources/fire.png')
- },
- pointMultiplier: {
- value: window.innerHeight / (2.0 * Math.tan(0.5 * 60.0 * Math.PI / 180.0))
- }
- };
- this._material = new THREE.ShaderMaterial({
- uniforms: uniforms,
- vertexShader: _VS,
- fragmentShader: _FS,
- blending: THREE.CustomBlending,
- blendEquation: THREE.AddEquation,
- blendSrc: THREE.OneFactor,
- blendDst: THREE.OneMinusSrcAlphaFactor,
- depthTest: true,
- depthWrite: false,
- transparent: true,
- vertexColors: true
- });
- this._camera = params.camera;
- this._particles = [];
- this._geometry = new THREE.BufferGeometry();
- this._geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3));
- this._geometry.setAttribute('size', new THREE.Float32BufferAttribute([], 1));
- this._geometry.setAttribute('colour', new THREE.Float32BufferAttribute([], 4));
- this._geometry.setAttribute('angle', new THREE.Float32BufferAttribute([], 1));
- this._geometry.setAttribute('blend', new THREE.Float32BufferAttribute([], 1));
- this._points = new THREE.Points(this._geometry, this._material);
- params.parent.add(this._points);
- // Declare a few splines for different sets of particles. This isn't structured that well, we should
- // instead separate these out into new particle system instances with customizable parameters. But
- // for the purposes of a demo, more than enough.
- this._alphaSplineF = new LinearSpline((t, a, b) => {
- return a + t * (b - a);
- });
- this._alphaSplineF.AddPoint(0.0, 0.0);
- this._alphaSplineF.AddPoint(0.1, 1.0);
- this._alphaSplineF.AddPoint(0.5, 1.0);
- this._alphaSplineF.AddPoint(1.0, 0.0);
- this._colourSplineF = new LinearSpline((t, a, b) => {
- const c = a.clone();
- return c.lerp(b, t);
- });
- this._colourSplineF.AddPoint(0.0, new THREE.Color(0xFFFF80));
- this._colourSplineF.AddPoint(1.0, new THREE.Color(0xFF8080));
- this._sizeSplineF = new LinearSpline((t, a, b) => {
- return a + t * (b - a);
- });
- this._sizeSplineF.AddPoint(0.0, 1.0);
- this._sizeSplineF.AddPoint(0.25, 7.0);
- this._sizeSplineF.AddPoint(0.5, 2.5);
- this._sizeSplineF.AddPoint(1.0, 0.0);
- this._alphaSplineS = new LinearSpline((t, a, b) => {
- return a + t * (b - a);
- });
- this._alphaSplineS.AddPoint(0.0, 0.0);
- this._alphaSplineS.AddPoint(0.1, 1.0);
- this._alphaSplineS.AddPoint(0.5, 1.0);
- this._alphaSplineS.AddPoint(1.0, 0.0);
- this._colourSplineS = new LinearSpline((t, a, b) => {
- const c = a.clone();
- return c.lerp(b, t);
- });
- this._colourSplineS.AddPoint(0.0, new THREE.Color(0x202020));
- this._colourSplineS.AddPoint(1.0, new THREE.Color(0x000000));
- this._sizeSplineS = new LinearSpline((t, a, b) => {
- return a + t * (b - a);
- });
- this._sizeSplineS.AddPoint(0.0, 1.0);
- this._sizeSplineS.AddPoint(0.5, 8.0);
- this._sizeSplineS.AddPoint(1.0, 16.0);
- this._alphaSplineX = new LinearSpline((t, a, b) => {
- return a + t * (b - a);
- });
- this._alphaSplineX.AddPoint(0.0, 0.0);
- this._alphaSplineX.AddPoint(0.1, 1.0);
- this._alphaSplineX.AddPoint(0.9, 1.0);
- this._alphaSplineX.AddPoint(1.0, 0.0);
- this._colourSplineX = new LinearSpline((t, a, b) => {
- const c = a.clone();
- return c.lerp(b, t);
- });
- this._colourSplineX.AddPoint(0.0, new THREE.Color(0xFF8080));
- this._colourSplineX.AddPoint(1.0, new THREE.Color(0xFFFFFF));
- this._sizeSplineX = new LinearSpline((t, a, b) => {
- return a + t * (b - a);
- });
- this._sizeSplineX.AddPoint(0.0, 1.0);
- this._sizeSplineX.AddPoint(1.0, 1.0);
- this._rateLimiter = 0.0;
- this._UpdateGeometry();
- }
- _CreateParticleF() {
- const life = (Math.random() * 0.75 + 0.25) * 10.0;
- return {
- position: new THREE.Vector3(
- (Math.random() * 2 - 1) * 4.0,
- (Math.random() * 2 - 1) * 4.0,
- (Math.random() * 2 - 1) * 4.0),
- size: (Math.random() * 0.5 + 0.5) * 2.0,
- colour: new THREE.Color(),
- alpha: 1.0,
- life: life,
- maxLife: life,
- rotation: Math.random() * 2.0 * Math.PI,
- velocity: new THREE.Vector3(0, 5, 0),
- blend: 0.0,
- };
- }
- _CreateParticleX() {
- const life = (Math.random() * 0.75 + 0.25) * 2.0;
- const dirX = (Math.random() * 2.0 - 1.0) * 3.0;
- const dirY = (Math.random() * 2.0 - 1.0) * 3.0;
- return {
- position: new THREE.Vector3(
- (Math.random() * 2 - 1) * 4.0,
- 10 + (Math.random() * 2 - 1) * 4.0,
- (Math.random() * 2 - 1) * 4.0),
- size: (Math.random() * 0.5 + 0.5) * 0.5,
- colour: new THREE.Color(),
- alpha: 1.0,
- life: life,
- maxLife: life,
- rotation: Math.random() * 2.0 * Math.PI,
- velocity: new THREE.Vector3(dirX, 10, dirY),
- blend: 0.0,
- };
- }
- _CreateParticleS() {
- const life = (Math.random() * 0.75 + 0.25) * 15.0;
- return {
- position: new THREE.Vector3(
- (Math.random() * 2 - 1) * 4.0,
- 10 + (Math.random() * 2 - 1) * 4.0,
- (Math.random() * 2 - 1) * 4.0),
- size: (Math.random() * 0.5 + 0.5) * 2.0,
- colour: new THREE.Color(),
- alpha: 1.0,
- life: life,
- maxLife: life,
- rotation: Math.random() * 2.0 * Math.PI,
- velocity: new THREE.Vector3(0, 5, 0),
- blend: 1.0,
- };
- }
- _AddParticles(timeElapsed) {
- this._rateLimiter += timeElapsed;
- const n = Math.floor(this._rateLimiter * 120.0);
- this._rateLimiter -= n / 120.0;
- for (let i = 0; i < n; i++) {
- const p = this._CreateParticleF();
- this._particles.push(p);
- }
- for (let i = 0; i < n; i++) {
- const p = this._CreateParticleS();
- this._particles.push(p);
- }
- for (let i = 0; i < n * 2; i++) {
- const p = this._CreateParticleX();
- this._particles.push(p);
- }
- }
- _UpdateGeometry() {
- const positions = [];
- const sizes = [];
- const colours = [];
- const angles = [];
- const blends = [];
- const box = new THREE.Box3();
- for (let p of this._particles) {
- positions.push(p.position.x, p.position.y, p.position.z);
- colours.push(p.colour.r, p.colour.g, p.colour.b, p.alpha);
- sizes.push(p.currentSize);
- angles.push(p.rotation);
- blends.push(p.blend);
- box.expandByPoint(p.position);
- }
- this._geometry.setAttribute(
- 'position', new THREE.Float32BufferAttribute(positions, 3));
- this._geometry.setAttribute(
- 'size', new THREE.Float32BufferAttribute(sizes, 1));
- this._geometry.setAttribute(
- 'colour', new THREE.Float32BufferAttribute(colours, 4));
- this._geometry.setAttribute(
- 'angle', new THREE.Float32BufferAttribute(angles, 1));
- this._geometry.setAttribute(
- 'blend', new THREE.Float32BufferAttribute(blends, 1));
-
- this._geometry.attributes.position.needsUpdate = true;
- this._geometry.attributes.size.needsUpdate = true;
- this._geometry.attributes.colour.needsUpdate = true;
- this._geometry.attributes.angle.needsUpdate = true;
- this._geometry.attributes.blend.needsUpdate = true;
- this._geometry.boundingBox = box;
- this._geometry.boundingSphere = new THREE.Sphere();
- box.getBoundingSphere(this._geometry.boundingSphere);
- }
- _UpdateParticles(timeElapsed) {
- for (let p of this._particles) {
- p.life -= timeElapsed;
- }
- this._particles = this._particles.filter(p => {
- return p.life > 0.0;
- });
- for (let p of this._particles) {
- const t = 1.0 - p.life / p.maxLife;
- p.rotation += timeElapsed * 0.5;
- if (p.blend == 0.0) {
- if (p.velocity.x != 0.0) {
- p.alpha = this._alphaSplineX.Get(t);
- p.currentSize = p.size * this._sizeSplineX.Get(t);
- p.colour.copy(this._colourSplineX.Get(t));
- } else {
- p.alpha = this._alphaSplineF.Get(t);
- p.currentSize = p.size * this._sizeSplineF.Get(t);
- p.colour.copy(this._colourSplineF.Get(t));
- }
- } else {
- p.alpha = this._alphaSplineS.Get(t);
- p.currentSize = p.size * this._sizeSplineS.Get(t);
- p.colour.copy(this._colourSplineS.Get(t));
- }
- p.position.add(p.velocity.clone().multiplyScalar(timeElapsed));
- const drag = p.velocity.clone();
- drag.multiplyScalar(timeElapsed * 0.1);
- drag.x = Math.sign(p.velocity.x) * Math.min(Math.abs(drag.x), Math.abs(p.velocity.x));
- drag.y = Math.sign(p.velocity.y) * Math.min(Math.abs(drag.y), Math.abs(p.velocity.y));
- drag.z = Math.sign(p.velocity.z) * Math.min(Math.abs(drag.z), Math.abs(p.velocity.z));
- p.velocity.sub(drag);
- }
- this._particles.sort((a, b) => {
- const d1 = this._camera.position.distanceTo(a.position);
- const d2 = this._camera.position.distanceTo(b.position);
- if (d1 > d2) {
- return -1;
- }
- if (d1 < d2) {
- return 1;
- }
- return 0;
- });
- }
- Step(timeElapsed) {
- this._AddParticles(timeElapsed);
- this._UpdateParticles(timeElapsed);
- this._UpdateGeometry();
- }
- }
- class BlendingDemo {
- constructor() {
- this._Initialize();
- }
- _Initialize() {
- this._threejs = new THREE.WebGLRenderer({
- antialias: true,
- });
- this._threejs.setPixelRatio(window.devicePixelRatio);
- this._threejs.setSize(window.innerWidth, window.innerHeight);
- document.body.appendChild(this._threejs.domElement);
- window.addEventListener('resize', () => {
- this._OnWindowResize();
- }, false);
- const loader = new THREE.CubeTextureLoader();
- const texture = loader.load([
- './resources/posx.jpg',
- './resources/negx.jpg',
- './resources/posy.jpg',
- './resources/negy.jpg',
- './resources/posz.jpg',
- './resources/negz.jpg',
- ]);
- this._backgroundScene = new THREE.Scene();
- this._backgroundScene.background = new THREE.Color(0x3f4f6a);
- const fov = 60;
- const aspect = 1920 / 1080;
- const near = 1.0;
- const far = 1000.0;
- this._backgroundCamera = new THREE.PerspectiveCamera(fov, aspect, near, far);
- this._backgroundCamera.position.set(35, 10, 0);
- const controls = new OrbitControls(this._backgroundCamera, this._threejs.domElement);
- controls.target.set(0, 10, 0);
- controls.update();
- this._camera = new THREE.OrthographicCamera(0, 1920, 1280, 0, 0, 1);
- const mat = new THREE.MeshBasicMaterial({
- map: new THREE.TextureLoader().load('./resources/fire.jpg'),
- depthTest: true,
- depthWrite: false,
- transparent: true,
- blending: THREE.CustomBlending,
- blendEquation: THREE.AddEquation,
- blendSrc: THREE.OneFactor,
- blendDst: THREE.OneMinusSrcAlphaFactor,
- });
- const postPlane = new THREE.PlaneBufferGeometry(800, 800);
- const postQuad = new THREE.Mesh(postPlane, mat);
- postQuad.position.set(
- 1920 - (800 * 0.5 + (1280 - 800) * 0.5),
- 1280 * 0.5, 0);
- this._scene = new THREE.Scene();
- // this._scene.add(postQuad);
- this._particles = new ParticleSystem({
- parent: this._backgroundScene,
- camera: this._backgroundCamera,
- });
- this._UpdateText();
- this._previousRAF = null;
- this._RAF();
- }
- _OnWindowResize() {
- this._backgroundCamera.aspect = window.innerWidth / window.innerHeight;
- this._backgroundCamera.updateProjectionMatrix();
- this._camera.aspect = window.innerWidth / window.innerHeight;
- this._camera.updateProjectionMatrix();
- this._threejs.setSize(window.innerWidth, window.innerHeight);
- }
- _UpdateText() {
- document.getElementById('overlay').style.display = 'none';
- document.getElementById('func').innerText = 'Subtract';
- document.getElementById('src').innerText = 'ONE';
- document.getElementById('dst').innerText = 'ONE';
- }
- _RAF() {
- requestAnimationFrame((t) => {
- if (this._previousRAF === null) {
- this._previousRAF = t;
- }
- this._Step(t - this._previousRAF);
- this._threejs.autoClear = true;
- this._threejs.render(this._backgroundScene, this._backgroundCamera);
- this._threejs.autoClear = false;
- this._threejs.render(this._scene, this._camera);
- this._RAF();
- this._previousRAF = t;
- });
- }
- _Step(timeElapsed) {
- const timeElapsedS = timeElapsed * 0.001;
- this._particles.Step(timeElapsedS);
- }
- }
- let _APP = null;
- window.addEventListener('DOMContentLoaded', () => {
- _APP = new BlendingDemo();
- });
|