From 5a1d96a898d9eecfe9f9237ff003bda990c47a03 Mon Sep 17 00:00:00 2001 From: FMS-Cat Date: Sun, 14 Mar 2021 03:56:49 +0900 Subject: [PATCH] feature: add FlickyParticles --- src/entities/FlickyParticles.ts | 143 ++++++++++++++++++++++ src/main.ts | 8 ++ src/shaders/flicky-particles-compute.frag | 114 +++++++++++++++++ src/shaders/flicky-particles-render.frag | 106 ++++++++++++++++ src/shaders/flicky-particles-render.vert | 85 +++++++++++++ 5 files changed, 456 insertions(+) create mode 100644 src/entities/FlickyParticles.ts create mode 100644 src/shaders/flicky-particles-compute.frag create mode 100644 src/shaders/flicky-particles-render.frag create mode 100644 src/shaders/flicky-particles-render.vert diff --git a/src/entities/FlickyParticles.ts b/src/entities/FlickyParticles.ts new file mode 100644 index 0000000..6339537 --- /dev/null +++ b/src/entities/FlickyParticles.ts @@ -0,0 +1,143 @@ +import { GLCatTexture } from '@fms-cat/glcat-ts'; +import { DISPLAY } from '../heck/DISPLAY'; +import { Entity } from '../heck/Entity'; +import { GPUParticles } from './GPUParticles'; +import { Geometry } from '../heck/Geometry'; +import { InstancedGeometry } from '../heck/InstancedGeometry'; +import { Material } from '../heck/Material'; +import quadVert from '../shaders/quad.vert'; +import flickyParticleComputeFrag from '../shaders/flicky-particles-compute.frag'; +import flickyParticleRenderFrag from '../shaders/flicky-particles-render.frag'; +import flickyParticleRenderVert from '../shaders/flicky-particles-render.vert'; +import { TRIANGLE_STRIP_QUAD } from '@fms-cat/experimental'; + +export interface FlickyParticlesOptions { + particlesSqrt: number; + textureRandom: GLCatTexture; + textureRandomStatic: GLCatTexture; +} + +export class FlickyParticles { + public get entity(): Entity { + return this.__gpuParticles.entity; + } + + private __gpuParticles: GPUParticles; + + public get materialCompute(): Material { + return this.__gpuParticles.materialCompute; + } + + public get materialRender(): Material { + return this.__gpuParticles.materialRender; + } + + public constructor( options: FlickyParticlesOptions ) { + this.__gpuParticles = new GPUParticles( { + materialCompute: this.__createMaterialCompute( options ), + geometryRender: this.__createGeometryRender( options ), + materialRender: this.__createMaterialRender( options ), + computeWidth: options.particlesSqrt, + computeHeight: options.particlesSqrt, + computeNumBuffers: 1, + namePrefix: process.env.DEV && 'FlickyParticles', + } ); + } + + private __createMaterialCompute( options: FlickyParticlesOptions ): Material { + const { particlesSqrt } = options; + const particles = particlesSqrt * particlesSqrt; + + const material = new Material( quadVert, flickyParticleComputeFrag ); + material.addUniform( 'particlesSqrt', '1f', particlesSqrt ); + material.addUniform( 'particles', '1f', particles ); + material.addUniformTexture( 'samplerRandom', options.textureRandom ); + + if ( process.env.DEV ) { + if ( module.hot ) { + module.hot.accept( '../shaders/flicky-particles-compute.frag', () => { + material.replaceShader( quadVert, flickyParticleComputeFrag ); + } ); + } + } + + return material; + } + + private __createGeometryRender( options: FlickyParticlesOptions ): Geometry { + const { particlesSqrt } = options; + const particles = particlesSqrt * particlesSqrt; + + const geometry = new InstancedGeometry(); + + const bufferP = DISPLAY.glCat.createBuffer(); + bufferP.setVertexbuffer( new Float32Array( TRIANGLE_STRIP_QUAD ) ); + + geometry.addAttribute( 'position', { + buffer: bufferP, + size: 2, + type: DISPLAY.gl.FLOAT, + } ); + + const bufferComputeUV = DISPLAY.glCat.createBuffer(); + bufferComputeUV.setVertexbuffer( ( () => { + const ret = new Float32Array( particles * 2 ); + for ( let iy = 0; iy < particlesSqrt; iy ++ ) { + for ( let ix = 0; ix < particlesSqrt; ix ++ ) { + const i = ix + iy * particlesSqrt; + const s = ( ix + 0.5 ) / particlesSqrt; + const t = ( iy + 0.5 ) / particlesSqrt; + ret[ i * 2 + 0 ] = s; + ret[ i * 2 + 1 ] = t; + } + } + return ret; + } )() ); + + geometry.addAttribute( 'computeUV', { + buffer: bufferComputeUV, + size: 2, + divisor: 1, + type: DISPLAY.gl.FLOAT + } ); + + geometry.count = 4; + geometry.mode = DISPLAY.gl.TRIANGLE_STRIP; + geometry.primcount = options.particlesSqrt * options.particlesSqrt; + + return geometry; + } + + private __createMaterialRender( options: FlickyParticlesOptions ): Material { + const material = new Material( + flickyParticleRenderVert, + flickyParticleRenderFrag, + ); + material.addUniform( 'colorVar', '1f', 0.1 ); + material.addUniformTexture( 'samplerRandomStatic', options.textureRandomStatic ); + + if ( process.env.DEV ) { + if ( module.hot ) { + module.hot.accept( '../shaders/flicky-particles-render.vert', () => { + material.replaceShader( + flickyParticleRenderVert, + flickyParticleRenderFrag, + ); + } ); + } + } + + if ( process.env.DEV ) { + if ( module.hot ) { + module.hot.accept( '../shaders/flicky-particles-render.frag', () => { + material.replaceShader( + flickyParticleRenderVert, + flickyParticleRenderFrag, + ); + } ); + } + } + + return material; + } +} diff --git a/src/main.ts b/src/main.ts index 9981f1e..7fcb1fb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -22,6 +22,7 @@ import { Glitch } from './entities/Glitch'; import { Rings } from './entities/Rings'; import { RTInspector } from './entities/RTInspector'; import { Component } from './heck/components/Component'; +import { FlickyParticles } from './entities/FlickyParticles'; // == gl =========================================================================================== const { canvas, glCat } = DISPLAY; @@ -217,6 +218,13 @@ dog.root.children.push( trails.entity ); const rings = new Rings(); dog.root.children.push( rings.entity ); +const flickyParticles = new FlickyParticles( { + particlesSqrt: 8, + textureRandom: randomTexture.texture, + textureRandomStatic: randomTextureStatic.texture +} ); +dog.root.children.push( flickyParticles.entity ); + const raymarcher = new Raymarcher( { textureRandom: randomTexture.texture, textureRandomStatic: randomTextureStatic.texture diff --git a/src/shaders/flicky-particles-compute.frag b/src/shaders/flicky-particles-compute.frag new file mode 100644 index 0000000..dc3b9b7 --- /dev/null +++ b/src/shaders/flicky-particles-compute.frag @@ -0,0 +1,114 @@ +#version 300 es + +precision highp float; + +const float PARTICLE_LIFE_LENGTH = 1.0; +const float HUGE = 9E16; +const float PI = 3.14159265; +const float TAU = 6.283185307; + +#define saturate(i) clamp(i,0.,1.) +#define lofi(i,m) (floor((i)/(m))*(m)) +#define lofir(i,m) (floor((i)/(m)+.5)*(m)) + +out vec4 fragCompute0; + +uniform bool init; +uniform float time; +uniform float beat; +uniform float particlesSqrt; +uniform float totalFrame; +uniform float deltaTime; +uniform float noiseScale; +uniform float noisePhase; +uniform vec2 resolution; +uniform sampler2D samplerCompute0; +uniform sampler2D samplerRandom; + +vec4 sampleRandom( vec2 _uv ) { + return texture( samplerRandom, _uv ); +} + +#pragma glslify: prng = require( ./-prng ); + +vec3 randomSphere( inout vec4 seed ) { + vec3 v; + for ( int i = 0; i < 10; i ++ ) { + v = vec3( + prng( seed ), + prng( seed ), + prng( seed ) + ) * 2.0 - 1.0; + if ( length( v ) < 1.0 ) { break; } + } + return v; +} + +vec2 randomCircle( inout vec4 seed ) { + vec2 v; + for ( int i = 0; i < 10; i ++ ) { + v = vec2( + prng( seed ), + prng( seed ) + ) * 2.0 - 1.0; + if ( length( v ) < 1.0 ) { break; } + } + return v; +} + +vec3 randomBox( inout vec4 seed ) { + vec3 v; + v = vec3( + prng( seed ), + prng( seed ), + prng( seed ) + ) * 2.0 - 1.0; + return v; +} + +// ------ + +void main() { + vec2 uv = gl_FragCoord.xy / resolution; + + float dt = deltaTime; + + // == prepare some vars ========================================================================== + vec4 seed = texture( samplerRandom, uv ); + prng( seed ); + + vec4 tex0 = texture( samplerCompute0, uv ); + + vec3 pos = tex0.xyz; + float life = tex0.w; + + float timing = mix( + 0.0, + PARTICLE_LIFE_LENGTH, + ( floor( uv.x * particlesSqrt ) / particlesSqrt + floor( uv.y * particlesSqrt ) ) / particlesSqrt + ); + timing += lofi( time, PARTICLE_LIFE_LENGTH ); + + if ( time - deltaTime + PARTICLE_LIFE_LENGTH < timing ) { + timing -= PARTICLE_LIFE_LENGTH; + } + + // == initialize particles ======================================================================= + if ( + time - deltaTime < timing && timing <= time + ) { + dt = time - timing; + + pos = 3.0 * randomSphere( seed ); + + life = 1.0; + } else { + // do nothing + // if you want to remove init frag from the particle, do at here + } + + // == update particles =========================================================================== + life -= dt / PARTICLE_LIFE_LENGTH; + + fragCompute0 = vec4( pos, life ); +} diff --git a/src/shaders/flicky-particles-render.frag b/src/shaders/flicky-particles-render.frag new file mode 100644 index 0000000..6b7dc02 --- /dev/null +++ b/src/shaders/flicky-particles-render.frag @@ -0,0 +1,106 @@ +#version 300 es + +precision highp float; + +const int MTL_UNLIT = 1; + +const int MODE_RECT = 0; +const int MODE_GRID = 1; +const int MODE_CIRCLE = 2; +const int MODES = 3; +const float PI = 3.14159265; +const float TAU = 6.28318531; + +#define saturate(i) clamp(i,0.,1.) +#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a))) + +// == varings / uniforms =========================================================================== +in vec4 vPosition; +in vec3 vNormal; +in float vLife; +in vec2 vUv; +in float vMode; + +layout (location = 0) out vec4 fragPosition; +layout (location = 1) out vec4 fragNormal; +layout (location = 2) out vec4 fragColor; +layout (location = 3) out vec4 fragWTF; + +uniform float time; + +// == utils ======================================================================================== +vec2 yflip( vec2 uv ) { + return vec2( 0.0, 1.0 ) + vec2( 1.0, -1.0 ) * uv; +} + +// == main procedure =============================================================================== +void main() { + int mode = int( vMode + 0.5 ); + + vec3 color = vec3( 0.0 ); + + if ( vLife < 0.0 ) { discard; } + if ( vLife < 0.1 && 0.5 < fract( 30.0 * vLife ) ) { discard; } + + if ( mode == MODE_RECT ) { + vec2 size = vec2( 0.5 ); + size.y *= 1.0 - exp( -10.0 * ( 1.0 - vLife ) ); + + vec2 uv = vUv; + vec2 deltaUv = abs( vec2( dFdx( uv.x ), dFdy( uv.y ) ) ); + vec2 folded = ( size - abs( uv - 0.5 ) ) / deltaUv; + bool isVert = false; + + if ( folded.x < folded.y ) { + uv.xy = vec2( uv.y, uv.x ); + folded.xy = folded.yx; + deltaUv.xy = deltaUv.yx; + isVert = true; + } + + float spinUvx = uv.y < 0.5 ? uv.x : ( 1.0 - uv.x ); + spinUvx = isVert ? ( 1.0 - spinUvx ) : spinUvx; + + float border = step( 0.0, folded.y ) * step( folded.y, 2.0 ); + border *= step( 0.0, sin( 12.0 * time + 0.5 * spinUvx / deltaUv.x ) ); // dashed + + if ( border < 0.5 ) { discard; } + + color = vec3( 1.0 ); + + } else if ( mode == MODE_GRID ) { + float size = 0.2; + size *= 1.0 - exp( -10.0 * ( 1.0 - vLife ) ); + + vec2 uv = vUv; + + vec2 folded = mod( 4.0 * uv, 1.0 ); + + float shape = step( length( folded - 0.5 ), size ); + + if ( shape < 0.5 ) { discard; } + + color = vec3( 1.0 ); + + } else if ( mode == MODE_CIRCLE ) { + float size = 0.5; + size *= 1.0 - exp( -10.0 * ( 1.0 - vLife ) ); + + vec2 uv = vUv; + vec2 deltaUv = abs( vec2( dFdx( uv.x ), dFdy( uv.y ) ) ); + + float r = length( uv - 0.5 ); + float shape = step( r, size ); + shape *= step( size, r + length( deltaUv ) ); + + if ( shape < 0.5 ) { discard; } + + color = vec3( 1.0 ); + + } + + fragPosition = vPosition; + fragNormal = vec4( vNormal, 1.0 ); + fragColor = vec4( color, 1.0 ); + fragWTF = vec4( vec3( 0.0 ), MTL_UNLIT ); +} diff --git a/src/shaders/flicky-particles-render.vert b/src/shaders/flicky-particles-render.vert new file mode 100644 index 0000000..ca3f1aa --- /dev/null +++ b/src/shaders/flicky-particles-render.vert @@ -0,0 +1,85 @@ +#version 300 es + +const float MODE_RECT = 0.0; +const float MODE_GRID = 1.0; +const float MODE_CIRCLE = 2.0; +const float MODES = 3.0; +const float HUGE = 9E16; +const float PI = 3.14159265; +const float TAU = 6.283185307; + +#define saturate(i) clamp(i,0.,1.) +#define lofi(i,m) (floor((i)/(m))*(m)) +#define lofir(i,m) (floor((i+0.5)/(m))*(m)) + +// ------------------------------------------------------------------------------------------------- + +in vec2 computeUV; +in vec2 position; + +out float vLife; +out float vMode; +out vec2 vUv; +out vec3 vNormal; +out vec4 vPosition; + +uniform vec2 resolution; +uniform vec2 resolutionCompute; +uniform mat4 projectionMatrix; +uniform mat4 viewMatrix; +uniform mat4 modelMatrix; +uniform mat4 normalMatrix; +uniform sampler2D samplerCompute0; +uniform sampler2D samplerRandomStatic; + +// == utils ======================================================================================== +vec4 random( vec2 uv ) { + return texture( samplerRandomStatic, uv ); +} + +mat2 rotate2D( float _t ) { + return mat2( cos( _t ), sin( _t ), -sin( _t ), cos( _t ) ); +} + +// == main procedure =============================================================================== +void main() { + vec4 tex0 = texture( samplerCompute0, computeUV ); + + // == assign varying variables =================================================================== + vec4 dice = random( computeUV.xy * 182.92 ); + vMode = floor( float( MODES ) * dice.w ); + + vUv = 0.5 + 0.5 * position; + + vNormal = normalize( ( normalMatrix * vec4( 0.0, 0.0, 1.0, 1.0 ) ).xyz ); + + vLife = tex0.w; + + // == compute size =============================================================================== + vPosition = vec4( tex0.xyz, 1.0 ); + + vec2 size; + + if ( vMode == MODE_RECT ) { + size = 1.0 * dice.xy; + + } else if ( vMode == MODE_GRID ) { + size = vec2( 0.25 + 0.25 * dice.x ); + + } else if ( vMode == MODE_CIRCLE ) { + size = vec2( 1.0 * dice.x ); + + } + + vec2 shape = position * size; + + vPosition.xy += shape; + + // == send the vertex position =================================================================== + vPosition = modelMatrix * vPosition; + vec4 outPos = projectionMatrix * viewMatrix * vPosition; + outPos.x *= resolution.y / resolution.x; + gl_Position = outPos; + + vPosition.w = outPos.z / outPos.w; +}