Three.js 3D-Stack
Überprüft 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 benötigen React 19 (fiber 9.8 peer: react >=19 <19.4).
npm i three @react-three/fiber @react-three/drei
npm i @react-three/rapier # Physik
npm i @react-three/postprocessing postprocessing # Effekte
npm i -D @types/three
Basisszene (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 Box() {
const ref = useRef<THREE.Mesh>(null!)
useFrame((_, delta) => { ref.current.rotation.y += delta }) // Refs mutieren, niemals setState pro Frame
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}>
<Box />
<Environment preset="city" />
</Suspense>
<ContactShadows position={[0, -0.5, 0]} opacity={0.5} blur={2} />
<OrbitControls />
</Canvas>
Regeln:
- Alles Asynchrone (useGLTF, useTexture, Environment, Text-Fonts, Physik-WASM) liegt unter
<Suspense>. useGLTF.preload(url)auf Modulebene für Modelle, die beim ersten Rendern verwendet werden.- Szenen im Bereich von 1–10 Einheiten halten; große/kleine Skalierungen brechen Schatten und Steuerung.
MeshStandardMaterialbenötigt Licht oder ein<Environment>; ohne beides wird es schwarz dargestellt.
Drei (nur das Nötigste auswählen)
- Kamera/Steuerung:
OrbitControls,CameraControls,PerspectiveCamera makeDefault - Assets:
useGLTF,useTexture,Environment(Voreinstellungen: apartment, city, dawn, forest, lobby, night, park, studio, sunset, warehouse),Center,Bounds - Inszenierung:
ContactShadows,AccumulativeShadows,Stage,Float,Sparkles,Stars,Cloud - Materialien:
MeshTransmissionMaterial(Glas),MeshReflectorMaterial(spiegelnder Boden) - Text:
Text(SDF, jede Schriftart-URL),Text3D(benötigt Typeface-JSON),Html(DOM-Overlay)
Rapier-Physik
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={bodyRef}>
<mesh><torusGeometry /><meshStandardMaterial /></mesh>
</RigidBody>
<RigidBody type="fixed">
<CuboidCollider args={[10, 0.1, 10]} /> {/* halbe Ausdehnung */}
</RigidBody>
</Physics>
</Suspense>
// bodyRef.current?.applyImpulse({ x: 0, y: 5, z: 0 }, true)
colliders:"cuboid"(Standard),"ball","hull","trimesh",false(Kollider-Komponenten selbst hinzufügen).type:"dynamic"(Standard),"fixed","kinematicPosition","kinematicVelocity".timeStepstandardmäßig fest 1/60.timeStep="vary"ist nicht deterministisch. Manuelles Stepping:useRapier().step(1 / 60).debugauf<Physics>zeichnet Kollider.
Postprocessing
import { EffectComposer, Bloom, Vignette, DepthOfField, Noise } from '@react-three/postprocessing'
<EffectComposer>{/* letztes Kind von <Canvas> */}
<Bloom luminanceThreshold={0.9} intensity={1.2} mipmapBlur />
<Vignette offset={0.1} darkness={0.9} />
</EffectComposer>
- Bloom erfasst nur Pixel oberhalb von
luminanceThreshold. Um ein Objekt leuchten zu lassen:emissiveFarbe +emissiveIntensity> 1 +toneMapped={false}auf seinem Material. - Jeder Effekt kostet einen Fullscreen-Pass; 2–4 Effekte reichen völlig.
Für Video
Remotion (siehe remotion-markup/3d.md): <ThreeCanvas width height> von @remotion/three; kein useFrame; jede Bewegung kommt von useCurrentFrame(). Zusätzlich:
- Kein
OrbitControls– die Kamera ausframebewegen (interpolate/spring). - Drei-Helfer, die nach ihrer eigenen Uhr animieren (
Float,Sparkles,Stars,Cloud,MeshWobbleMaterial) flackern in Renderings. Sie weglassen oder die Bewegung ausframenachbilden. - Physik: Remotion rendert Frames in beliebiger Reihenfolge über Tabs hinweg, daher kann eine Live-Simulation nicht mithalten. Die Physik vorbacken:
@dimforge/rapier3d-compat(die Engine unter @react-three/rapier) einmal initialisieren,world.step()mittimestep = 1 / fpsfür jeden Frame, die Position + Rotation jedes Körpers pro Frame speichern, dann die Transformationen bei Indexframerendern.
Außerhalb von Remotion (Bildschirmaufnahme, GSAP-gesteuert): useFrame ist in Ordnung; die Zeit von einer Timeline (tl.time()) statt von delta treiben, wenn der Shot wiederholbar sein muss.