mirror of
https://github.com/FMS-Cat/condition.git
synced 2025-08-16 10:34:09 +02:00
feature: add FlickyParticles
This commit is contained in:
143
src/entities/FlickyParticles.ts
Normal file
143
src/entities/FlickyParticles.ts
Normal 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;
|
||||
}
|
||||
}
|
@@ -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
|
||||
|
114
src/shaders/flicky-particles-compute.frag
Normal file
114
src/shaders/flicky-particles-compute.frag
Normal 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 );
|
||||
}
|
106
src/shaders/flicky-particles-render.frag
Normal file
106
src/shaders/flicky-particles-render.frag
Normal 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 );
|
||||
}
|
85
src/shaders/flicky-particles-render.vert
Normal file
85
src/shaders/flicky-particles-render.vert
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user