r3f-gltf-area-lights
v1.0.0
Published
Shape-aware area lighting for GLTF models in React Three Fiber - realistic light emission from object surfaces
Maintainers
Readme
r3f-gltf-area-lights
Shape-aware area lighting for GLTF models in React Three Fiber. This package enables GLTF/GLB objects to emit realistic lighting that follows their geometry, creating natural light emission from entire surfaces rather than point sources.
Features
- 🌟 Geometry-Based Emission: Light radiates from the entire surface of objects
- 🎨 Shape-Aware: Lighting follows object contours and complex geometries
- 🚀 Performance Optimized: Efficient GPU-based calculations
- 🎭 Material Compatible: Works with standard Three.js materials
- 🎮 Easy Integration: Simple React components for r3f projects
- 📦 GLTF Support: Designed specifically for GLTF/GLB models
- ⚡ Dynamic Updates: Supports animated models with real-time light updates
- 🔧 TypeScript Ready: Full TypeScript support with type definitions
Installation
npm install r3f-gltf-area-lights
# or
yarn add r3f-gltf-area-lights
# or
pnpm add r3f-gltf-area-lightsQuick Start
import { Canvas } from '@react-three/fiber'
import { useGLTF } from '@react-three/drei'
import { AreaLightEmitter, AreaLightProvider, useAreaLightManager } from 'r3f-gltf-area-lights'
function Scene() {
const { scene } = useGLTF('/models/neon-sign.glb')
const lightManager = useAreaLightManager()
return (
<AreaLightProvider manager={lightManager}>
<AreaLightEmitter intensity={2} samples={32}>
<primitive object={scene} />
</AreaLightEmitter>
{/* Objects in the scene will receive realistic lighting */}
<mesh>
<sphereGeometry />
<meshStandardMaterial />
</mesh>
</AreaLightProvider>
)
}
export default function App() {
return (
<Canvas>
<Scene />
</Canvas>
)
}Complete Guide for GLB Objects
Basic GLB Setup
import { Canvas } from '@react-three/fiber'
import { useGLTF, OrbitControls } from '@react-three/drei'
import { AreaLightEmitter, AreaLightProvider, useAreaLightManager } from 'r3f-gltf-area-lights'
function GLBModel({ url }) {
const { scene } = useGLTF(url)
return (
<AreaLightEmitter intensity={3} samples={32}>
<primitive object={scene} />
</AreaLightEmitter>
)
}
function Scene() {
const lightManager = useAreaLightManager()
return (
<AreaLightProvider manager={lightManager}>
{/* Your glowing GLB model */}
<GLBModel url="/models/neon-sign.glb" />
{/* Environment that receives light */}
<mesh position={[0, -2, 0]} rotation={[-Math.PI / 2, 0, 0]}>
<planeGeometry args={[10, 10]} />
<meshStandardMaterial color="#1a1a1a" />
</mesh>
</AreaLightProvider>
)
}
export default function App() {
return (
<Canvas camera={{ position: [5, 5, 5] }}>
<OrbitControls />
<Scene />
</Canvas>
)
}Preparing Your GLB Models
For best results with area lighting, your GLB models should:
- Have Emissive Materials: Set emissive color and intensity in your 3D software
- Use Standard Materials: Works with
MeshStandardMaterialorMeshPhysicalMaterial - Optimize Geometry: Fewer polygons = better performance
- Bake Textures: Emissive textures are fully supported
Advanced GLB Example with Multiple Emitters
function ComplexGLBScene() {
const { nodes, materials } = useGLTF('/models/cyberpunk-scene.glb')
const lightManager = useAreaLightManager()
return (
<AreaLightProvider manager={lightManager}>
{/* Neon signs with high intensity */}
<AreaLightEmitter intensity={5} samples={64} color="#ff00ff">
<mesh geometry={nodes.NeonSign.geometry} material={materials.Neon} />
</AreaLightEmitter>
{/* Computer screens with lower intensity */}
<AreaLightEmitter intensity={1} samples={16} range={3}>
<mesh geometry={nodes.Monitor.geometry} material={materials.Screen} />
</AreaLightEmitter>
{/* Animated hologram */}
<AreaLightEmitter intensity={3} samples={32} updateFrequency={30}>
<mesh geometry={nodes.Hologram.geometry} material={materials.Hologram}>
<meshStandardMaterial
{...materials.Hologram}
emissive="#00ffff"
emissiveIntensity={2}
/>
</mesh>
</AreaLightEmitter>
</AreaLightProvider>
)
}Working with Animated GLB Models
import { useAnimations } from '@react-three/drei'
function AnimatedGLB({ url }) {
const { scene, animations } = useGLTF(url)
const { actions } = useAnimations(animations, scene)
useEffect(() => {
// Play animation
actions.Animation?.play()
}, [actions])
return (
<AreaLightEmitter
intensity={3}
samples={32}
updateFrequency={30} // Important for animated models
>
<primitive object={scene} />
</AreaLightEmitter>
)
}How It Works
The package analyzes GLTF models with emissive materials and creates area lights that:
- Sample the Surface: Distributes light emission points across the mesh surface
- Follow Geometry: Each point emits light based on surface normals and area
- Calculate Realistically: Uses physically-based calculations for natural falloff
- Update Dynamically: Supports animated models with real-time updates
API Reference
<AreaLightEmitter>
Wraps GLTF objects to make them emit area lights.
<AreaLightEmitter
intensity={2} // Light intensity multiplier
samples={32} // Number of surface samples (higher = better quality)
range={10} // Maximum light range
color="#ffffff" // Override emission color
updateFrequency={60} // Updates per second for animated models
enabled={true} // Enable/disable the light
>
<primitive object={gltfScene} />
</AreaLightEmitter>Props:
intensity(number): Multiplier for light brightness. Default:1samples(number): Number of surface samples. Range: 4-64. Default:32range(number): Maximum light influence distance. Default:10color(string | THREE.Color): Override emissive color. Default: uses material emissionupdateFrequency(number): Updates per second for animations. Default:60enabled(boolean): Toggle light on/off. Default:true
<AreaLightProvider>
Context provider that manages all area lights in the scene.
<AreaLightProvider manager={lightManager}>
{/* Your scene with area lights */}
</AreaLightProvider>useAreaLightManager()
Hook that creates and provides the area light manager.
const lightManager = useAreaLightManager()
// Access properties
lightManager.getLightCount() // Number of active lights
lightManager.getDataTexture() // GPU data texture
lightManager.update() // Manual update (called automatically)
// Listen to updates
useEffect(() => {
return lightManager.onUpdate(() => {
console.log('Lights updated')
})
}, [lightManager])useAreaLight()
Hook for creating area lights from mesh references (advanced usage).
const meshRef = useRef()
const lightManager = useAreaLightManager()
const areaLight = useAreaLight(meshRef, lightManager, {
intensity: 2,
samples: 16,
enabled: true,
updateFrequency: 30
})Best Practices
Performance Optimization
Sample Count: Balance quality vs performance
- Low (4-16): Fast, good for small/distant objects
- Medium (16-32): Default, good balance
- High (32-64): Best quality, for hero objects
Update Frequency: Reduce for static or slowly moving objects
// Static objects <AreaLightEmitter updateFrequency={0} samples={32}> // Animated objects <AreaLightEmitter updateFrequency={30} samples={16}>Light Range: Smaller ranges improve performance
<AreaLightEmitter range={5} intensity={2}>Light Count: Maximum 16 area lights per scene
Material Setup
For best visual results:
// Good material for receiving light
<meshStandardMaterial
color="#ffffff"
roughness={0.5}
metalness={0}
/>
// Good emissive material
<meshStandardMaterial
color="#ff0000"
emissive="#ff0000"
emissiveIntensity={2}
/>Common Patterns
Multiple Light Sources in One Model
function MultiLightGLB() {
const { nodes } = useGLTF('/model.glb')
return (
<>
<AreaLightEmitter intensity={2} color="#ff0000">
<mesh geometry={nodes.RedLight.geometry} />
</AreaLightEmitter>
<AreaLightEmitter intensity={3} color="#00ff00">
<mesh geometry={nodes.GreenLight.geometry} />
</AreaLightEmitter>
</>
)
}Conditional Lighting
function ToggleableLight({ isOn }) {
const { scene } = useGLTF('/lamp.glb')
return (
<AreaLightEmitter enabled={isOn} intensity={2}>
<primitive object={scene} />
</AreaLightEmitter>
)
}Light Color Animation
function AnimatedColorLight() {
const { scene } = useGLTF('/model.glb')
const [hue, setHue] = useState(0)
useFrame((state) => {
setHue(state.clock.elapsedTime * 50 % 360)
})
return (
<AreaLightEmitter
color={`hsl(${hue}, 100%, 50%)`}
intensity={3}
>
<primitive object={scene} />
</AreaLightEmitter>
)
}Troubleshooting
Common Issues
Lights Not Showing
// ❌ Wrong - Missing AreaLightProvider
<AreaLightEmitter intensity={3}>
<primitive object={scene} />
</AreaLightEmitter>
// ✅ Correct - With AreaLightProvider
<AreaLightProvider manager={lightManager}>
<AreaLightEmitter intensity={3}>
<primitive object={scene} />
</AreaLightEmitter>
</AreaLightProvider>Performance Issues
// ❌ Too many samples for multiple lights
{lights.map(light => (
<AreaLightEmitter samples={64} key={light.id}>
{/* ... */}
</AreaLightEmitter>
))}
// ✅ Balanced sample count
{lights.map(light => (
<AreaLightEmitter samples={16} key={light.id}>
{/* ... */}
</AreaLightEmitter>
))}Materials Not Receiving Light
// ❌ Using unsupported material
<meshBasicMaterial />
// ✅ Using standard material
<meshStandardMaterial />
// or
<meshPhysicalMaterial />Debug Tips
Check Light Count:
console.log('Active lights:', lightManager.getLightCount())Verify Material Emissive:
// Ensure your GLB has emissive materials const { materials } = useGLTF('/model.glb') console.log('Emissive:', materials.Light.emissive)Monitor Performance:
import { Stats } from '@react-three/drei' <Canvas> <Stats /> {/* Your scene */} </Canvas>
Technical Details
Architecture Overview
The package uses a sophisticated GPU-based lighting system:
- Geometry Sampling: Extracts representative points from mesh surfaces
- Data Texture: Packs light data into a 256x128 RGBA float texture
- Shader Integration: Custom GLSL chunks integrate with Three.js materials
- Real-time Updates: Supports dynamic meshes with configurable update rates
Limitations
- Maximum 16 area lights per scene (GPU shader limit)
- WebGL 2.0 recommended for best performance
- Large meshes with many samples can impact performance
- Only works with
MeshStandardMaterialandMeshPhysicalMaterial
Performance Benchmarks
Typical performance on modern hardware:
- 8 area lights @ 32 samples each: 60+ FPS
- 16 area lights @ 16 samples each: 60+ FPS
- Single light @ 64 samples: 120+ FPS
Migration Guide
From Point Lights
// Before - Three.js point light
<pointLight position={[0, 5, 0]} intensity={1} color="white" />
// After - Area light from geometry
<AreaLightEmitter intensity={1} samples={32}>
<Box position={[0, 5, 0]} args={[2, 0.1, 2]}>
<meshStandardMaterial emissive="white" emissiveIntensity={1} />
</Box>
</AreaLightEmitter>From Emissive Materials Only
// Before - Just emissive material
<mesh>
<boxGeometry />
<meshStandardMaterial emissive="#ff0000" emissiveIntensity={2} />
</mesh>
// After - Emissive material that casts light
<AreaLightEmitter intensity={2}>
<mesh>
<boxGeometry />
<meshStandardMaterial emissive="#ff0000" emissiveIntensity={2} />
</mesh>
</AreaLightEmitter>Examples
Check out the /examples directory for complete working examples:
basic/- Simple scene with area lights- More examples coming soon!
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Setup
# Clone the repo
git clone https://github.com/yourusername/r3f-gltf-area-lights.git
# Install dependencies
npm install
# Start development build
npm run dev
# Run tests
npm test
# Build for production
npm run buildBrowser Support
- ✅ Chrome/Edge 90+
- ✅ Firefox 85+
- ✅ Safari 14+
- ✅ Mobile browsers with WebGL 2.0
- ⚠️ WebGL 1.0 (limited features)
License
MIT © Vincent Duchateau
