React Three Fiberでパーティクルシステムを実装 - 3D空間での動的表現

2025/5/31

このサイトはアフィリエイト広告を利用しています。

目次

完成サンプル

はじめに

WebGLを活用した3D表現において、パーティクルシステムは動的で魅力的な視覚効果を生み出す重要な技術です。本記事では、React Three Fiberを使用してパーティクルシステムを実装する方法を、実際のコード例とともに詳しく解説します。

パーティクルシステムの基本概念から、GPU最適化やInstancedMeshを活用したパフォーマンス向上のテクニックまで、実用的な知識を体系的にお伝えします。

パーティクルシステムとは?

基本概念

パーティクルシステムとは、大量の小さなオブジェクト(パーティクル)を制御して、複雑な視覚効果を表現する技術です。以下のような効果の実現に使用されます:

React Three Fiberでのアプローチ

従来のThree.jsではpointspointsMaterialを使用してパーティクルを表現しますが、React Three Fiberでは***InstancedMeshを活用することで、より効率的で制御しやすいパーティクルシステム***を構築できます。

従来の手法 vs InstancedMesh:

手法描画コール数制御性パフォーマンス
points + pointsMaterial少ない限定的良い(静的)
InstancedMesh1回高い非常に良い

React Three Fiberでの実装

基本的なパーティクルコンポーネント

以下は、InstancedMeshを使用した効率的なパーティクルシステムの実装例です:

import React, { useMemo, useRef, useEffect } from 'react'
import { Canvas, useFrame } from '@react-three/fiber'
import * as THREE from 'three'
function Particles({ count = 1000 }) {
const mesh = useRef<THREE.InstancedMesh>(null!)
const dummy = useMemo(() => new THREE.Object3D(), [])
const particles = useMemo(() => {
return Array.from({ length: count }, () => ({
position: new THREE.Vector3(
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10
),
velocity: new THREE.Vector3(
(Math.random() - 0.5) * 0.01,
(Math.random() - 0.5) * 0.01,
(Math.random() - 0.5) * 0.01
)
}))
}, [count])
// 初期マトリックスの設定
useEffect(() => {
if (mesh.current) {
particles.forEach((particle, i) => {
dummy.position.copy(particle.position)
dummy.updateMatrix()
mesh.current.setMatrixAt(i, dummy.matrix)
})
mesh.current.instanceMatrix.needsUpdate = true
}
}, [particles, dummy])
useFrame(() => {
if (mesh.current) {
particles.forEach((particle, i) => {
// パーティクルの移動
particle.position.add(particle.velocity)
// 境界での反射
if (Math.abs(particle.position.x) > 5) particle.velocity.x *= -1
if (Math.abs(particle.position.y) > 5) particle.velocity.y *= -1
if (Math.abs(particle.position.z) > 5) particle.velocity.z *= -1
// マトリックスの更新
dummy.position.copy(particle.position)
dummy.updateMatrix()
mesh.current.setMatrixAt(i, dummy.matrix)
})
mesh.current.instanceMatrix.needsUpdate = true
}
})
return (
<instancedMesh ref={mesh} args={[undefined, undefined, count]}>
<sphereGeometry args={[0.05, 8, 8]} />
<meshBasicMaterial color="cyan" />
</instancedMesh>
)
}
export function ParticleSystemDemo({ count = 500 }) {
return (
<div className="w-full h-96 bg-gray-900 rounded-lg overflow-hidden">
<Canvas camera={{ position: [0, 0, 8] }}>
<Particles count={count} />
</Canvas>
</div>
)
}

MDXファイルでの使用方法

作成したコンポーネントは、MDXファイルで以下のように使用できます:

import { ParticleSystemDemo } from '../components/WebGL/ParticleSystem.tsx'
<ParticleSystemDemo count={200} client:load />

パフォーマンス最適化の重要ポイント

InstancedMeshによる描画最適化

InstancedMeshの最大の利点は、数千個のオブジェクトを単一のドローコールで描画できることです。これにより、GPUの負荷を大幅に削減できます。

// ❌ 効率が悪い例(各パーティクルが個別のメッシュ)
{particles.map((particle, i) => (
<mesh key={i} position={particle.position}>
<sphereGeometry args={[0.05, 8, 8]} />
<meshBasicMaterial color="cyan" />
</mesh>
))}
// ✅ 効率的な例(InstancedMesh使用)
<instancedMesh ref={mesh} args={[undefined, undefined, count]}>
<sphereGeometry args={[0.05, 8, 8]} />
<meshBasicMaterial color="cyan" />
</instancedMesh>

メモリ管理とオブジェクトプーリング

大規模なパーティクルシステムでは、メモリ効率の最適化が重要です:

function OptimizedParticles({ count = 10000 }) {
const mesh = useRef<THREE.InstancedMesh>(null!)
// オブジェクトプーリング: 再利用可能なオブジェクトを事前作成
const dummy = useMemo(() => new THREE.Object3D(), [])
const tempVector = useMemo(() => new THREE.Vector3(), [])
// メモリ効率的なデータ構造
const particleData = useMemo(() => {
const positions = new Float32Array(count * 3)
const velocities = new Float32Array(count * 3)
for (let i = 0; i < count; i++) {
const i3 = i * 3
positions[i3] = (Math.random() - 0.5) * 20
positions[i3 + 1] = (Math.random() - 0.5) * 20
positions[i3 + 2] = (Math.random() - 0.5) * 20
velocities[i3] = (Math.random() - 0.5) * 0.02
velocities[i3 + 1] = (Math.random() - 0.5) * 0.02
velocities[i3 + 2] = (Math.random() - 0.5) * 0.02
}
return { positions, velocities }
}, [count])
return (
<instancedMesh ref={mesh} args={[undefined, undefined, count]}>
<sphereGeometry args={[0.03, 6, 6]} />
<meshBasicMaterial color="orange" />
</instancedMesh>
)
}

💪 パフォーマンス最適化のコツ

検索結果によると、パーティクル数が1000個を超える場合は、TypedArrayを使用してメモリアクセスを最適化し、フレームレートの監視を行うことが推奨されています。

高度な実装パターン

LOD(Level of Detail)対応

カメラからの距離に応じてパーティクルの詳細度を調整することで、パフォーマンスを大幅に向上できます:

function LODParticles({ count = 5000 }) {
const mesh = useRef<THREE.InstancedMesh>(null!)
const { camera } = useThree()
useFrame(() => {
if (mesh.current) {
particles.forEach((particle, i) => {
const distance = camera.position.distanceTo(particle.position)
// 距離に応じてスケールを調整
let scale = 1
if (distance > 20) scale = 0.5 // 遠距離: 小さく
else if (distance > 10) scale = 0.7 // 中距離: やや小さく
else scale = 1 // 近距離: 標準サイズ
dummy.position.copy(particle.position)
dummy.scale.setScalar(scale)
dummy.updateMatrix()
mesh.current.setMatrixAt(i, dummy.matrix)
})
mesh.current.instanceMatrix.needsUpdate = true
}
})
// 実装...
}

BufferGeometry活用パターン

より高度な制御が必要な場合は、BufferGeometryとカスタム属性を活用できます:

function CustomBufferParticles({ count = 2000 }) {
const points = useRef<THREE.Points>(null!)
const { positions, colors } = useMemo(() => {
const positions = new Float32Array(count * 3)
const colors = new Float32Array(count * 3)
for (let i = 0; i < count; i++) {
const i3 = i * 3
// ランダムな位置
positions[i3] = (Math.random() - 0.5) * 20
positions[i3 + 1] = (Math.random() - 0.5) * 20
positions[i3 + 2] = (Math.random() - 0.5) * 20
// HSL色空間でランダムな色
const hue = Math.random()
const color = new THREE.Color().setHSL(hue, 0.8, 0.6)
colors[i3] = color.r
colors[i3 + 1] = color.g
colors[i3 + 2] = color.b
}
return { positions, colors }
}, [count])
return (
<points ref={points}>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={count}
array={positions}
itemSize={3}
/>
<bufferAttribute
attach="attributes-color"
count={count}
array={colors}
itemSize={3}
/>
</bufferGeometry>
<pointsMaterial size={0.1} vertexColors />
</points>
)
}

実用的な応用例

wawa-vfx エンジンの活用

より高度なVFXが必要な場合は、専用のパーティクルエンジンも利用できます:

Terminal window
npm install wawa-vfx
import { BatchedRenderer, QuarksLoader } from 'wawa-vfx'
function AdvancedVFXParticles() {
const [batchRenderer] = useState(() => new BatchedRenderer())
const { scene } = useThree()
useEffect(() => {
const loader = new QuarksLoader()
loader.load('./particle-config.json', (obj) => {
obj.traverse((child) => {
if (child.type === 'ParticleEmitter') {
batchRenderer.addSystem(child.system)
}
})
scene.add(obj)
})
scene.add(batchRenderer)
}, [scene, batchRenderer])
useFrame((state, delta) => {
batchRenderer.update(delta)
})
return null
}

トラブルシューティング

よくある問題と解決策

問題原因解決方法
フレームレート低下パーティクル数過多LOD実装、動的パーティクル数調整
メモリリークオブジェクト再作成useMemoでオブジェクトプーリング
描画エラーマトリックス更新忘れinstanceMatrix.needsUpdate = true

⚠️ パフォーマンス監視

検索結果によると、複数のCanvasを同時に使用するとパフォーマンスが大幅に低下します。可能な限り、単一のCanvasで複数のパーティクルシステムを管理することを推奨します。

まとめ

React Three Fiberを使用したパーティクルシステムの実装により、効率的で視覚的に魅力的な3D表現が可能になります。本記事で解説した以下のポイントを押さえることで、実用的なパーティクルシステムを構築できます:

パーティクルシステムは、WebGL技術の中でも特に創造性とパフォーマンスのバランスが重要な分野です。基本的な実装から始めて、徐々に複雑な表現に挑戦していくことで、より豊かな3D体験を提供できるようになります。


出典

公式リソース(Official Resources)

参考サイト(Reference Sites)

この記事をシェアする