パーティクルが集まって、画像のシルエットになる(しょぼい)
シルエット画像のダウロード
【https://www.cleanpng.com/free/silhouette-car.html】
CDN
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.175.0/build/three.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
Scene / PerspectiveCamera / WebGLRenderer
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 500;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
ブラウザ幅のリサイズ対応
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
Canvasで画像を読み取って粒子配置のポイントを取得します
const image = new Image();
image.src = 'https://8ch.tech/wp-content/uploads/2025/04/car.png';
image.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
const imageData = ctx.getImageData(0, 0, image.width, image.height).data;
const points = [];
for (let y = 0; y < image.height; y += 2) {
for (let x = 0; x < image.width; x += 2) {
const i = (y * image.width + x) * 4;
const alpha = imageData[i + 3];
if (alpha > 128) {
points.push({
x: x - image.width / 2,
y: -(y - image.height / 2),
z: Math.random() * 20 - 10, // ランダムな奥行き
});
}
}
}
createParticles(points);
パーティクルの作成
function createParticles(points) {
const geometry = new THREE.BufferGeometry();
const particleCount = points.length;
const positions = new Float32Array(particleCount * 3);
const targets = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount; i++) {
// 初期位置はランダム
positions[i * 3] = (Math.random() - 0.5) * 800;
positions[i * 3 + 1] = (Math.random() - 0.5) * 800;
positions[i * 3 + 2] = (Math.random() - 0.5) * 800;
// 目標位置(動物の形)
targets[i * 3] = points[i].x;
targets[i * 3 + 1] = points[i].y;
targets[i * 3 + 2] = points[i].z;
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('target', new THREE.BufferAttribute(targets, 3));
const material = new THREE.PointsMaterial({
size: 2,
color: 0xffffff
});
const particles = new THREE.Points(geometry, material);
scene.add(particles);
animateParticles(particles);
}
パーティクルが目標地点に近づくアニメーション
function animateParticles(particles) {
const pos = particles.geometry.attributes.position;
const tgt = particles.geometry.attributes.target;
function animate() {
requestAnimationFrame(animate);
for (let i = 0; i < pos.count; i++) {
const ix = i * 3;
for (let j = 0; j < 3; j++) {
const delta = tgt.array[ix + j] - pos.array[ix + j];
pos.array[ix + j] += delta * 0.05; // だんだん近づける
}
}
pos.needsUpdate = true;
renderer.render(scene, camera);
}
animate();
}
}; <--- image.onload = () => {} の }