feature: add FlickyParticles

This commit is contained in:
FMS-Cat
2021-03-14 03:56:49 +09:00
parent d51a3ccb96
commit 5a1d96a898
5 changed files with 456 additions and 0 deletions

View File

@@ -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<WebGL2RenderingContext>;
textureRandomStatic: GLCatTexture<WebGL2RenderingContext>;
}
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;
}
}

View File

@@ -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

View File

@@ -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 );
}

View File

@@ -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 );
}

View File

@@ -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;
}