Splat shader effects (#141)

* Added particle flow transition to examples

* Added particle flow transition to examples

* Added splat-shader-effects to examples
This commit is contained in:
kali
2025-07-31 03:20:08 -03:00
committed by GitHub
parent a6bdb5fa2c
commit ab83e53579
+235
View File
@@ -0,0 +1,235 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Spark • GLSL Shaders</title>
<style>
body { margin: 0; }
canvas { touch-action: none; }
</style>
</head>
<body>
<script type="importmap">
{
"imports": {
"three": "/examples/js/vendor/three/build/three.module.js",
"lil-gui": "/examples/js/vendor/lil-gui/dist/lil-gui.esm.js",
"@sparkjsdev/spark": "/dist/spark.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { SparkControls, SplatMesh, dyno } from "@sparkjsdev/spark";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
import GUI from "lil-gui";
// Scene setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Cat model setup
const splatURL = await getAssetFileURL("cat.spz");
const cat = new SplatMesh({ url: splatURL });
cat.quaternion.set(1, 0, 0, 0);
cat.position.set(0, 0, -1.5);
cat.scale.set(.5, .5, .5);
scene.add(cat);
const animateT = dyno.dynoFloat(0);
// Effect parameters and GUI
const effectParams = {
effect: "Disintegrate",
intensity: 0.8
};
const gui = new GUI({ title: "Fractal Effects" });
gui.add(effectParams, "effect", ["Electronic", "Deep Meditation", "Waves", "Disintegrate", "Flare"])
.name("Effect Type")
.onChange(() => cat.updateGenerator());
gui.add(effectParams, "intensity", 0, 1, 0.01)
.name("Intensity")
.onChange(() => cat.updateGenerator());
// Shader modifier setup
cat.objectModifier = dyno.dynoBlock(
{ gsplat: dyno.Gsplat },
{ gsplat: dyno.Gsplat },
({ gsplat }) => {
const d = new dyno.Dyno({
inTypes: {
gsplat: dyno.Gsplat,
t: "float",
effectType: "int",
intensity: "float"
},
outTypes: { gsplat: dyno.Gsplat },
globals: () => [
dyno.unindent(`
vec3 hash(vec3 p) {
return fract(sin(p*123.456)*123.456);
}
mat2 rot(float a) {
float s = sin(a), c = cos(a);
return mat2(c, -s, s, c);
}
vec3 headMovement(vec3 pos, float t) {
pos.xy *= rot(smoothstep(-1., -2., pos.y) * .2 * sin(t*2.));
return pos;
}
vec3 breathAnimation(vec3 pos, float t) {
float b = sin(t*1.5);
pos.yz *= rot(smoothstep(-1., -3., pos.y) * .15 * -b);
pos.z += .3;
pos.y += 1.2;
pos *= 1. + exp(-3. * length(pos)) * b;
pos.z -= .3;
pos.y -= 1.2;
return pos;
}
vec4 fractal1(vec3 pos, float t, float intensity) {
float m = 100.;
vec3 p = pos * .1;
p.y += .5;
for (int i = 0; i < 8; i++) {
p = abs(p) / clamp(abs(p.x * p.y), 0.3, 3.) - 1.;
p.xy *= rot(radians(90.));
if (i > 1) m = min(m, length(p.xy) + step(.3, fract(p.z * .5 + t * .5 + float(i) * .2)));
}
m = step(m, 0.5) * 1.3 * intensity;
return vec4(-pos.y * .3, 0.5, 0.7, .3) * intensity + m;
}
vec4 fractal2(vec3 center, vec3 scales, vec4 rgba, float t, float intensity) {
vec3 pos = center;
float splatSize = length(scales);
float pattern = exp(-50. * splatSize);
vec3 p = pos * .65;
pos.y += 2.;
float c = 0.;
float l, l2 = length(p);
float m = 100.;
for (int i = 0; i < 10; i++) {
p.xyz = abs(p.xyz) / dot(p.xyz, p.xyz) - .8;
l = length(p.xyz);
c += exp(-1. * abs(l - l2) * (1. + sin(t * 1.5 + pos.y)));
l2 = length(p.xyz);
m = min(m, length(p.xyz));
}
c = smoothstep(0.3, 0.5, m + sin(t * 1.5 + pos.y * .5)) + c * .1;
return vec4(vec3(length(rgba.rgb)) * vec3(c, c*c, c*c*c) * intensity,
rgba.a * exp(-20. * splatSize) * m * intensity);
}
vec4 sin3D(vec3 p, float t) {
float m = exp(-2. * length(sin(p * 5. + t * 3.))) * 5.;
return vec4(m) + .3;
}
vec4 disintegrate(vec3 pos, float t, float intensity) {
vec3 p = pos + (hash(pos) * 2. - 1.) * intensity;
float tt = smoothstep(-1., 0.5, -sin(t + -pos.y * .5));
p.xz *= rot(tt * 2. + p.y * 2. * tt);
return vec4(mix(p, pos, tt), tt);
}
vec4 flare(vec3 pos, float t) {
vec3 p = vec3(0., -1.5, 0.);
float tt = smoothstep(-1., .5, sin(t + hash(pos).x));
tt = tt * tt;
p.x += sin(t * 2.) * tt;
p.z += sin(t * 2.) * tt;
p.y += sin(t) * tt;
return vec4(mix(pos, p, tt), tt);
}
`)
],
statements: ({ inputs, outputs }) => dyno.unindentLines(`
${outputs.gsplat} = ${inputs.gsplat};
vec3 localPos = ${inputs.gsplat}.center;
vec3 splatScales = ${inputs.gsplat}.scales;
vec4 splatColor = ${inputs.gsplat}.rgba;
if (${inputs.effectType} == 1) {
${outputs.gsplat}.center = headMovement(localPos, ${inputs.t});
vec4 effect1 = fractal1(localPos, ${inputs.t}, ${inputs.intensity});
${outputs.gsplat}.rgba.rgba = mix(splatColor, splatColor*effect1, ${inputs.intensity});
}
else if (${inputs.effectType} == 2) {
vec4 effectColor = fractal2(localPos, splatScales, splatColor, ${inputs.t}, ${inputs.intensity});
${outputs.gsplat}.rgba.rgba = mix(splatColor, effectColor, ${inputs.intensity});
${outputs.gsplat}.center = breathAnimation(localPos, ${inputs.t});
}
else if (${inputs.effectType} == 3) {
vec4 effect = sin3D(localPos, ${inputs.t});
${outputs.gsplat}.rgba.rgba = mix(splatColor, splatColor*effect, ${inputs.intensity});
vec3 pos = localPos;
pos.y += 1.;
pos *= (1. + effect.x * .05 * ${inputs.intensity});
pos.y -= 1.;
${outputs.gsplat}.center = pos;
}
else if (${inputs.effectType} == 5) {
vec4 e = disintegrate(localPos, ${inputs.t}, ${inputs.intensity});
${outputs.gsplat}.center = e.xyz;
${outputs.gsplat}.scales = mix(vec3(.01, .01, .01), ${inputs.gsplat}.scales, e.w);
}
else if (${inputs.effectType} == 4) {
vec4 e = flare(localPos, ${inputs.t});
${outputs.gsplat}.center = e.xyz;
${outputs.gsplat}.rgba.rgb = mix(splatColor.rgb, vec3(1.), abs(e.w));
${outputs.gsplat}.rgba.a = mix(splatColor.a, 0.3, abs(e.w));
}
`),
});
// Map effect names to type integers
const effectTypeMap = {
"Electronic": 1,
"Deep Meditation": 2,
"Waves": 3,
"Flare": 4,
"Disintegrate": 5
};
const effectType = effectTypeMap[effectParams.effect] || 1;
gsplat = d.apply({
gsplat,
t: animateT,
effectType: dyno.dynoInt(effectType),
intensity: dyno.dynoFloat(effectParams.intensity)
}).gsplat;
return { gsplat };
}
);
cat.updateGenerator();
// Animation loop
const controls = new SparkControls({ canvas: renderer.domElement });
renderer.setAnimationLoop(function animate(time) {
animateT.value = time / 1000;
cat.updateVersion();
cat.position.set(0, -.7, -2.5);
cat.rotation.set(Math.PI, time / 2000, 0);
controls.update(camera);
renderer.render(scene, camera);
});
</script>
</body>
</html>