Key Points of 3D Graph Feature

Implementing a 3D spatial navigation that breaks away from traditional list or grid post displays and represents the relevance between articles as distance.

  • GPU-friendly repulsion and attraction calculation by a custom-made 3D physics engine
  • Real-time synchronization of over 100 nodes and edges through dynamic updates of BufferGeometry
  • Expression of glass-like transparency and refraction using MeshPhysicalMaterial
  • Partial hydration of a heavy WebGL world with Astro 5 Server Islands

Drag to rotate • Scroll to zoom

Why is the experience of “reading a blog” always an up-to-down scroll?

The human brain remembers information not as a list (bullet points) but as a “network” connected to each other. If so, shouldn’t the blog interface be closer to the structure of the brain? Based on that hypothesis, I implemented this “3D Graph” in “HonoGear,” the blog I am developing individually.

Tech Stack: Combination of Modern WebGL

This 3D space is built by combining the following tech stack:

  • 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)

Technical Deep Dive 1: 3D Physics Calculation “Made by Hand” on Purpose

I initially considered using d3-force-3d, but in order to synchronize React state updates and WebGL rendering loops at a high dimension and minimize overhead, I intentionally implemented a custom engine that performs coordinate calculation directly within useFrame.

// Repulsion calculation logic within useFrame (simplified)
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) {
        // Applying repulsion based on Coulomb's law (Inverse-square law)
        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);
      }
    }
  }
  // Applying inertia and damping
  nodes.forEach((n) => {
    n.pos.add(n.velocity.multiplyScalar(DAMPING));
  });
});

Through this implementation, since Vector3 of Three.js can be directly manipulated without going through a bridge with external libraries, fluid movement could be achieved with very low overhead.

Technical Deep Dive 2: Real-time Update of Connection Lines

For the rendering of “edges (lines of constellations)” connecting nodes, I adopted a method of updating the position information of bufferGeometry per frame.

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) => {
      // Direct writing of latest coordinates of source and target to 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>
  );
}

By using the primitive lineSegments instead of connecting a large amount of Mesh naively, the rendering load is kept to a minimum.

Technical Deep Dive 3: Texture of Glass and Immersion

For the nodes, “I adopted “glass cubes” using MeshPhysicalMaterial to create a futuristic feeling.

// Glass material settings
child.material = new THREE.MeshPhysicalMaterial({
  color: "#ffffff",
  roughness: 0.1,
  metalness: 0.1,
  transmission: 0.9, // Transparency (transmission amount)
  thickness: 1.5, // Physical thickness (refraction)
  ior: 1.5, // Index of refraction
  transparent: true,
});

The way Sparkles (spark-like particles) in the background transmit and refract through the glass is a visual experience unique to WebGL.

Deep Dive: Repulsion and Attraction Formulas in 3D Space

To maintain a beautiful network-like shape, we apply physical laws similar to “Coulomb’s Law” between nodes.

  • Repulsion: A force that pulls nodes apart in inverse proportion to the square of the distance. Prevents overlapping.
  • Attraction: A spring-like pulling force that only acts between nodes with a connection.
// Calculation based on the Inverse Square Law
const force = strength / (distance * distance);

By fine-tuning this balance every frame, chaotic data groups converge over time into an orderly, galaxy-like shape.

Future Outlook: Paving the Way for Spatial Computing

In 2026, when spatial computers such as Apple Vision Pro become popular, websites also need to evolve from “flat” to “space.” This 3D Graph is planned to eventually support WebXR and develop into an experience where you can “directly grab and read” articles of your own site in VR/AR space.


This feature is currently available as a beta version at /3d-graph. Actual page here → Open 3D Graph By all means, please explore the “universe of articles” on a large PC screen.