はじめに
Reddit でモデルを粒子化するデモ動画があり、興味を持ったので真似て実装してみました。
コードも公開されてるようですが、そちらは見てないです。(本家の方が多機能)
今回もインスペクタを付けて、動的にシェーダーの変数値を変更/更新できるようにしてます。
Next.jsのプロジェクト内で作成してますが、ほぼバニラ Three.js で動かしてるので、JavaScriptカテゴリにしようかと思いましたが、今後もデフォルトのThree.jsでアプリ制作する事が増えると思うので、Next.jsにしてます。
アイデアと技術スタックを事前にAIと相談して、たたき台を作って、そこからカスタムして完成させていますが、モデルを粒子化させてそれを動かすだけなのでコード量はそんなに多くないです。
モデルデータはここからお借りしています。
制作者:printable_models 様
スカルv3
スカルv3 3Dモデル
https://free3d.com/ja/3d-model/skull-v3--785914.html静止画:
動画:
3Dモデルを PointCloud 化して GLSL 揺らぎを追加するデモ#threejs #javascript #glsl #shader #lil-gui
OBJモデルを粒子として再構築し、GLSLで揺らぎアニメーションを付けたデモ。▼ 使用技術・Next.js (App Router)・Three.js ・OBJ + MTL・PointCloud(THREE.Points)・ShaderMaterial(GLSL / Vertex-Fragment)・lil-gu...
https://www.youtube.com/shorts/qIxA_hXaiW4前回の記事:
[Next.js #12] R3Fに“インスペクタ”を付ける:Levaで雲・水・ライトをリアルタイム調整
Next.js + React-Three-Fiber で構築した“世界”にインスペクタ機能を追加し、雲のスピード・色、ライト強度、地形スケール、水の揺らぎといったパラメータをリアルタイムに変更できるようにする。Leva …
https://humanxai.info/posts/nextjs-12-r3f-leva-inspector/1. Mesh を Point Cloud に変換する理由
3D モデルを 粒子(Point Cloud) で再構成すると、 通常の Mesh では絶対に出せない 生き物のような“揺らぎ” を作れる。
粒子化のメリットは明確で、特に WebGL で効く。
メリット
- モデルのシルエットを保ったまま動かせる → Mesh の頂点変形より直感的で、破綻が起きにくい。
- シェーダーで自由に変形できる → sin・noise・FBM などを直接ぶち込める。
- GPU で全部処理できて軽い → Point Cloud は WebGL でも高速に動く。
- 揺らぎ・浮遊感が簡単に作れる → アート表現と相性が最高。
今回の記事では、
OBJ 読み込み → 粒子化 → GLSL アニメーション → モデル表示切替(Points / Mesh)
ここまでを、Vanilla Three.js(Next.js 内)で再現。
2. 概要:処理パイプライン
今回実装した粒子化システムは、非常にシンプルな 1本道のパイプラインで成立している。
OBJ / MTL 読み込み
↓
Geometry の頂点を basePosition として複製
↓
size / color / amplitude などの属性を付与
↓
ShaderMaterial + Points で描画
↓
GLSL(Vertex Shader)側で粒子を揺らす
↓
GUI(lil-gui)でパラメータをリアルタイム調整
↓
"Original / Particles" の表示切り替え
これは Point Cloud アニメーションの定番構成で、 WebGL / Three.js / R3F / Unity Shader Graph すべて同じ発想で応用できる。
特に今回のポイントは:
- basePosition(元の頂点)を複製して保持すること → 粒子を揺らしても、モデル形状が崩壊しない。
- size / color などの属性を BufferAttribute で追加 → 全粒子を GPU だけで処理できる。
- sin による擬似ノイズ揺らぎ → 今後 FBM / Perlin / Worley に簡単に差し替え可能。
- GUI でアーティスト作業が一気に快適になる
このパイプラインを理解しておけば、 次のステップで ノイズ揺らぎ / 爆散 / 流体風 / 軌跡 / Dissolve などに即応用できる。
3. basePosition を持たせる理由
モデル形状を壊さず、各粒子だけを揺らしたりアニメさせたいなら、
basePosition を必ず保持する必要がある。 OBJ + MTL の注意
ポイントはこれ:
geometry.attributes.position // GPU が描画に使う「現在の位置」
basePosition // 元のモデル形状をそのまま保存
offset // シェーダーで加算するアニメ量
pos = basePosition + offset;
なぜ basePosition が必須?
basePosition がないと —— 粒子は自分の“元の居場所”を忘れる。
つまり:
- アニメーションするほど 原点へ吸い込まれる
- 全体が ボコボコに崩れて形状が消える
- “粒子モデル”ではなく、ただの ノイズの塊になる
逆に basePosition を持っていれば:
形状維持しながら揺らせる
(シルエットがそのまま残る)
粒子単位で自由に動かせる
(sin / noise / FBM / curl すべて OK)
モデルの「ボリューム」を保てる
(雲・煙・バラバラ分解・波紋などの演出が可能)
4. Shader の核心
Vertex のコア部分(粒子の揺らぎ)
attribute vec3 basePosition;
attribute float size;
uniform float uTime;
uniform float uAmplitude;
uniform float uFrequency;
uniform float uSpeed;
void main() {
float t = uTime * uSpeed;
vec3 offset = vec3(
sin(t + basePosition.x * uFrequency),
sin(t + basePosition.y * uFrequency * 1.3),
sin(t + basePosition.z * uFrequency * 0.7)
) * uAmplitude;
vec3 pos = basePosition + offset;
gl_PointSize = size;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
なぜ揺らぐのか
1)basePosition をそのまま使う
→ モデルの形が崩れない。 → 粒子が“元の位置を中心に”動く。
2)x / y / z で周波数を変える
→ 単調な波にならず、“有機的な揺れ”が生まれる。
3)sin() だけで十分 “生き物感” が出る
もしもっと自然にしたいなら、 後で FBM / Perlin / Curl Noise を混ぜれば “プロの揺らぎ” になる。
この1ブロックを書ければ応用無限
- モデルの分解演出
- ワイヤーフレームの震え
- 霧・雲の粒子化
- 爆散・再構築エフェクト
- ポリゴンから点群への遷移アニメ
全部この “basePosition + offset” の変形で実現できる。
5. Fragment:点スプライトのフェード表現
float d = length(gl_PointCoord - vec2(0.5));
float alpha = smoothstep(0.5, 0.0, d);
gl_FragColor = vec4(uColor, alpha);
何をしているか
1)gl_PointCoord:点スプライト内のUV(0〜1)
→ vec2(0.5) を中心として距離 d を作る。
2)smoothstep(0.5 → 0.0):外側をフェードアウト → 粒子が“丸く”見える。 → 縁が滑らかになるので Additive Blend との相性が最強。
なぜ丸く見えるのか?
smoothstep が “距離 0.5 付近から透明になる” 境界を作るから。
距離 d が小さい → 中心 → 不透明
距離 d が大きい → 外側 → 透明
つまり GLSL 内だけで 丸いテクスチャを生成している。
テクスチャ不要。 画像もいらない。 これが「GPU粒子の基本形」。
応用:プロっぽい粒子にする方法
ここからただの点ではなく、演出になる:
pow(alpha, 2.0)→ 柔らかい発光- 色を
mix(color1, color2, alpha)で変化 uTimeを混ぜて “揺れる発光”- 中心を明るくし、外側を青に → 雲/魔法/火花になる
6. lil-gui でリアルタイム調整
今回コントロールできるのは次の 6 パラメータ。
▶ size (粒子サイズ)
▶ amplitude (揺れの大きさ)
▶ frequency (揺れの周波数)
▶ speed (アニメ速度)
▶ color (発光色)
▶ showOriginal (モデル / 粒子 表示切替)
size / amplitude / frequency / speed はすべて Shader の uniform に直結しており、
値を動かすと 即座に粒子アニメーションに反映される。
showOriginal は「元の OBJ モデル」と「粒子シェーダー版」を
Group ごとに visible 切り替えするだけなので軽い。
UI スクリーンショットを貼るだけで十分
- lil-gui は Next.js / Vanilla どちらでも動く
- 自作エディタのようにモデルを“調整しながら楽しめる”
- uniform の変化をリアルタイムで確認できるため学習効果も高い
記事では UI のキャプチャ + 最低限の解説だけで成立する。
7. OBJ + MTL の注意点
OBJ/MTL は古い形式だけど、 “点群化(PointCloud 化)する用途では最強クラスに扱いやすい”。
理由はこれ。
OBJ の利点
1. Geometry が素直(壊れにくい)
OBJ は 純粋な頂点+法線+UV だけで構成されているので、
geometry.attributes.position がそのまま “モデル形状そのもの”。
粒子化するときに必須の:
basePosition = geometry.attributes.position
が 変換なしで使える。
2. スケールの謎補正が発生しない
GLB はシーン階層が複雑で、親ノードにスケールがかかっていたりする。
例:
Scene
└─ Armature (scale: 100)
└─ Mesh (scale: 1)
この構造のまま点群化すると、 突然モデルが巨大化・崩壊・中心がズレる など混乱しやすい。
OBJ は階層構造ほぼ無し → 点群化が安全。
3. ウェイト・ボーンが無いので安心
GLB は humanoid の場合ボーン階層が必ず付いてくる。 点群化では不要な情報なので、OBJ の方がクリーン。
MTL の注意点
MTL(マテリアルファイル)はテクスチャパスだけ記録しているため:
- パスがズレると真っ黒になる
- ローダーの
materials.preload()が必須 - Ambient / Diffuse の影響を強く受ける
今回は 粒子化が本体なので、 “元モデルの見た目が多少崩れても OK” という前提で使用。
今回 OBJ を使った理由(結論)
✔ 頂点さえあれば良い
✔ 階層の複雑さが無い
✔ 値が破壊されにくい
✔ WebGL の PointCloud との相性が最強
GLB はアニメ・リグ・ウェイトが必要な場合は最強。
だが “点群化” には OBJ が一番安全。
8. Original / Particles の切り替え
粒子化すると「元の形状と比べたい」場面が必ず出てくる。 そこで、モデル(Mesh)と粒子(Points)を 1 ボタンで切り替えできるようにした。
originalGroup.visible = params.showModel;
particleGroup.visible = !params.showModel;
showModel は lil-gui のチェックボックスに連動。
この仕組みのメリット
- 粒子化しても 輪郭が正しく保たれているか確認できる
- basePosition のズレ / スケール破壊の デバッグが一瞬で終わる
- 記事の読者が「元のモデルと比較」しやすい
- 実際の作品でも “変形アニメ → 元形状に戻す” に応用できる
設計の基本は「Group を分ける」こと
scene
├─ originalGroup (普通の Mesh)
└─ particleGroup (PointCloud + Shader)
Mesh と Points を混ぜないことで、 visible の ON/OFF だけで高速・安全に切り替えができる。
9. まとめ:Mesh は “構造”、Particles は “魂” を動かす
3Dモデルをそのまま動かすより、 粒子に置き換えた瞬間、モデルは“生命感”を持つ。
理由はシンプルで、粒子は:
- シルエットを維持したまま動く
- 1点ずつ別々の時間・揺らぎを持てる
- Shader が全てを支配する(=応用無限)
- 軽量で Web でも強い
つまり、 Mesh = 形状(Structure) Particles = 動きの魂(Spirit)
という明確な分業になる。
粒子化すると、表現の“幅”が一気に広がる
- モデルの輪郭を波で崩す
- 爆散 → 再構築(再生演出)
- 音に合わせて振動させる VJ 的表現
- 魔法陣 / 変身エフェクト
- WebXR でも軽く動く
普通の Mesh では絶対に出せない世界。
今回やったことの価値
OBJ/MTL 読み込み → 点群化 → basePosition → Shader揺らぎ → lil-gui でリアルタイム制御 → Original/Particles の切り替え
これは、WebGL の中でも “応用テク” に分類される領域で、 Three.js という枠を一段超えた 実装と言ってよい。
💬 コメント