3D Graph機能の要点
従来の「リスト」や「グリッド」による記事表示から脱却し、記事間の関連性を「距離」で表現する3D空間ナビゲーションを実装しました。
- • 自作の3D物理エンジンによるGPUフレンドリーな反発・引力計算
- • BufferGeometryの動的更新による、100以上のノードとエッジのリアルタイム同期
- • MeshPhysicalMaterial を活用した、ガラスのような透明感と屈折の表現
- • Astro 5 Server Islandsによる、重厚なWebGL世界の部分的ハイドレーション
Drag to rotate • Scroll to zoom
「ブログを読む」という体験は、なぜ常に上から下へのスクロールなのでしょうか?
人間の脳は、情報をリスト(箇条書き)ではなく、互いに結びつき合った「ネットワーク」として記憶しています。 ならば、ブログのインターフェースも脳の構造に近づけるべきではないか——そんな仮説から、私が個人開発しているブログ「HonoGear」に実装したのが、この 「3D Graph」 です。
技術スタック:モダンWebGLの組み合わせ
この3D空間は、以下の技術スタックを組み合わせて構築されています。
- Framework : Astro 5 (Server Islands)
- Library : React 19 + React Three Fiber (R3F)
- Physics : Hand-crafted Physics Engine (Custom repulsion/attraction)
- Visuals : @react-three/drei, @react-three/postprocessing (Bloom, Noise)
技術的深掘り1:あえて「手作り」した3D物理演算
当初は d3-force-3d の利用を検討していましたが、Reactのステート更新とWebGLのレンダリングループを高次元で同期させ、オーバーヘッドを最小化するため、あえて useFrame 内で直接座標計算を行う自作エンジンを実装しました。
// useFrame 内での斥力計算ロジック(簡略化)
useFrame((state) => {
const REPULSION = 8.5;
const DAMPING = 0.95;
for (let i = 0; i < nodes.length; i++) {
for (let j = i + 1; j < nodes.length; j++) {
const dist = nodes[i].pos.distanceTo(nodes[j].pos);
if (dist > 0.1 && dist < 6) {
// クローンの法則(平方逆比例の法則)に基づいた斥力の付与
const force = (REPULSION / (dist * dist)) * 0.01;
const dir = nodes[i].pos
.clone()
.sub(nodes[j].pos)
.normalize()
.multiplyScalar(force);
nodes[i].velocity.add(dir);
nodes[j].velocity.sub(dir);
}
}
}
// 慣性と減衰の適用
nodes.forEach((n) => {
n.pos.add(n.velocity.multiplyScalar(DAMPING));
});
});
この実装により、外部ライブラリとのブリッジを介さず、Three.jsの Vector3 を直接操作できるため、非常に低いオーバーヘッドで流れるような動きを実現できました。
技術的深掘り2:接続線のリアルタイム更新
ノード同士を繋ぐ「エッジ(星座の線)」の描画には、 bufferGeometry の位置情報をフレームごとに更新する手法を採っています。
function Connections({ links }: any) {
const geomRef = useRef<THREE.BufferGeometry>(null);
useFrame(() => {
if (!geomRef.current) return;
const positions = geomRef.current.attributes.position.array as Float32Array;
let i = 0;
links.forEach((link) => {
// sourceとtargetの最新座標をBufferAttributeに直接書き込み
positions[i++] = link.source.pos.x;
positions[i++] = link.source.pos.y;
positions[i++] = link.source.pos.z;
positions[i++] = link.target.pos.x;
positions[i++] = link.target.pos.y;
positions[i++] = link.target.pos.z;
});
geomRef.current.attributes.position.needsUpdate = true;
});
return (
<lineSegments>
<bufferGeometry ref={geomRef}>
<bufferAttribute attach="attributes-position" count={links.length * 2} itemSize={3} ... />
</bufferGeometry>
<lineBasicMaterial transparent opacity={0.08} />
</lineSegments>
);
}
大量の Mesh を愚直に繋ぐのではなく、 lineSegments というプリミティブを用いることで、描画負荷を最小限に抑えています。
技術的深掘り3:ガラスの質感と没入感
ノードには、未来感を演出するために MeshPhysicalMaterial を使用した「ガラスの立方体」を採用しました。
// ガラスのマテリアル設定
child.material = new THREE.MeshPhysicalMaterial({
color: "#ffffff",
roughness: "0.1",
metalness: "0.1",
transmission: "0.9", // 透明度(透過量)
thickness: "1.5", // 物理的な厚み(屈折)
ior: "1.5", // 屈折率
transparent: true,
});
背景の Sparkles(火花のようなパーティクル)がガラスを透過して屈折する様子は、WebGLならではの視覚体験です。
Deep Dive: 三次元空間における斥力と引力の計算式
美しいネットワーク状の形状を保つため、ノード間には「クーロンの法則」に似た物理法則を適用しています。
- 斥力 (Repulsion): 距離の二乗に反比例して引き離す力。ノードが重なるのを防ぎます。
- 引力 (Attraction): 接続があるノード間のみに働く、バネのような引き寄せる力。
// 二乗逆比例の法則に基づいた計算
const force = strength / (distance * distance);
このバランスをフレームごとに微調整することで、カオスなデータ群が時間の経過とともに秩序のある「銀河」のような形状に収束していきます。
今後の展望:空間コンピューティングへの布石
Apple Vision Proなどの空間コンピュータが普及する2026年において、Webサイトも「平面」から「空間」へと進化する必要があります。 この3D Graphは、将来的にWebXR対応を行い、VR/AR空間で自サイトの記事を「直接掴んで読む」体験へと発展させる予定です。
現在、この機能はベータ版として /3d-graph で公開しています。実際のページはこちら → 3D Graphを開く
ぜひ、PCの大画面で「記事の宇宙」を探索してみてください。






⚠️ コメントのルール
※違反コメントはAIおよび管理者により予告なく削除されます
まだコメントがありません。最初のコメントを投稿しましょう!