
Threejs Impl Drei
- 19 installs
- 11 repo stars
- Updated July 8, 2026
- openaec-foundation/three.js-claude-skill-package
Helps with ai & agent building tasks.
About
threejs-impl-drei is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- threejs-impl-drei
- AI & Agent Building
- AI-coding skill
Threejs Impl Drei by the numbers
- 19 all-time installs (skills.sh)
- +2 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #10,587 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/three.js-claude-skill-package --skill threejs-impl-dreiAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 19 |
|---|---|
| repo stars | ★ 11 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/three.js-claude-skill-package ↗ |
What it does
Helps with ai & agent building tasks.
Files
threejs-impl-drei
Quick Reference
Installation
npm install @react-three/drei @react-three/fiber threeCritical Warnings
ALWAYS wrap components that use loader hooks (useGLTF, useTexture, useFBX, useKTX2, useFont) in <Suspense fallback={...}>. Omitting Suspense causes the entire React tree to crash.
ALWAYS add makeDefault to your primary camera controls (<OrbitControls makeDefault />). Without makeDefault, Drei controls do NOT integrate with R3F's event system and pointer events break.
NEVER use an invalid Environment preset name. The ONLY valid presets are: apartment, city, dawn, forest, lobby, night, park, studio, sunset, warehouse.
NEVER forget to call .preload() for critical assets. Use useGLTF.preload('/model.glb') at module scope to start loading before component mount.
ALWAYS use <Instances> for rendering more than 100 identical meshes. Individual meshes cause one draw call each; instances batch them into one.
---
Controls
OrbitControls
The most common camera control. Orbit, zoom, and pan around a target.
import { OrbitControls } from '@react-three/drei'
<OrbitControls
makeDefault // ALWAYS add — integrates with R3F events
enableDamping // smooth movement
dampingFactor={0.05}
minDistance={2}
maxDistance={20}
minPolarAngle={0}
maxPolarAngle={Math.PI / 2} // prevent going below ground
/>Other Controls
| Component | Use Case |
|---|---|
CameraControls | Full-featured camera (recommended for complex scenes) |
MapControls | Top-down map navigation (orbit restricted to vertical axis) |
PresentationControls | Drag-to-rotate with spring physics (product viewers) |
ScrollControls | Scroll-driven animation (pages prop sets scroll length) |
TransformControls | Translate/rotate/scale gizmo on selected objects |
DragControls | Drag objects in 3D space |
KeyboardControls | Keyboard input as React context |
FaceControls | Face-tracking camera movement |
ScrollControls Pattern
import { ScrollControls, useScroll } from '@react-three/drei'
<ScrollControls pages={3} damping={0.1}>
<ScrollScene />
</ScrollControls>
function ScrollScene() {
const scroll = useScroll()
useFrame(() => {
const offset = scroll.offset // 0 to 1
})
return <mesh />
}---
Environment and Staging
Environment
Loads HDR environment maps for realistic reflections and lighting.
import { Environment } from '@react-three/drei'
// Preset (downloads from polyhaven CDN)
<Environment preset="city" background blur={0.5} />
// Custom HDR file
<Environment files="/env.hdr" background />
// Custom environment with Lightformers
<Environment background>
<Lightformer form="rect" intensity={2} position={[0, 5, -5]} scale={[10, 5, 1]} />
</Environment>Valid presets: apartment, city, dawn, forest, lobby, night, park, studio, sunset, warehouse.
Stage
Complete lighting and shadow setup in one component. Ideal for product viewers.
import { Stage } from '@react-three/drei'
<Stage preset="rembrandt" intensity={0.5} environment="city" adjustCamera>
<Model />
</Stage>Shadow Components
| Component | Use Case | Key Props |
|---|---|---|
ContactShadows | Soft ground shadows (no light needed) | opacity, scale, blur, far, resolution, color |
AccumulativeShadows | High-quality baked soft shadows | frames, alphaTest, scale, opacity |
RandomizedLight | Child of AccumulativeShadows | amount, radius, intensity, position |
BakeShadows | Bake shadow maps once, stop updating | — |
SoftShadows | PCSS soft shadows for real-time lights | — |
AccumulativeShadows Pattern
<AccumulativeShadows temporal frames={100} scale={10} position={[0, -0.5, 0]}>
<RandomizedLight amount={8} radius={4} position={[5, 5, -10]} />
</AccumulativeShadows>Atmosphere
| Component | Purpose | Key Props |
|---|---|---|
Sky | Procedural sky dome | sunPosition, turbidity, rayleigh |
Stars | Particle starfield | radius, count, factor, fade |
Sparkles | Floating particles | count, size, speed, color |
Cloud | Volumetric clouds | opacity, speed, segments, bounds |
---
Text and HTML
Text (SDF)
High-quality 2D text rendered with signed distance fields via troika-three-text.
import { Text } from '@react-three/drei'
<Text
fontSize={0.5}
color="white"
anchorX="center"
anchorY="middle"
maxWidth={2}
font="/fonts/Inter-Bold.woff"
>
Hello World
</Text>Text3D
Extruded 3D geometry text. Requires a JSON font file (use facetype.js to convert).
import { Text3D, Center } from '@react-three/drei'
<Center>
<Text3D font="/fonts/Inter_Bold.json" size={0.75} height={0.2} bevelEnabled bevelSize={0.02}>
Hello
<meshStandardMaterial color="orange" />
</Text3D>
</Center>Html
Renders DOM elements positioned in 3D space.
import { Html } from '@react-three/drei'
<mesh position={[0, 2, 0]}>
<Html
transform // transforms with 3D position
distanceFactor={10} // scales with distance
occlude // hides behind 3D objects
center // centers the HTML element
className="label"
>
<div style={{ color: 'white' }}>Annotation</div>
</Html>
</mesh>Billboard
ALWAYS faces the camera. Use for labels and sprites.
import { Billboard, Text } from '@react-three/drei'
<Billboard follow={true} lockX={false} lockY={false} lockZ={false}>
<Text fontSize={0.5}>Always Visible</Text>
</Billboard>Hud
Renders a heads-up display in a separate orthographic scene.
import { Hud, OrthographicCamera } from '@react-three/drei'
<Hud renderPriority={1}>
<OrthographicCamera makeDefault position={[0, 0, 5]} />
<Text position={[0, 0, 0]} fontSize={0.1}>HUD Text</Text>
</Hud>---
Materials
| Component | Purpose |
|---|---|
MeshReflectorMaterial | Reflective floors with blur, distortion, resolution |
MeshTransmissionMaterial | Glass with chromatic aberration, distortion, thickness |
MeshRefractionMaterial | Refraction using environment cube map |
MeshWobbleMaterial | Animated wobble on MeshStandardMaterial |
MeshDistortMaterial | Perlin noise distortion on MeshStandardMaterial |
MeshDiscardMaterial | Renders nothing (shadow-only objects) |
shaderMaterial | Helper to create custom ShaderMaterial as JSX |
MeshReflectorMaterial Example
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.5, 0]}>
<planeGeometry args={[50, 50]} />
<MeshReflectorMaterial
blur={[300, 100]}
resolution={1024}
mixBlur={1}
mixStrength={50}
roughness={1}
depthScale={1.2}
minDepthThreshold={0.4}
maxDepthThreshold={1.4}
color="#151515"
metalness={0.5}
/>
</mesh>shaderMaterial Helper
import { shaderMaterial } from '@react-three/drei'
import { extend } from '@react-three/fiber'
const WaveMaterial = shaderMaterial(
{ uTime: 0, uColor: new THREE.Color(0.2, 0.0, 0.1) },
/* vertex */ `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`,
/* fragment */ `uniform float uTime; uniform vec3 uColor; varying vec2 vUv; void main() { gl_FragColor = vec4(vUv * uColor, 1.0); }`
)
extend({ WaveMaterial })
// Usage: <waveMaterial uTime={clock.elapsedTime} />---
Loaders
ALWAYS wrap loader-consuming components in <Suspense>.
| Hook | Returns | Preload |
|---|---|---|
useGLTF(url) | { nodes, materials, scene, animations } | useGLTF.preload(url) |
useTexture(url) | THREE.Texture or map object | useTexture.preload(url) |
useFBX(url) | THREE.Group | useFBX.preload(url) |
useKTX2(url) | Compressed texture | useKTX2.preload(url) |
useFont(url) | Font data for Text3D | useFont.preload(url) |
useAnimations(clips, ref) | { actions, names, mixer, ref } | — |
useVideoTexture(url) | THREE.VideoTexture | — |
useGLTF Pattern
import { useGLTF } from '@react-three/drei'
function Model(props) {
const { nodes, materials } = useGLTF('/model.glb')
return (
<group {...props}>
<mesh geometry={nodes.Body.geometry} material={materials.Metal} />
</group>
)
}
useGLTF.preload('/model.glb')useTexture with Multiple Maps
const textures = useTexture({
map: '/color.jpg',
normalMap: '/normal.jpg',
roughnessMap: '/roughness.jpg',
aoMap: '/ao.jpg',
})
// Spread directly onto material
<meshStandardMaterial {...textures} />useAnimations Pattern
function AnimatedModel() {
const group = useRef()
const { nodes, animations } = useGLTF('/character.glb')
const { actions } = useAnimations(animations, group)
useEffect(() => {
actions['Walk']?.play()
return () => actions['Walk']?.stop()
}, [actions])
return <group ref={group}><primitive object={nodes.Scene} /></group>
}---
Performance
Instances
ALWAYS use for large numbers of identical meshes (>100). Reduces draw calls from N to 1.
import { Instances, Instance } from '@react-three/drei'
<Instances limit={1000} range={1000}>
<boxGeometry />
<meshStandardMaterial />
{positions.map((pos, i) => (
<Instance key={i} position={pos} color="orange" />
))}
</Instances>Merged
Merges different geometries into a single draw call.
import { Merged } from '@react-three/drei'
function Furniture({ nodes }) {
return (
<Merged meshes={[nodes.Chair, nodes.Table, nodes.Lamp]}>
{(Chair, Table, Lamp) => (
<>
<Chair position={[0, 0, 0]} />
<Table position={[2, 0, 0]} />
<Lamp position={[1, 1, 0]} />
</>
)}
</Merged>
)
}Performance Helpers
| Component | Purpose |
|---|---|
Detailed | LOD — switches geometry based on camera distance |
BakeShadows | Bakes shadows once, stops updating |
AdaptiveDpr | Lowers device pixel ratio during performance drops |
AdaptiveEvents | Reduces event frequency during performance drops |
PerformanceMonitor | Monitors FPS, triggers regression callbacks |
Bvh | BVH-accelerated raycasting for complex meshes |
meshBounds | Fast bounding-box raycasting (replaces per-triangle) |
PerformanceMonitor Pattern
<PerformanceMonitor
onIncline={() => setDpr(2)}
onDecline={() => setDpr(1)}
flipflops={3}
onFallback={() => setDpr(0.5)}
/>---
Staging and Layout
| Component | Purpose | Key Props |
|---|---|---|
Center | Centers children at origin | top, right, bottom, left, front, back |
Float | Floating hover animation | speed, rotationIntensity, floatIntensity |
Bounds | Auto-fit camera to content | fit, clip, observe, margin |
Resize | Normalizes children to unit size | width, height, depth |
---
Abstractions and Effects
| Component | Purpose |
|---|---|
Edges | Renders wireframe edges |
Outlines | Screen-space outlines |
Trail | Motion trail behind objects |
Decal | Project texture onto mesh surface |
Splat | Gaussian splatting renderer |
Clone | Deep clone with shared geometry/materials |
Image | Texture-mapped plane with shader effects |
MeshPortalMaterial | Portal — renders scene inside mesh surface |
GradientTexture | Procedural gradient texture |
---
Gizmos
| Component | Purpose |
|---|---|
GizmoHelper | Viewport orientation widget |
PivotControls | Interactive pivot gizmo (translate/rotate/scale) |
TransformControls | Three.js TransformControls wrapper |
Grid | Infinite configurable grid plane |
Helper / useHelper | Visualize light/camera helpers |
---
Component Selection Guide
| Scenario | Component |
|---|---|
| Product viewer | Stage + OrbitControls + Environment |
| Architectural walkthrough | CameraControls + Environment + ContactShadows |
| Scrolling experience | ScrollControls + useScroll |
| Data visualization | Instances + Html + Billboard |
| Text labels in 3D | Text (2D) or Text3D (extruded) + Billboard |
| Glass/transparent objects | MeshTransmissionMaterial + Environment |
| Reflective floors | MeshReflectorMaterial |
| Large identical meshes | Instances (>100) or Merged (mixed geometries) |
| Model loading | useGLTF + Suspense + .preload() |
| HUD / overlay | Hud or Html with fullscreen |
---
Reference Links
- references/methods.md -- Key component props and hook signatures
- references/examples.md -- Complete working examples
- references/anti-patterns.md -- Common mistakes and fixes
Official Sources
- https://drei.docs.pmnd.rs/
- https://github.com/pmndrs/drei
- https://r3f.docs.pmnd.rs/
threejs-impl-drei — Anti-Patterns
AP-01: Missing Suspense Around Loader Hooks
NEVER use useGLTF, useTexture, useFBX, useKTX2, or useFont without wrapping the consuming component in <Suspense>.
// WRONG — crashes the entire React tree
function App() {
return (
<Canvas>
<Model />
</Canvas>
)
}
function Model() {
const { nodes } = useGLTF('/model.glb') // Suspense throw with no boundary
return <mesh geometry={nodes.Body.geometry} />
}// CORRECT — Suspense catches the loading promise
function App() {
return (
<Canvas>
<Suspense fallback={null}>
<Model />
</Suspense>
</Canvas>
)
}Why: Drei loader hooks use React Suspense internally. Without a <Suspense> boundary, React has no way to handle the thrown promise and the entire component tree unmounts with an error.
---
AP-02: Forgetting makeDefault on Controls
NEVER omit makeDefault on your primary camera controls.
// WRONG — controls work but pointer events on meshes break
<OrbitControls />// CORRECT — integrates with R3F's event system
<OrbitControls makeDefault />Why: Without makeDefault, R3F does not know about the controls. The event system uses the default camera for raycasting, but the controls may update a different camera reference. This causes pointer events (onClick, onPointerOver) to use stale camera data, resulting in incorrect hit detection.
---
AP-03: Invalid Environment Preset Name
NEVER use a preset name that is not in the valid list.
// WRONG — silently fails or throws, no environment loaded
<Environment preset="outdoor" />
<Environment preset="hdri" />
<Environment preset="default" />// CORRECT — use ONLY these exact preset names
<Environment preset="city" />
// Valid: apartment, city, dawn, forest, lobby, night, park, studio, sunset, warehouseWhy: Drei downloads preset HDR files from a CDN. Invalid preset names result in a 404 network error. The component fails silently or throws, leaving the scene without environment lighting. There is no fallback mechanism.
---
AP-04: Not Preloading Critical Assets
NEVER rely solely on component-mount loading for assets visible on first render.
// WRONG — model loads only when component mounts, causing visible pop-in
function Model() {
const { nodes } = useGLTF('/hero-model.glb')
return <mesh geometry={nodes.Body.geometry} />
}// CORRECT — preload at module scope, asset is ready before mount
function Model() {
const { nodes } = useGLTF('/hero-model.glb')
return <mesh geometry={nodes.Body.geometry} />
}
useGLTF.preload('/hero-model.glb') // starts loading immediately at import timeWhy: Without .preload(), the asset download begins only when the component first renders. For hero models or textures that are visible immediately, this causes a jarring flash of empty content followed by sudden appearance. Preloading moves the network request to module evaluation time, overlapping with other initialization work.
---
AP-05: Using Individual Meshes Instead of Instances
NEVER render hundreds of identical meshes as separate <mesh> elements.
// WRONG — 1000 draw calls, one per mesh
{positions.map((pos, i) => (
<mesh key={i} position={pos}>
<boxGeometry args={[0.5, 0.5, 0.5]} />
<meshStandardMaterial color="orange" />
</mesh>
))}// CORRECT — 1 draw call for all 1000 instances
<Instances limit={1000} range={1000}>
<boxGeometry args={[0.5, 0.5, 0.5]} />
<meshStandardMaterial color="orange" />
{positions.map((pos, i) => (
<Instance key={i} position={pos} color="orange" />
))}
</Instances>Why: Each separate <mesh> generates its own draw call. At 100+ meshes, draw call overhead dominates frame time and FPS drops dramatically. <Instances> uses GPU instancing to render all copies in a single draw call, reducing CPU overhead by orders of magnitude.
---
AP-06: Recreating Geometry and Material on Every Render
NEVER create Three.js objects inline without memoization.
// WRONG — new geometry and material every render cycle
function MyMesh() {
return (
<mesh>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
array={new Float32Array(computeVertices())} // new array every render
count={vertexCount}
itemSize={3}
/>
</bufferGeometry>
<meshStandardMaterial color="red" />
</mesh>
)
}// CORRECT — memoize computed data
function MyMesh() {
const vertices = useMemo(() => new Float32Array(computeVertices()), [])
return (
<mesh>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
array={vertices}
count={vertexCount}
itemSize={3}
/>
</bufferGeometry>
<meshStandardMaterial color="red" />
</mesh>
)
}Why: R3F disposes and recreates Three.js objects when their constructor arguments (args) change. Creating new arrays or objects inline causes R3F to detect a change every frame, triggering GPU resource deallocation and reallocation. This causes massive GC pressure and frame drops.
---
AP-07: Using Html Without occlude or distanceFactor
NEVER use <Html> in a 3D scene without considering occlusion and scaling.
// WRONG — HTML label floats above everything, same size regardless of distance
<Html>
<div>Label</div>
</Html>// CORRECT — label scales with distance and hides behind objects
<Html
transform
distanceFactor={10}
occlude
center
>
<div>Label</div>
</Html>Why: Without occlude, HTML elements render on top of ALL 3D content, breaking spatial perception. Without distanceFactor, labels remain the same pixel size regardless of camera distance, making them look disconnected from the 3D scene. Without transform, the HTML element does not participate in 3D positioning.
---
AP-08: Using ContactShadows with frames={Infinity} Unnecessarily
NEVER use real-time ContactShadows when objects are static.
// WRONG — re-renders shadow every frame for a static scene
<ContactShadows
opacity={0.5}
scale={10}
blur={2}
resolution={1024}
/>// CORRECT — bake shadow once for static scenes
<ContactShadows
opacity={0.5}
scale={10}
blur={2}
resolution={1024}
frames={1} // render once and stop
/>Why: ContactShadows defaults to rendering every frame (frames={Infinity}). For static scenes where objects do not move, this wastes GPU resources re-rendering an identical shadow map 60 times per second. Setting frames={1} bakes the shadow on mount and stops updating, saving significant GPU time.
---
AP-09: TransformControls Conflicting with OrbitControls
NEVER use TransformControls and OrbitControls without preventing event conflict.
// WRONG — dragging the gizmo also orbits the camera
<OrbitControls makeDefault />
<TransformControls object={meshRef.current} />// CORRECT — disable orbit controls while dragging the gizmo
function Scene() {
const orbitRef = useRef()
return (
<>
<OrbitControls ref={orbitRef} makeDefault />
<TransformControls
object={meshRef.current}
onMouseDown={() => (orbitRef.current.enabled = false)}
onMouseUp={() => (orbitRef.current.enabled = true)}
/>
</>
)
}Why: Both controls listen to the same pointer events. When dragging a TransformControls gizmo handle, the OrbitControls also receives the drag event and rotates the camera simultaneously. Disabling OrbitControls during TransformControls interaction prevents this conflict.
---
AP-10: Mounting the Same useGLTF Scene Object Multiple Times
NEVER use <primitive object={gltf.scene} /> in multiple components simultaneously.
// WRONG — same object reference mounted twice, second instance steals it from the first
function App() {
return (
<>
<Model position={[0, 0, 0]} />
<Model position={[5, 0, 0]} />
</>
)
}
function Model({ position }) {
const gltf = useGLTF('/model.glb')
return <primitive object={gltf.scene} position={position} />
}// CORRECT — clone the scene for each instance, or use individual nodes
function Model({ position }) {
const { nodes, materials } = useGLTF('/model.glb')
return (
<group position={position}>
<mesh geometry={nodes.Body.geometry} material={materials.Metal} />
</group>
)
}
// ALTERNATIVE — use Clone from Drei
import { Clone } from '@react-three/drei'
function Model({ position }) {
const { scene } = useGLTF('/model.glb')
return <Clone object={scene} position={position} />
}Why: A Three.js Object3D can only have one parent. When you mount the same scene object in two locations, the second <primitive> reparents it, removing it from the first. Use <Clone> to create a deep copy with shared geometry and materials, or access individual nodes and create separate mesh elements.
threejs-impl-drei — Examples
Example 1: Product Viewer with Environment and Shadows
Complete product viewer with orbit controls, environment lighting, and contact shadows.
import { Canvas } from '@react-three/fiber'
import { OrbitControls, Environment, ContactShadows, Center } from '@react-three/drei'
import { Suspense } from 'react'
function ProductViewer() {
return (
<Canvas shadows camera={{ position: [0, 2, 5], fov: 50 }}>
<Suspense fallback={null}>
<Center>
<Product />
</Center>
<Environment preset="studio" background={false} />
</Suspense>
<ContactShadows
position={[0, -1, 0]}
opacity={0.5}
scale={10}
blur={2}
far={4}
resolution={512}
/>
<OrbitControls
makeDefault
minDistance={2}
maxDistance={10}
maxPolarAngle={Math.PI / 2}
enablePan={false}
/>
</Canvas>
)
}
function Product(props) {
const { nodes, materials } = useGLTF('/shoe.glb')
return (
<group {...props}>
<mesh
geometry={nodes.Shoe.geometry}
material={materials.Leather}
castShadow
/>
</group>
)
}
useGLTF.preload('/shoe.glb')---
Example 2: Scroll-Driven Experience
A scrolling website with 3D elements that animate based on scroll position.
import { Canvas, useFrame } from '@react-three/fiber'
import { ScrollControls, useScroll, Text, Float, Environment } from '@react-three/drei'
import { useRef } from 'react'
function ScrollExperience() {
return (
<Canvas camera={{ position: [0, 0, 5] }}>
<Environment preset="sunset" />
<ScrollControls pages={3} damping={0.1}>
<ScrollContent />
</ScrollControls>
</Canvas>
)
}
function ScrollContent() {
const scroll = useScroll()
const groupRef = useRef()
useFrame(() => {
const offset = scroll.offset
groupRef.current.rotation.y = offset * Math.PI * 2
groupRef.current.position.y = -offset * 10
})
return (
<group ref={groupRef}>
<Float speed={2} floatIntensity={0.5}>
<Text
fontSize={0.8}
color="white"
anchorX="center"
anchorY="middle"
position={[0, 0, 0]}
>
Scroll Down
</Text>
</Float>
<mesh position={[0, -5, 0]}>
<torusKnotGeometry args={[1, 0.3, 128, 16]} />
<meshStandardMaterial color="hotpink" metalness={0.8} roughness={0.2} />
</mesh>
<mesh position={[0, -10, 0]}>
<icosahedronGeometry args={[1.5, 4]} />
<meshStandardMaterial color="cyan" metalness={0.9} roughness={0.1} />
</mesh>
</group>
)
}---
Example 3: Glass Material with Transmission
Realistic glass objects using MeshTransmissionMaterial.
import { Canvas } from '@react-three/fiber'
import {
MeshTransmissionMaterial, Environment, OrbitControls,
AccumulativeShadows, RandomizedLight, Center
} from '@react-three/drei'
import { Suspense } from 'react'
function GlassScene() {
return (
<Canvas shadows camera={{ position: [0, 1.5, 4], fov: 45 }}>
<color attach="background" args={['#f0f0f0']} />
<Suspense fallback={null}>
<Center>
<GlassSphere />
<GlassCube position={[2, 0, 0]} />
</Center>
<Environment preset="city" />
</Suspense>
<AccumulativeShadows
temporal
frames={100}
scale={10}
position={[0, -1, 0]}
opacity={0.8}
>
<RandomizedLight amount={8} radius={4} position={[5, 5, -10]} />
</AccumulativeShadows>
<OrbitControls makeDefault />
</Canvas>
)
}
function GlassSphere(props) {
return (
<mesh {...props}>
<sphereGeometry args={[0.8, 64, 64]} />
<MeshTransmissionMaterial
transmission={1}
thickness={0.5}
roughness={0}
chromaticAberration={0.1}
ior={1.5}
color="#ffffff"
backside
backsideThickness={0.3}
resolution={1024}
samples={16}
/>
</mesh>
)
}
function GlassCube({ position }) {
return (
<mesh position={position}>
<boxGeometry args={[1, 1, 1]} />
<MeshTransmissionMaterial
transmission={0.95}
thickness={0.8}
roughness={0.05}
chromaticAberration={0.15}
distortion={0.1}
temporalDistortion={0.1}
ior={1.45}
color="#aaddff"
backside
/>
</mesh>
)
}---
Example 4: Instanced Rendering with Performance Monitor
Efficiently rendering thousands of objects with adaptive performance.
import { Canvas } from '@react-three/fiber'
import {
Instances, Instance, PerformanceMonitor,
AdaptiveDpr, Environment, OrbitControls
} from '@react-three/drei'
import { useState, useMemo } from 'react'
function MassiveScene() {
const [dpr, setDpr] = useState(1.5)
return (
<Canvas dpr={dpr} camera={{ position: [0, 10, 20], fov: 60 }}>
<PerformanceMonitor
onIncline={() => setDpr(2)}
onDecline={() => setDpr(1)}
flipflops={3}
onFallback={() => setDpr(0.5)}
/>
<AdaptiveDpr pixelated />
<Environment preset="warehouse" />
<Cubes count={5000} />
<OrbitControls makeDefault />
</Canvas>
)
}
function Cubes({ count }) {
const positions = useMemo(() => {
return Array.from({ length: count }, () => [
(Math.random() - 0.5) * 50,
(Math.random() - 0.5) * 50,
(Math.random() - 0.5) * 50,
])
}, [count])
return (
<Instances limit={count} range={count}>
<boxGeometry args={[0.5, 0.5, 0.5]} />
<meshStandardMaterial roughness={0.3} metalness={0.8} />
{positions.map((pos, i) => (
<Instance
key={i}
position={pos}
rotation={[Math.random() * Math.PI, Math.random() * Math.PI, 0]}
color={`hsl(${(i / count) * 360}, 70%, 60%)`}
/>
))}
</Instances>
)
}---
Example 5: Annotated 3D Model with Html Labels
Loading a GLTF model with interactive HTML annotations positioned in 3D space.
import { Canvas } from '@react-three/fiber'
import {
useGLTF, Html, OrbitControls, Environment, Billboard, Text
} from '@react-three/drei'
import { Suspense, useState } from 'react'
function AnnotatedModel() {
return (
<Canvas camera={{ position: [0, 2, 5], fov: 50 }}>
<Suspense fallback={null}>
<Environment preset="apartment" />
<CarModel />
</Suspense>
<OrbitControls makeDefault />
</Canvas>
)
}
function CarModel() {
const { nodes, materials } = useGLTF('/car.glb')
const [active, setActive] = useState(null)
return (
<group>
<primitive object={nodes.Scene} />
{/* Engine annotation */}
<Annotation
position={[0, 1.2, 1.5]}
label="Engine"
description="V8 Twin-Turbo, 450 HP"
isActive={active === 'engine'}
onClick={() => setActive(active === 'engine' ? null : 'engine')}
/>
{/* Wheel annotation */}
<Annotation
position={[1.5, 0.4, 1.8]}
label="Wheels"
description="20-inch alloy, Michelin PS4S"
isActive={active === 'wheels'}
onClick={() => setActive(active === 'wheels' ? null : 'wheels')}
/>
</group>
)
}
function Annotation({ position, label, description, isActive, onClick }) {
return (
<group position={position}>
{/* 3D marker */}
<Billboard>
<mesh onClick={onClick}>
<circleGeometry args={[0.1, 32]} />
<meshBasicMaterial color={isActive ? '#ff6600' : '#ffffff'} />
</mesh>
</Billboard>
{/* HTML tooltip */}
{isActive && (
<Html
transform
distanceFactor={8}
center
occlude
style={{
background: 'rgba(0, 0, 0, 0.8)',
color: 'white',
padding: '12px 16px',
borderRadius: '8px',
fontSize: '14px',
whiteSpace: 'nowrap',
pointerEvents: 'none',
}}
>
<strong>{label}</strong>
<p style={{ margin: '4px 0 0', opacity: 0.8 }}>{description}</p>
</Html>
)}
</group>
)
}
useGLTF.preload('/car.glb')threejs-impl-drei — Methods Reference
Controls
OrbitControls
<OrbitControls
makeDefault?: boolean // ALWAYS set true for primary controls
enableDamping?: boolean // default: true
dampingFactor?: number // default: 0.05
enableZoom?: boolean // default: true
enableRotate?: boolean // default: true
enablePan?: boolean // default: true
minDistance?: number // minimum zoom distance
maxDistance?: number // maximum zoom distance
minPolarAngle?: number // minimum vertical angle (radians)
maxPolarAngle?: number // maximum vertical angle (radians)
minAzimuthAngle?: number // minimum horizontal angle
maxAzimuthAngle?: number // maximum horizontal angle
target?: [x, y, z] // orbit target point
onChange?: (e: Event) => void // fires on camera change
onStart?: (e: Event) => void
onEnd?: (e: Event) => void
/>CameraControls
<CameraControls
makeDefault?: boolean
minDistance?: number
maxDistance?: number
minPolarAngle?: number
maxPolarAngle?: number
smoothTime?: number // transition duration in seconds
draggingSmoothTime?: number
azimuthRotateSpeed?: number
polarRotateSpeed?: number
dollySpeed?: number
truckSpeed?: number
/>Imperative methods via ref:
ref.current.setLookAt(posX, posY, posZ, targetX, targetY, targetZ, enableTransition)ref.current.dolly(distance, enableTransition)ref.current.truck(x, y, enableTransition)ref.current.rotate(azimuth, polar, enableTransition)ref.current.fitToBox(box3OrObject, enableTransition, options)ref.current.setPosition(x, y, z, enableTransition)ref.current.setTarget(x, y, z, enableTransition)
ScrollControls / useScroll
<ScrollControls
pages?: number // number of scroll pages (default: 1)
distance?: number // scroll factor (default: 1)
damping?: number // scroll smoothing (default: 0.25)
horizontal?: boolean // horizontal scroll (default: false)
enabled?: boolean // enable/disable (default: true)
infinite?: boolean // infinite scroll (default: false)
eps?: number // scroll threshold (default: 0.00001)
>
{children}
</ScrollControls>const scroll = useScroll()
scroll.offset // normalized scroll position 0..1
scroll.delta // scroll speed
scroll.visible(from, range) // visibility within range
scroll.range(from, range) // clamped progress within range
scroll.curve(from, range) // bell curve within rangePresentationControls
<PresentationControls
global?: boolean // respond to events globally
cursor?: boolean // show grab cursor
snap?: boolean | object // snap back to initial rotation
speed?: number // rotation speed (default: 1)
zoom?: number // zoom speed (default: 1)
rotation?: [x, y, z] // initial rotation
polar?: [min, max] // vertical rotation limits
azimuth?: [min, max] // horizontal rotation limits
config?: SpringConfig // react-spring config
/>TransformControls
<TransformControls
object?: THREE.Object3D // target object (or wrap children)
mode?: 'translate' | 'rotate' | 'scale'
space?: 'world' | 'local'
showX?: boolean
showY?: boolean
showZ?: boolean
size?: number // gizmo size
onObjectChange?: () => void
/>KeyboardControls / useKeyboardControls
<KeyboardControls
map={[
{ name: 'forward', keys: ['ArrowUp', 'KeyW'] },
{ name: 'backward', keys: ['ArrowDown', 'KeyS'] },
{ name: 'jump', keys: ['Space'] },
]}
>
{children}
</KeyboardControls>const [subscribeKeys, getKeys] = useKeyboardControls()
const pressed = getKeys() // { forward: true, backward: false, jump: false }
subscribeKeys((state) => { }) // subscribe to all changes
subscribeKeys(
(state) => state.jump, // selector
(pressed) => { } // callback when jump changes
)---
Environment
Environment
<Environment
preset?: 'apartment' | 'city' | 'dawn' | 'forest' | 'lobby' |
'night' | 'park' | 'studio' | 'sunset' | 'warehouse'
files?: string | string[] // HDR/EXR file path(s)
path?: string // base path for files
background?: boolean | 'only' // show as scene background
blur?: number // background blur (0..1)
resolution?: number // cube map resolution (default: 256)
near?: number // near plane for ground projection
far?: number // far plane for ground projection
ground?: { height, radius } // ground-projected environment
map?: THREE.Texture // pre-loaded texture
frames?: number // render frames (Infinity = realtime)
encoding?: THREE.TextureEncoding
/>Lightformer
<Lightformer
form?: 'circle' | 'ring' | 'rect' // shape (default: 'rect')
intensity?: number // light intensity
color?: string | THREE.Color // light color
position?: [x, y, z]
rotation?: [x, y, z]
scale?: number | [w, h, d]
target?: [x, y, z] // look-at target
/>useEnvironment
const envMap = useEnvironment({
preset?: string,
files?: string | string[],
path?: string,
resolution?: number,
extensions?: (loader: Loader) => void,
})---
Shadows
ContactShadows
<ContactShadows
opacity?: number // shadow darkness (default: 1)
scale?: number | [w, h] // shadow plane size (default: 10)
blur?: number // blur passes (default: 1)
far?: number // shadow distance (default: 10)
resolution?: number // texture resolution (default: 512)
color?: string // shadow color (default: '#000000')
frames?: number // render frames (1 = bake, Infinity = realtime)
position?: [x, y, z]
/>AccumulativeShadows
<AccumulativeShadows
temporal?: boolean // spread across frames
frames?: number // accumulation frames (default: 40)
alphaTest?: number // alpha cutoff (default: 0.75)
scale?: number // shadow plane size
position?: [x, y, z]
opacity?: number // overall opacity
color?: string // shadow color
toneMapped?: boolean
/>RandomizedLight
<RandomizedLight
amount?: number // number of lights (default: 8)
radius?: number // jitter radius (default: 1)
intensity?: number // light intensity (default: Math.PI)
ambient?: number // ambient light fraction (default: 0.5)
position?: [x, y, z]
bias?: number // shadow bias
mapSize?: number // shadow map resolution
size?: number // light area size
near?: number
far?: number
castShadow?: boolean
/>---
Text and HTML
Text
<Text
font?: string // font URL (.woff, .woff2, .ttf, .otf)
fontSize?: number // default: 1
color?: string // text color
maxWidth?: number // word wrap width
lineHeight?: number // line height multiplier
letterSpacing?: number // letter spacing
textAlign?: 'left' | 'right' | 'center' | 'justify'
anchorX?: 'left' | 'center' | 'right' | number
anchorY?: 'top' | 'top-baseline' | 'middle' | 'bottom-baseline' | 'bottom' | number
outlineWidth?: number | string
outlineColor?: string
outlineOpacity?: number
strokeWidth?: number
strokeColor?: string
fillOpacity?: number
depthOffset?: number
overflowWrap?: 'normal' | 'break-word'
whiteSpace?: 'normal' | 'nowrap' | 'overflowWrap'
characters?: string // pre-render charset for SDF
onSync?: (troika) => void // fires after text layout
>
{string}
</Text>Text3D
<Text3D
font={string} // REQUIRED: JSON font URL
size?: number // default: 1
height?: number // extrusion depth (default: 0.2)
bevelEnabled?: boolean // default: false
bevelSize?: number // default: 0.02
bevelThickness?: number // default: 0.1
bevelSegments?: number // default: 1
bevelOffset?: number // default: 0
curveSegments?: number // default: 8
letterSpacing?: number // default: 0
lineHeight?: number // default: 1
>
{string}
<meshStandardMaterial /> // MUST provide material as child
</Text3D>Html
<Html
as?: string // wrapper element (default: 'div')
transform?: boolean // transform with 3D position
sprite?: boolean // always face camera
distanceFactor?: number // scale based on distance from camera
center?: boolean // center the element
occlude?: boolean | Object3D[] // hide behind 3D objects
zIndexRange?: [number, number] // z-index range (default: [16777271, 0])
portal?: React.RefObject // render into portal
prepend?: boolean // prepend to container
fullscreen?: boolean // fill entire viewport
className?: string
style?: CSSProperties
castShadow?: boolean
receiveShadow?: boolean
wrapperClass?: string
pointerEvents?: string
/>---
Materials
MeshTransmissionMaterial
<MeshTransmissionMaterial
transmission?: number // transmission factor (default: 1)
thickness?: number // glass thickness (default: 0)
roughness?: number // surface roughness
chromaticAberration?: number // color fringing (default: 0.06)
anisotropy?: number // anisotropic reflections
distortion?: number // distortion (default: 0)
distortionScale?: number
temporalDistortion?: number // animated distortion
ior?: number // index of refraction (default: 1.5)
color?: string // transmission color
backside?: boolean // render backside (default: false)
backsideThickness?: number
resolution?: number // FBO resolution (default: 1024)
samples?: number // MSAA samples (default: 10)
background?: THREE.Texture // background texture
/>MeshReflectorMaterial
<MeshReflectorMaterial
blur?: [number, number] // blur x/y (default: [0, 0])
resolution?: number // FBO resolution (default: 256)
mixBlur?: number // blur mix (default: 0)
mixStrength?: number // reflection strength (default: 1)
roughness?: number
depthScale?: number // depth-based reflection (default: 0)
minDepthThreshold?: number // depth range start (default: 0.9)
maxDepthThreshold?: number // depth range end (default: 1)
color?: string
metalness?: number
mirror?: number // mirror factor (0..1)
/>shaderMaterial Helper
const MyMaterial = shaderMaterial(
uniforms: { [key: string]: any }, // uniform defaults
vertexShader: string, // GLSL vertex
fragmentShader: string, // GLSL fragment
onInit?: (material: ShaderMaterial) => void
)Returns a class. ALWAYS register with extend({ MyMaterial }) before use in JSX.
---
Loaders
useGLTF
const result = useGLTF(
url: string | string[],
useDraco?: boolean | string, // enable Draco (true = CDN decoder)
useMeshOpt?: boolean, // enable MeshOpt
extendLoader?: (loader: GLTFLoader) => void
)
// result: { nodes, materials, scene, animations, asset }
// nodes: Record<string, THREE.Mesh | THREE.Group> (by name)
// materials: Record<string, THREE.Material> (by name)
useGLTF.preload(url, useDraco?, useMeshOpt?)useTexture
const texture = useTexture(url: string)
const textures = useTexture(urls: string[])
const maps = useTexture({
map: string,
normalMap?: string,
roughnessMap?: string,
metalnessMap?: string,
aoMap?: string,
displacementMap?: string,
emissiveMap?: string,
})
useTexture.preload(url)useAnimations
const { actions, names, mixer, ref } = useAnimations(
clips: THREE.AnimationClip[],
root?: React.RefObject<THREE.Object3D>
)
// actions: Record<string, THREE.AnimationAction | null>
// names: string[]
// mixer: THREE.AnimationMixer
// ref: React.RefObject (pass as ref to root group)useVideoTexture
const texture = useVideoTexture(
src: string | MediaStream,
props?: {
unsuspend?: 'canplay' | 'canplaythrough' | 'loadedmetadata'
start?: boolean
crossOrigin?: string
muted?: boolean
loop?: boolean
playsInline?: boolean
}
)---
Performance
Instances / Instance
<Instances
limit?: number // max instances (default: 1000)
range?: number // visible instances (default: limit)
>
<boxGeometry /> // shared geometry
<meshStandardMaterial /> // shared material
<Instance
position?: [x, y, z]
rotation?: [x, y, z]
scale?: number | [x, y, z]
color?: string | THREE.Color
onClick?: (e) => void
/>
</Instances>Merged
<Merged
meshes: THREE.Mesh[] | { [key: string]: THREE.Mesh }
>
{(MeshComponent1, MeshComponent2, ...) => ReactNode}
// or with object: {(meshes) => <meshes.Chair />}
</Merged>Detailed (LOD)
<Detailed distances={[0, 50, 100]}>
<HighPolyMesh /> // shown 0-50 units
<MidPolyMesh /> // shown 50-100 units
<LowPolyMesh /> // shown 100+ units
</Detailed>PerformanceMonitor
<PerformanceMonitor
ms?: number // sample window in ms (default: 200)
iterations?: number // iterations before action (default: 10)
threshold?: number // FPS threshold (default: 0.75)
onIncline?: () => void // FPS improving
onDecline?: () => void // FPS dropping
onFallback?: () => void // after N flipflops
flipflops?: number // max flipflops before fallback (default: Infinity)
factor?: number // current performance factor (0..1)
onChange?: (api) => void // fires on every sample
/>---
Staging
Center
<Center
top?: boolean // align top
right?: boolean // align right
bottom?: boolean // align bottom
left?: boolean // align left
front?: boolean // align front
back?: boolean // align back
precise?: boolean // use precise bounding box
onCentered?: (props: { container, width, height, depth }) => void
/>Float
<Float
speed?: number // animation speed (default: 1)
rotationIntensity?: number // rotation amount (default: 1)
floatIntensity?: number // float amount (default: 1)
floatingRange?: [min, max] // y-axis range (default: [-0.1, 0.1])
>
{children}
</Float>Bounds
<Bounds
fit?: boolean // auto-fit on mount (default: false)
clip?: boolean // auto-clip near/far (default: false)
observe?: boolean // watch for content changes (default: false)
margin?: number // extra padding (default: 1.2)
maxDuration?: number // animation duration
interpolateFunc?: fn // custom interpolation
>
{children}
</Bounds>Imperative: ref.current.refresh().clip().fit()
---
Portals
View
<View
index?: number // render order
frames?: number // render frames (Infinity = continuous)
track: React.RefObject // DOM element to track
>
{children}
</View>RenderTexture
<RenderTexture
width?: number
height?: number
frames?: number // Infinity = continuous
stencilBuffer?: boolean
depthBuffer?: boolean
generateMipmaps?: boolean
>
{scene content}
</RenderTexture>MeshPortalMaterial
<MeshPortalMaterial
blend?: number // 0 = no portal, 1 = full portal
resolution?: number
blur?: number
worldUnits?: boolean
eventPriority?: number
>
{scene content rendered inside mesh}
</MeshPortalMaterial>