..

December 29, 2025

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

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:

Model3D - UI completa:

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:

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

Material presets

default, metal, plastic, glass, gold, copper

Camera presets