update.vert
#version 300 es
layout (location = 0) in vec2 aPosition;
layout (location = 1) in float aAge;
layout (location = 2) in float aLife;
layout (location = 3) in vec2 aVelocity;
out vec2 vPosition;
out vec2 vVelocity;
out float vAge;
out float vLife;
uniform float uTime;
uniform float uTimeDelta;
uniform float uMinTheta;
uniform float uMaxTheta;
uniform float uMinSpeed;
uniform float uMaxSpeed;
uniform vec2 uGravity;
uniform vec2 uOrigin;
const uint UINT_MAX = 0xffffffffu;
uvec3 k = uvec3(0x456789abu, 0x6789ab45u, 0x89ab4567u);
uvec3 u = uvec3(1, 2, 3);
uvec2 uhash22(uvec2 n){
n ^= (n.yx << u.xy);
n ^= (n.yx >> u.xy);
n *= k.xy;
n ^= (n.yx << u.xy);
return n * k.xy;
}
vec2 hash22(vec2 b) {
uvec2 n = floatBitsToUint(b);
return vec2(uhash22(n)) / vec2(UINT_MAX);
}
uvec3 uhash33(uvec3 n){
n ^= (n.yzx << u);
n ^= (n.yzx >> u);
n *= k;
n ^= (n.yzx << u);
return n * k;
}
float gtable3(vec3 lattice, vec3 p){
uvec3 n = floatBitsToUint(lattice);
uint ind = uhash33(n).x >> 28;
float u = ind < 8u ? p.x : p.y;
float v = ind < 4u ? p.y : ind == 12u || ind == 14u ? p.x : p.z;
return ((ind & 1u) == 0u? u: -u) + ((ind & 2u) == 0u? v : -v);
}
vec3 hermite5(vec3 x) {
return x * x * x * (x * (x * 6.0 - 15.0) + 10.0);
}
const float apx_1inSqrt2 = 0.70710678;
float pnoise31(vec3 p) {
vec3 p0 = floor(p);
vec3 p1 = p0 + vec3(1.0, 0.0, 0.0);
vec3 p2 = p0 + vec3(0.0, 1.0, 0.0);
vec3 p3 = p0 + vec3(1.0, 1.0, 0.0);
vec3 p4 = p0 + vec3(0.0, 0.0, 1.0);
vec3 p5 = p4 + vec3(1.0, 0.0, 0.0);
vec3 p6 = p4 + vec3(0.0, 1.0, 0.0);
vec3 p7 = p4 + vec3(1.0, 1.0, 0.0);
vec3 f = fract(p);
vec3 d0 = f;
vec3 d1 = f - vec3(1.0, 0.0, 0.0);
vec3 d2 = f - vec3(0.0, 1.0, 0.0);
vec3 d3 = f - vec3(1.0, 1.0, 0.0);
vec3 d4 = f - vec3(0.0, 0.0, 1.0);
vec3 d5 = f - vec3(1.0, 0.0, 1.0);
vec3 d6 = f - vec3(0.0, 1.0, 1.0);
vec3 d7 = f - vec3(1.0, 1.0, 1.0);
float dh0 = gtable3(p0, d0) * apx_1inSqrt2;
float dh1 = gtable3(p1, d1) * apx_1inSqrt2;
float dh2 = gtable3(p2, d2) * apx_1inSqrt2;
float dh3 = gtable3(p3, d3) * apx_1inSqrt2;
float dh4 = gtable3(p4, d4) * apx_1inSqrt2;
float dh5 = gtable3(p5, d5) * apx_1inSqrt2;
float dh6 = gtable3(p6, d6) * apx_1inSqrt2;
float dh7 = gtable3(p7, d7) * apx_1inSqrt2;
vec3 w = hermite5(f);
float z0 = mix(mix(dh0, dh1, w.x), mix(dh2, dh3, w.x), w.y);
float z1 = mix(mix(dh4, dh5, w.x), mix(dh6, dh7, w.x), w.y);
float i = mix(z0, z1, w.z);
return i;
}
void main(){
if (aAge >= aLife) {
vec2 noiseCoord = vec2(gl_VertexID % 512, gl_VertexID / 512);
vec2 rand = hash22(noiseCoord);
float theta = uMinTheta + rand.x * (uMaxTheta - uMinTheta);
float x = cos(theta);
float y = sin(theta);
vPosition = uOrigin;
vAge = 0.0;
vLife = aLife;
vVelocity = vec2(x, y) * (uMinSpeed + rand.y * (uMaxSpeed - uMinSpeed));
} else {
vec2 force = 4.0 * vec2(pnoise31(vec3(aPosition, uTime)), pnoise31(vec3(aPosition + 700.0, uTime)));
vPosition = aPosition + aVelocity * uTimeDelta;
vAge = aAge + uTimeDelta;
vLife = aLife;
vVelocity = aVelocity + uGravity * uTimeDelta + force * uTimeDelta;
}
}
update.frag
#version 300 es
precision highp float;
out vec4 fragColor;
void main() {
discard;
}
render.vert
#version 300 es
layout (location = 0) in vec2 aPosition;
layout (location = 1) in float aAge;
layout (location = 2) in float aLife;
out float vAge;
out float vLife;
void main() {
vAge = aAge;
vLife = aLife;
gl_PointSize = 1.0 + 3.0 * (1.0 - aAge / aLife);
gl_Position = vec4(aPosition, 0.0, 1.0);
}
render.frag
#version 300 es
precision highp float;
in float vAge;
in float vLife;
out vec4 fragColor;
vec3 palette(float t, vec3 a, vec3 b, vec3 c, vec3 d) {
return a + b * cos(6.28318 * (c * t + d));
}
void main(){
float t = vAge / vLife;
vec3 outColor = palette(
t,
vec3(0.5, 0.5, 0.5),
vec3(0.5, 0.5, 0.5),
vec3(1.0, 1.0, 1.0),
vec3(0.0, 0.1, 0.2)
);
fragColor = vec4(outColor, 1.0 - t);
}
render.ts
import { SketchGl, type SketchConfig, type SketchFn } from "sketchgl"
import { InterleavedInitialData } from "sketchgl/utility"
import { Timer } from "sketchgl/interactive"
import { SwapTFRenderer } from "sketchgl/renderer"
import { Uniforms } from "sketchgl/program"
import { AliveParticlesSystem } from "@/lib/feature/particle/alive-particle"
import vertForUpdate from "./update.vert?raw"
import fragForUpdate from "./update.frag?raw"
import vertForRender from "./render.vert?raw"
import fragForRender from "./render.frag?raw"
const sketch: SketchFn = ({ gl, canvas }) => {
const particlesCount = 100000
const particleMinAge = 1.01
const particleMaxAge = 5.15
const initialData = new InterleavedInitialData(() => {
const life = particleMinAge + Math.random() * (particleMaxAge - particleMinAge)
return {
vPosition: [0, 0],
vAge: life + 1,
vLife: life,
vVelocity: [0, 0]
}
})
const interleaved = initialData.generate({ count: particlesCount })
const particles = new AliveParticlesSystem(canvas, interleaved.length / 6)
particles.gravity = [0, 0]
const uniformsFor = {
update: new Uniforms(gl, [
"uTime",
"uTimeDelta",
"uGravity",
"uOrigin",
"uMinTheta",
"uMaxTheta",
"uMinSpeed",
"uMaxSpeed"
])
}
const renderer = new SwapTFRenderer(gl, initialData.keys)
renderer.attachUpdateProgram(vertForUpdate, fragForUpdate)
renderer.attachRenderProgram(vertForRender, fragForRender)
renderer.registUpdateAttrib("vPosition", { location: 0, components: 2 })
renderer.registUpdateAttrib("vAge", { location: 1, components: 1 })
renderer.registUpdateAttrib("vLife", { location: 2, components: 1 })
renderer.registUpdateAttrib("vVelocity", { location: 3, components: 2 })
renderer.registRenderAttrib("aPosition", { location: 0, components: 2 })
renderer.registRenderAttrib("aAge", { location: 1, components: 1 })
renderer.registRenderAttrib("aLife", { location: 2, components: 1 })
renderer.setup(new Float32Array(interleaved))
uniformsFor.update.init(renderer.glProgramForUpdate)
const timer = new Timer()
timer.start()
gl.enable(gl.BLEND)
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
gl.clearColor(0.0, 0.0, 0.0, 1.0)
return {
drawOnFrame() {
const deltaTime = timer.elapsed
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height)
renderer.startUpdate()
uniformsFor.update.float("uTime", timer.totalTime)
uniformsFor.update.float("uTimeDelta", deltaTime * 0.001)
uniformsFor.update.fvector2("uGravity", particles.gravity)
uniformsFor.update.fvector2("uOrigin", particles.origin)
uniformsFor.update.float("uMinTheta", particles.minTheta)
uniformsFor.update.float("uMaxTheta", particles.maxTheta)
uniformsFor.update.float("uMinSpeed", particles.minSpeed)
uniformsFor.update.float("uMaxSpeed", particles.maxSpeed)
gl.drawArrays(gl.POINTS, 0, particles.alives)
renderer.endUpdate()
renderer.startRender()
gl.drawArrays(gl.POINTS, 0, particles.alives)
renderer.endRender()
timer.update()
particles.updateForNext(deltaTime)
}
}
}
export const onload = () => {
const config: SketchConfig = {
canvas: {
el: "gl-canvas",
fit: "square",
autoResize: true
}
}
SketchGl.init(config, sketch)
}