RSLAB
Blog

Running WebGPU and Three.js inside React Native

24 Jul 2026

Motion Fossil was my first Three.js experiment, and it runs entirely inside a React Native app: your finger trace becomes a live 3D ribbon, and when you release, it crystallizes into a metallic sculpture you can spin around. Here is what it took.

The Metro part nobody tells you about

react-native-webgpu gives you a real WebGPU surface on iOS. But Three.js ships its WebGPU renderer under a separate entry, and @react-three/fiber needs its module build on native. Both are Metro resolution problems:

metro.config.js
config.resolver.resolveRequest = (context, moduleName, platform) => {
  if (moduleName.startsWith('three')) {
    moduleName = 'three/webgpu';
  }
  if (platform !== 'web' && moduleName.startsWith('@react-three/fiber')) {
    return context.resolveRequest(
      {
        ...context,
        unstable_conditionNames: ['module'],
        mainFields: ['module'],
      },
      moduleName,
      platform,
    );
  }
  return context.resolveRequest(context, moduleName, platform);
};

Without the alias, three resolves to the WebGL build and nothing renders. Without the condition names, fiber pulls its CommonJS build and crashes at import time. Add the react-native-webgpu plugin to app.json and build a custom dev client, it does not run in Expo Go.

One ribbon buffer, zero allocations

The trace is a single pre-allocated BufferGeometry sized for 180 points. Gesture Handler samples the finger every 5 pixels and maps position plus velocity into 3D points, fast strokes dive deeper into the scene. Each frame writes four vertices per point around the path tangent and grows the draw range:

positions[offset] = point.x + normalX;
positions[offset + 1] = point.y + normalY;
positions[offset + 2] = point.z + halfDepth;
// ...three more vertices per point
geometry.setDrawRange(0, count * INDICES_PER_SEGMENT);
geometry.computeVertexNormals();

The ribbon extends under your finger without ever rebuilding the mesh, which is what keeps the trace at 60fps while WebGPU is rendering reflections.

Crystallize on release

While you draw, the material is emissive clay: low metalness, high roughness, a warm glow. On release, one medium haptic commits the fossil and three material properties animate together:

material.metalness = 0.12 + crystallize * 0.72;
material.roughness = 0.72 - crystallize * 0.46;
material.emissiveIntensity = 0.42 - crystallize * 0.3;

Clay becomes metal, the glow dies, and the reflections take over. Further drags rotate the preserved object instead of drawing.

Try it

npx @rslab/cli add motion-fossil

The full source lives in the registry, and the interactive demo is on rselmi.com/lab/motion-fossil.