root@wguilherme:~$ cat ~/til/react-three-fiber-gltf-mdx.mdx

TIL: React Three Fiber + GLTF + MDX

#react #threejs #mdx #nextjs

Criei um organism para renderizar modelos 3D (GLTF) diretamente em posts MDX usando React Three Fiber. O foco é visualização de peças paramétricas (CAD), não 3D comum — por isso a câmera padrão é ortográfica/isométrica.

Stack

  • @react-three/fiber (v8) - Abstração declarativa do ThreeJS para React
  • @react-three/drei - Helpers (Bounds, Center, OrbitControls, useGLTF, Environment)
  • three (v0.160.0) - Engine 3D base
  • lucide-react - Ícones (Lock/Unlock)
  • next/dynamic com ssr: false - R3F não funciona no servidor

Arquitetura do componente

text
components/organisms/
├── Model3D.tsx        # Wrapper com dynamic import (SSR disabled)
└── Model3DCanvas.tsx  # Implementação real do viewer

Model3D.tsx (Wrapper)

Dynamic import sem SSR:

tsx
const Model3DCanvas = dynamic(
  () => import('./Model3DCanvas').then((mod) => ({ default: mod.Model3D })),
  { ssr: false }
)

Model3DCanvas.tsx

Dois componentes principais:

Model - Renderiza o modelo 3D:

  • useGLTF para carregar GLTF
  • Clona cena e centraliza geometria via bounding box
  • Aplica materiais dinamicamente
  • useFrame para rotação automática

Model3D - UI completa:

  • Sistema de abas (wireframe, solid, material)
  • Toggle de interatividade (Lock/Unlock)
  • Configuração de Canvas

Conceitos técnicos

Centralização do modelo

Modelos GLTF podem ter pivot point em qualquer lugar:

tsx
const clonedScene = useMemo(() => {
  const clone = scene.clone()
  const box = new THREE.Box3().setFromObject(clone)
  const center = box.getCenter(new THREE.Vector3())

  clone.traverse((child) => {
    if ((child as THREE.Mesh).isMesh) {
      child.position.sub(center)
    }
  })
  return clone
}, [scene])

Sistema de materiais

Factory functions para criar materiais sob demanda:

tsx
const materialPresets: Record<MaterialPreset, ()=> THREE.Material> = {
  default: () => new THREE.MeshStandardMaterial({
    color: 0xcccccc, metalness: 0.1, roughness: 0.8,
  }),
  metal: () => new THREE.MeshStandardMaterial({
    color: 0x888888, metalness: 0.9, roughness: 0.2,
  }),
  glass: () => new THREE.MeshPhysicalMaterial({
    color: 0xcccccc, transmission: 0.9, thickness: 0.5,
  }),
}

Presets de câmera

tsx
const cameraPresets = {
  cad: { orthographic: true, position: [5, 5, 5], zoom: 50 },
  organic: { orthographic: false, position: [0, 0, 5], fov: 50 },
}

Iluminação

tsx
<ambientLight intensity={viewMode= 'wireframe' ? 1 : 0.5} />
<directionalLight position={[10, 10, 5]} intensity={...} />
{viewMode !== 'wireframe' && <Environment preset="city" />}

Auto-fit da câmera

tsx
const bounds = useBounds()
useEffect(() => {
  bounds.refresh().clip().fit()
}, [bounds, clonedScene])

Desafios encontrados

SSR não funciona com R3F

R3F depende de WebGL/canvas (APIs do browser). Solução: next/dynamic com SSR desabilitado.

Incompatibilidade de versões

R3F v9 requer React 19. Com React 18:

json
"@react-three/fiber": "^8",
"@react-three/drei": "^9",
"three": "^0.160.0"

Touch conflitando com scroll no mobile

OrbitControls captura touch mesmo desabilitado. Solução: renderização condicional:

tsx
{showControls && interactiveMode && (
  <OrbitControls enablePan={false} enableZoom={false} makeDefault />
)}

Sistema de abas

3 modos de visualização:

  • wireframe - Sempre disponível, wireframe verde (#09E22C)
  • solid - Sempre disponível, material default
  • material - Só aparece se prop material for passada
tsx
const hasMaterialTab = material && material !== 'default'
const currentMaterial = viewMode === 'material' && material ? material : 'default'

Uso no MDX

jsx
<Model3D src="/models/object.gltf" scale={1.5} material="metal" />

Props

  • src (string, required) - Caminho para o arquivo GLTF
  • scale (number, default: 1) - Escala do modelo
  • height (string, default: "400px") - Altura do viewer
  • autoRotate (boolean, default: true) - Rotação automática
  • material (MaterialPreset) - Preset de material (habilita aba extra)
  • camera (CameraPreset, default: "cad") - Preset de câmera
  • defaultMode (ViewMode, default: "wireframe") - Modo inicial

Material presets

default, metal, plastic, glass, gold, copper

Camera presets

  • cad - Ortográfica/isométrica (peças paramétricas)
  • organic - Perspectiva (objetos orgânicos)