Skip to content

Commit

Permalink
[Dev] lesson 40: finish
Browse files Browse the repository at this point in the history
  • Loading branch information
trixky authored and trixky committed Aug 11, 2024
1 parent 440fd39 commit e99958e
Show file tree
Hide file tree
Showing 6 changed files with 369 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/lib/components/layout/header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
const skiped_lessons: number[] = [1, 2, 13, 26];
const finished_lessons: number[] = [
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39
25, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40
];
</script>

Expand Down
12 changes: 12 additions & 0 deletions src/lib/shaders/morphing/fragment.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
varying vec3 vColor;

void main()
{
vec2 uv = gl_PointCoord;
float distanceToCenter = length(uv - 0.5);
float alpha = 0.05 / distanceToCenter - 0.1;

gl_FragColor = vec4(vColor, alpha);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
71 changes: 71 additions & 0 deletions src/lib/shaders/morphing/simplex.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Simplex 3D Noise
// by Ian McEwan, Ashima Arts
//
vec4 permute(vec4 x){ return mod(((x*34.0)+1.0)*x, 289.0); }
vec4 taylorInvSqrt(vec4 r){ return 1.79284291400159 - 0.85373472095314 * r; }

float simplexNoise3d(vec3 v)
{
const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);

// First corner
vec3 i = floor(v + dot(v, C.yyy) );
vec3 x0 = v - i + dot(i, C.xxx) ;

// Other corners
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 - g;
vec3 i1 = min( g.xyz, l.zxy );
vec3 i2 = max( g.xyz, l.zxy );

// x0 = x0 - 0. + 0.0 * C
vec3 x1 = x0 - i1 + 1.0 * C.xxx;
vec3 x2 = x0 - i2 + 2.0 * C.xxx;
vec3 x3 = x0 - 1. + 3.0 * C.xxx;

// Permutations
i = mod(i, 289.0 );
vec4 p = permute( permute( permute( i.z + vec4(0.0, i1.z, i2.z, 1.0 )) + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));

// Gradients
// ( N*N points uniformly over a square, mapped onto an octahedron.)
float n_ = 1.0/7.0; // N=7
vec3 ns = n_ * D.wyz - D.xzx;

vec4 j = p - 49.0 * floor(p * ns.z *ns.z); // mod(p,N*N)

vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N)

vec4 x = x_ *ns.x + ns.yyyy;
vec4 y = y_ *ns.x + ns.yyyy;
vec4 h = 1.0 - abs(x) - abs(y);

vec4 b0 = vec4( x.xy, y.xy );
vec4 b1 = vec4( x.zw, y.zw );

vec4 s0 = floor(b0)*2.0 + 1.0;
vec4 s1 = floor(b1)*2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));

vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;

vec3 p0 = vec3(a0.xy,h.x);
vec3 p1 = vec3(a0.zw,h.y);
vec3 p2 = vec3(a1.xy,h.z);
vec3 p3 = vec3(a1.zw,h.w);

// Normalise gradients
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;

// Mix final noise value
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
m = m * m;
return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3) ) );
}
41 changes: 41 additions & 0 deletions src/lib/shaders/morphing/vertex.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include ./simplex.glsl // simplexNoise3d

attribute vec3 aPositionTarget;
attribute float aSize;

uniform vec2 uResolution;
uniform float uSize;
uniform float uProgress;
uniform vec3 uColorA;
uniform vec3 uColorB;

varying vec3 vColor;

void main()
{
// Mixed position
float noiseOrigin = simplexNoise3d(position * 0.2);
float noiseTarget = simplexNoise3d(aPositionTarget * 0.2);
float noise = mix(noiseOrigin, noiseTarget, uProgress);
noise = smoothstep(-1.0, 1.0, noise);

float duration = 0.4;
float delay = (1.0 - duration) * noise;
float end = delay + duration;

float progress = smoothstep(delay, end, uProgress);
vec3 MixedPosition = mix(position, aPositionTarget, progress);

// Final position
vec4 modelPosition = modelMatrix * vec4(MixedPosition, 1.0);
vec4 viewPosition = viewMatrix * modelPosition;
vec4 projectedPosition = projectionMatrix * viewPosition;
gl_Position = projectedPosition;

// Point size
gl_PointSize = aSize * uSize * uResolution.y;
gl_PointSize *= (1.0 / - viewPosition.z);

// Varying
vColor = mix(uColorA, uColorB, noise);
}
244 changes: 244 additions & 0 deletions src/routes/lessons/40/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
<!-- ================================================= SCRIPT -->
<script lang="ts">
import { browser } from "$app/environment";
import { onMount } from "svelte";
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/addons/loaders/DRACOLoader.js";
import GUI from "lil-gui";
import gsap from "gsap";
import particlesVertexShader from "$lib/shaders/morphing/vertex.glsl";
import particlesFragmentShader from "$lib/shaders/morphing/fragment.glsl";
import { base } from "$app/paths";
let canvas: any = null;
const WIDTH = 800;
const HEIGHT = 600;
const RATIO = WIDTH / HEIGHT;
const debugObject = {
clearColor: "#160920",
};
let gui: GUI | null = null;
function start() {
gui = new GUI();
const pixelRatio = Math.min(window.devicePixelRatio, 2);
// Loaders
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath(base + "/draco/");
const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader);
// Scene
const scene = new THREE.Scene();
// Camera
const camera = new THREE.PerspectiveCamera(35, RATIO, 0.1, 100);
camera.position.set(0, 0, 8 * 2);
scene.add(camera);
// Camera controls
const controls = new OrbitControls(camera, canvas);
controls.enableDamping = true;
// Renderer
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setPixelRatio(pixelRatio);
renderer.setSize(WIDTH, HEIGHT);
gui!.addColor(debugObject, "clearColor").onChange(() => {
renderer.setClearColor(debugObject.clearColor);
});
renderer.setClearColor(debugObject.clearColor);
renderer.render(scene, camera);
// Load models
gltfLoader.load(base + "/morphing/models.glb", (gltf) => {
/**
* Particles
*/
const particles = {
geometry: null as THREE.BufferGeometry | null,
material: null as THREE.ShaderMaterial | null,
points: null as THREE.Points | null,
positions: [] as Array<THREE.BufferAttribute>,
index: 0,
morph: (index: number) => {
if (particles.geometry !== null && particles.material !== null) {
// Update attributes
particles.geometry.attributes.position =
particles.positions[particles.index];
particles.geometry.attributes.aPositionTarget =
particles.positions[index];
// Animate uProgress
gsap.fromTo(
particles.material.uniforms.uProgress,
{ value: 0 },
{ value: 1 }
);
particles.index = index;
}
},
colorA: "#ff7300",
colorB: "#0091ff",
};
// Morph functions animations
const morphFunctions = {
"Morph 1": () => particles.morph(0),
"Morph 2": () => particles.morph(1),
"Morph 3": () => particles.morph(2),
"Morph 4": () => particles.morph(3),
};
gui!.add(morphFunctions, "Morph 1");
gui!.add(morphFunctions, "Morph 2");
gui!.add(morphFunctions, "Morph 3");
gui!.add(morphFunctions, "Morph 4");
const positions = (gltf.scene.children as Array<THREE.Mesh>).map(
(child) => child.geometry.attributes.position
) as Array<THREE.BufferAttribute>;
const maxPositionCount = Math.max(
...positions.map((position) => position.count)
);
for (const position of positions) {
const originalArray = position.array;
const newArray = new Float32Array(maxPositionCount * 3);
for (let i = 0; i < maxPositionCount; i++) {
const i3 = i * 3;
if (i3 < originalArray.length) {
newArray[i3] = originalArray[i3];
newArray[i3 + 1] = originalArray[i3 + 1];
newArray[i3 + 2] = originalArray[i3 + 2];
} else {
const randomIndex = Math.floor(position.count * Math.random()) * 3;
newArray[i3] = originalArray[randomIndex];
newArray[i3 + 1] = originalArray[randomIndex + 1];
newArray[i3 + 2] = originalArray[randomIndex + 2];
}
}
particles.positions.push(new THREE.Float32BufferAttribute(newArray, 3));
}
// Geometry
const sizesArray = new Float32Array(maxPositionCount)
.fill(0)
.map(() => Math.random());
particles.geometry = new THREE.BufferGeometry();
particles.geometry.setAttribute(
"position",
particles.positions[particles.index]
);
particles.geometry.setAttribute(
"aPositionTarget",
particles.positions[3]
);
particles.geometry.setAttribute(
"aSize",
new THREE.BufferAttribute(sizesArray, 1)
);
particles.geometry.setIndex(null);
// Material
particles.material = new THREE.ShaderMaterial({
vertexShader: particlesVertexShader,
fragmentShader: particlesFragmentShader,
uniforms: {
uSize: new THREE.Uniform(0.4),
uResolution: new THREE.Uniform(
new THREE.Vector2(WIDTH * pixelRatio, HEIGHT * pixelRatio)
),
uProgress: new THREE.Uniform(0),
uColorA: new THREE.Uniform(new THREE.Color(particles.colorA)),
uColorB: new THREE.Uniform(new THREE.Color(particles.colorB)),
},
// transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false,
});
// Points
particles.points = new THREE.Points(
particles.geometry,
particles.material
);
// NEW frustumCulled property is set to the first geometry
// to update the boundingSphere and/or fix the bug if we change the geometry (3 solutions):
// 1) we can deactivate the frustumCulled property
particles.points.frustumCulled = false;
// 2) we can update the boundingSphere property directly in the geometry property
// setTimeout(() => {
// console.log(particles.points!.geometry.boundingSphere);
// particles.points!.geometry.boundingSphere!.radius = 15;
// }, 1000);
// 3) we can ask to three.js to update the boundingSphere property but it's not performant
scene.add(particles.points);
// Tweak
gui!
.add(particles.material.uniforms.uSize, "value")
.min(0)
.max(0.8)
.step(0.01)
.name("Size");
gui!
.add(particles.material.uniforms.uProgress, "value")
.min(0)
.max(1)
.step(0.01)
.name("Progress")
.listen();
gui!
.addColor(particles, "colorA")
.name("Color A")
.onChange(() => {
particles.material!.uniforms.uColorA.value.set(particles.colorA);
});
gui!
.addColor(particles, "colorB")
.name("Color B")
.onChange(() => {
particles.material!.uniforms.uColorB.value.set(particles.colorB);
});
});
// Clock
const clock = new THREE.Clock();
// Animation
const tick = () => {
const elapsedTime = clock.getElapsedTime();
controls.update();
renderer.render(scene, camera);
window.requestAnimationFrame(tick);
};
tick();
}
onMount(() => {
if (browser) {
start();
return () => {
gui?.destroy();
};
}
});
</script>

<!-- ================================================= CONTENT -->
<canvas bind:this={canvas} width={WIDTH} height={HEIGHT}></canvas>

<!-- ================================================= CSS -->
<style lang="postcss">
</style>
Binary file added static/morphing/models.glb
Binary file not shown.

0 comments on commit e99958e

Please sign in to comment.