Communitygithub.com

phido0410/threejs-3d

Desarrollo 3D con Three.js, React Three Fiber (R3F), helpers Drei, física Rapier (@react-three/rapier) y efectos de postprocesado (@react-three/postprocessing). Úsalo al construir escenas 3D para web o video: modelos (GLTF), luces, cámaras, mapas de entorno, física, bloom/viñeta/profundidad de campo. Dentro de Remotion, sigue también remotion-markup/3d.md.

¿Qué es threejs-3d?

Este skill proporciona un stack completo para desarrollo 3D en React, combinando Three.js con React Three Fiber (R3F) y un conjunto de librerías complementarias. Está diseñado para construir escenas 3D interactivas para web o contenido de video, soportando carga de modelos GLTF, configuración de luces y cámaras, mapas de entorno, simulación física con Rapier, y efectos de postprocesado como bloom, viñeta y profundidad de campo. Es compatible con plataformas como Remotion para renders de video cuadro a cuadro, y está optimizado para React 19. El skill incluye plantillas de código base, patrones recomendados (como el uso de Suspense para assets asíncronos) y prácticas para mantener rendimiento y previsibilidad, evitando animaciones basadas en estado dentro del bucle de render.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/phido0410/claude-video-skills/tree/HEAD/skills/threejs-3d

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Stack 3D con Three.js

Verificado 2026-10: three 0.186, @react-three/fiber 9.8, @react-three/drei 10.7, @react-three/rapier 2.2, @react-three/postprocessing 3.2 (+ postprocessing 6.39). Fiber 9 / Drei 10 / Rapier 2 / postprocessing 3 requieren React 19 (fiber 9.8 peer: react >=19 <19.4).

npm i three @react-three/fiber @react-three/drei
npm i @react-three/rapier                         # física
npm i @react-three/postprocessing postprocessing  # efectos
npm i -D @types/three

Escena base (web)

import { useRef, Suspense } from 'react'
import * as THREE from 'three'
import { Canvas, useFrame } from '@react-three/fiber'
import { OrbitControls, Environment, ContactShadows } from '@react-three/drei'

function Caja() {
  const ref = useRef<THREE.Mesh>(null!)
  useFrame((_, delta) => { ref.current.rotation.y += delta }) // mutar refs, nunca llamar setState por fotograma
  return (
    <mesh ref={ref} castShadow>
      <boxGeometry />
      <meshStandardMaterial color="#4a9eff" />
    </mesh>
  )
}

<Canvas camera={{ position: [3, 2, 5], fov: 45 }} dpr={[1, 2]} shadows>
  <ambientLight intensity={0.3} />
  <directionalLight position={[5, 5, 5]} castShadow />
  <Suspense fallback={null}>
    <Caja />
    <Environment preset="city" />
  </Suspense>
  <ContactShadows position={[0, -0.5, 0]} opacity={0.5} blur={2} />
  <OrbitControls />
</Canvas>

Reglas:

  • Cualquier componente asíncrono (useGLTF, useTexture, Environment, fuentes de Text, WASM de Physics) debe ir dentro de <Suspense>.
  • Usar useGLTF.preload(url) a nivel de módulo para modelos que aparecen en el primer render.
  • Mantener las escenas en un rango de 1 a 10 unidades; escalas muy grandes o muy pequeñas rompen sombras y controles.
  • MeshStandardMaterial necesita luz o un <Environment>; sin ellos, se renderiza en negro.

Drei (elegir lo necesario)

  • Cámara/controles: OrbitControls, CameraControls, PerspectiveCamera makeDefault
  • Assets: useGLTF, useTexture, Environment (presets: apartment, city, dawn, forest, lobby, night, park, studio, sunset, warehouse), Center, Bounds
  • Escenografía: ContactShadows, AccumulativeShadows, Stage, Float, Sparkles, Stars, Cloud
  • Materiales: MeshTransmissionMaterial (vidrio), MeshReflectorMaterial (suelo reflectante)
  • Texto: Text (SDF, cualquier URL de fuente), Text3D (requiere JSON de typeface), Html (superposición DOM)

Física Rapier

import { Physics, RigidBody, CuboidCollider, type RapierRigidBody } from '@react-three/rapier'

<Suspense>
  <Physics gravity={[0, -9.81, 0]} /* debug */>
    <RigidBody colliders="hull" restitution={0.6} ref={refCuerpo}>
      <mesh><torusGeometry /><meshStandardMaterial /></mesh>
    </RigidBody>
    <RigidBody type="fixed">
      <CuboidCollider args={[10, 0.1, 10]} /> {/* semiejes */}
    </RigidBody>
  </Physics>
</Suspense>
// refCuerpo.current?.applyImpulse({ x: 0, y: 5, z: 0 }, true)
  • colliders: "cuboid" (por defecto), "ball", "hull", "trimesh", false (añadir componentes de colisión manualmente).
  • type: "dynamic" (por defecto), "fixed", "kinematicPosition", "kinematicVelocity".
  • timeStep por defecto es fijo a 1/60. timeStep="vary" no es determinista. Paso manual: useRapier().step(1/60).
  • debug en <Physics> dibuja los colisionadores.

Postprocesado

import { EffectComposer, Bloom, Vignette, DepthOfField, Noise } from '@react-three/postprocessing'

<EffectComposer>{/* último hijo de <Canvas> */}
  <Bloom luminanceThreshold={0.9} intensity={1.2} mipmapBlur />
  <Vignette offset={0.1} darkness={0.9} />
</EffectComposer>
  • Bloom solo captura píxeles por encima de luminanceThreshold. Para hacer que un objeto brille: color emissive + emissiveIntensity > 1 + toneMapped={false} en su material.
  • Cada efecto cuesta un pase de pantalla completa; entre 2 y 4 efectos es suficiente.

Para video

Remotion (ver remotion-markup/3d.md): <ThreeCanvas width height> de @remotion/three; no usar useFrame; todo movimiento proviene de useCurrentFrame(). Además:

  • No usar OrbitControls — mover la cámara desde frame (interpolate / spring).
  • Los helpers de Drei que animan con su propio reloj (Float, Sparkles, Stars, Cloud, MeshWobbleMaterial) parpadean en los renders. Evitarlos o reproducir el movimiento desde frame.
  • Física: Remotion renderiza fotogramas fuera de orden entre pestañas, por lo que una simulación en vivo no funciona. Horneado: inicializar @dimforge/rapier3d-compat (el motor bajo @react-three/rapier) una vez, ejecutar world.step() con timestep = 1 / fps para cada fotograma, guardar posición + rotación de cada cuerpo en cada fotograma, y luego renderizar las transformaciones en el índice frame.

Fuera de Remotion (grabación de pantalla, animación con GSAP): useFrame está bien; conducir el tiempo desde una línea de tiempo (tl.time()) en lugar de delta cuando la toma debe ser repetible.

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

Skills relacionados