Mastering Three.js Lighting: Illuminating Your 3D World

Welcome back! 👋 In my previous posts, I covered Three.js basics, geometries and materials. Today, I'm diving into lighting - one of the most important aspects of creating realistic 3D scenes.
Good lighting can make or break your 3D scene. It's the difference between a flat, lifeless render and a vibrant, realistic world. Let's explore how to light our scenes like a professional photographer!
What is Lighting in Three.js?
Lighting in Three.js simulates how light behaves in the real world. Different light types mimic different real-world light sources:
Sun (DirectionalLight)
Light bulbs (PointLight)
Flashlights (SpotLight)
Sky lighting (HemisphereLight)
Overall brightness (AmbientLight)
Understanding when and how to use each light type is key to creating the mood and atmosphere you want.
The Photography Analogy
Think of Three.js lighting is like setting up a photography or film studio:
Key Light (main light source) - DirectionalLight or SpotLight
Fill Light (softens shadows) - AmbientLight or HemisphereLight
Rim/Back Light (separates subject from background) - PointLight or SpotLight
Practical Lights (visible light sources) - PointLight with visible geometry
Just like in photography, you rarely use just one light. The magic happens when you combine multiple lights!
Project Setup
Here's our HTML structure with controls:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js Lighting Guide</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: Arial, sans-serif;
}
canvas {
display: block;
}
#controls {
position: absolute;
top: 20px;
right: 20px;
color: white;
background: rgba(0, 0, 0, 0.85);
padding: 20px;
border-radius: 8px;
font-size: 13px;
max-width: 280px;
max-height: 90vh;
overflow-y: auto;
}
#controls h3 {
margin-top: 0;
color: #4ecdc4;
font-size: 16px;
}
.light-section {
margin: 15px 0;
padding: 10px;
background: rgba(255, 255, 255, 0.05);
border-radius: 4px;
}
.light-section h4 {
margin: 0 0 10px 0;
color: #ffe66d;
font-size: 14px;
}
.control-group {
margin: 8px 0;
}
.control-group label {
display: block;
margin-bottom: 3px;
color: #aaa;
font-size: 11px;
}
input[type="range"] {
width: 100%;
}
input[type="checkbox"] {
margin-right: 5px;
}
.toggle-label {
display: flex;
align-items: center;
cursor: pointer;
}
#info {
position: absolute;
top: 20px;
left: 20px;
color: white;
background: rgba(0, 0, 0, 0.85);
padding: 15px;
border-radius: 8px;
font-size: 13px;
max-width: 250px;
}
#info h3 {
margin-top: 0;
color: #4ecdc4;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div id="info">
<h3>Lighting Demo</h3>
<p>Toggle different lights to see how they affect the scene. Light helpers show light positions.</p>
</div>
<div id="controls">
<h3>Light Controls</h3>
<div class="light-section">
<h4>Ambient Light</h4>
<div class="control-group">
<label class="toggle-label">
<input type="checkbox" id="ambientToggle" checked>
Enable
</label>
</div>
<div class="control-group">
<label>Intensity: <span id="ambientIntensity">0.3</span></label>
<input type="range" id="ambientSlider" min="0" max="1" step="0.1" value="0.3">
</div>
</div>
<div class="light-section">
<h4>Directional Light</h4>
<div class="control-group">
<label class="toggle-label">
<input type="checkbox" id="directionalToggle" checked>
Enable
</label>
</div>
<div class="control-group">
<label>Intensity: <span id="directionalIntensity">1.0</span></label>
<input type="range" id="directionalSlider" min="0" max="2" step="0.1" value="1">
</div>
</div>
<div class="light-section">
<h4>Point Light</h4>
<div class="control-group">
<label class="toggle-label">
<input type="checkbox" id="pointToggle" checked>
Enable
</label>
</div>
<div class="control-group">
<label>Intensity: <span id="pointIntensity">1.0</span></label>
<input type="range" id="pointSlider" min="0" max="2" step="0.1" value="1">
</div>
</div>
<div class="light-section">
<h4>Spot Light</h4>
<div class="control-group">
<label class="toggle-label">
<input type="checkbox" id="spotToggle" checked>
Enable
</label>
</div>
<div class="control-group">
<label>Intensity: <span id="spotIntensity">1.0</span></label>
<input type="range" id="spotSlider" min="0" max="2" step="0.1" value="1">
</div>
<div class="control-group">
<label>Angle: <span id="spotAngle">0.5</span></label>
<input type="range" id="spotAngleSlider" min="0" max="1.57" step="0.1" value="0.5">
</div>
</div>
<div class="light-section">
<h4>Hemisphere Light</h4>
<div class="control-group">
<label class="toggle-label">
<input type="checkbox" id="hemisphereToggle">
Enable
</label>
</div>
<div class="control-group">
<label>Intensity: <span id="hemisphereIntensity">0.5</span></label>
<input type="range" id="hemisphereSlider" min="0" max="1" step="0.1" value="0.5">
</div>
</div>
<div class="light-section">
<h4>Helpers</h4>
<div class="control-group">
<label class="toggle-label">
<input type="checkbox" id="helpersToggle" checked>
Show Light Helpers
</label>
</div>
</div>
</div>
<script type="module" src="/src/lighting.js"></script>
</body>
</html>
Building Our Lighting Scene
Let's create a scene that demonstrates most used light types!
Step 1: Scene and Camera Setup
import * as THREE from "three";
const canvas = document.getElementById("canvas");
// Scene setup
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a0a); // Very dark background
// Camera setup
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 8, 20);
camera.lookAt(0, 0, 0);
Step 2: Creating Scene Objects
Let's create a simple scene with objects that will show off the lighting:
// Create a ground plane
const groundGeometry = new THREE.PlaneGeometry(50, 50);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x333333,
roughness: 0.8,
metalness: 0.2,
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2; // Rotate to be horizontal
ground.position.y = -2;
scene.add(ground);
// Create center sphere
const sphereGeometry = new THREE.SphereGeometry(2, 64, 64);
const sphereMaterial = new THREE.MeshStandardMaterial({
color: 0xffffff,
roughness: 0.3,
metalness: 0.7,
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(0, 0, 0);
scene.add(sphere);
// Create surrounding cubes to show light distribution
const cubeGeometry = new THREE.BoxGeometry(1.5, 1.5, 1.5);
const cubeMaterial = new THREE.MeshStandardMaterial({
color: 0x4ecdc4,
roughness: 0.5,
metalness: 0.3,
});
const cubes = [];
const cubePositions = [
[-6, -0.5, 0], // Left
[6, -0.5, 0], // Right
[0, -0.5, 6], // Front
];
cubePositions.forEach((pos) => {
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(pos[0], pos[1], pos[2]);
cubes.push(cube);
scene.add(cube);
});
// Add a torus for variety
const torusGeometry = new THREE.TorusGeometry(1.5, 0.5, 16, 100);
const torusMaterial = new THREE.MeshStandardMaterial({
color: 0xff6b6b,
roughness: 0.4,
metalness: 0.6,
});
const torus = new THREE.Mesh(torusGeometry, torusMaterial);
torus.position.set(0, 5, 0);
scene.add(torus);
Step 3: Creating All Light Types
Now let's create each of the six light types with their helpers!
// ============================================
// 1. AMBIENT LIGHT
// Provides overall base illumination, no direction
// Does not cast shadows
// Use case: Base lighting so nothing is completely black.
// Never use alone, combine with directional lights.
// Performance: Very cheap, no calculations needed.
// No helper for ambient light (it's everywhere equally)
// ============================================
const ambientLight = new THREE.AmbientLight(
0xffffff, // White light
0.3 // Low intensity
);
scene.add(ambientLight);
// ============================================
// 2. DIRECTIONAL LIGHT
// Parallel rays like sunlight, illuminates from a direction
// Use case: Outdoor scenes (sun/moon), main key light.
// All rays are parallel regardless of distance.
// Performance: Cheap, good for primary light source.
// ============================================
const directionalLight = new THREE.DirectionalLight(
0xffffff, // White light
1.0 // Full intensity
);
directionalLight.position.set(5, 10, 5);
scene.add(directionalLight);
// Helper to visualize light direction
const directionalHelper = new THREE.DirectionalLightHelper(
directionalLight,
2 // Helper size
);
scene.add(directionalHelper);
// ============================================
// 3. POINT LIGHT
// Radiates light in all directions from a point (like a light bulb)
// Use case: Light bulbs, lamps, candles, explosions.
// Light diminishes with distance (uses distance and decay).
// Performance: Medium cost, use sparingly for many lights.
// ============================================
const pointLight = new THREE.PointLight(
0xff00ff, // Magenta color
1.0, // Intensity
50, // Distance (light fades to 0 at this distance)
2 // Decay (how fast light fades, 2 = realistic)
);
pointLight.position.set(-8, 3, 0);
scene.add(pointLight);
// Helper to visualize light position and range
const pointHelper = new THREE.PointLightHelper(
pointLight,
0.5 // Helper sphere size
);
scene.add(pointHelper);
// ============================================
// 4. SPOT LIGHT
// Cone of light from a point (like a flashlight or stage light)
// Use case: Flashlights, stage spotlights, car headlights.
// Creates dramatic focused lighting with soft edges.
// Performance: Most expensive, use carefully.
// ============================================
const spotLight = new THREE.SpotLight(
0x00ffff, // Cyan color
1.0, // Intensity
30, // Distance
Math.PI / 10, // Angle (cone width in radians)
0.5, // Penumbra (softness of edge, 0-1)
2 // Decay
);
spotLight.position.set(-6, 10, 0);
spotLight.target.position.set(0, 0, 0); // Point at center
scene.add(spotLight);
scene.add(spotLight.target); // Target must be in scene
// Helper to visualize cone
const spotHelper = new THREE.SpotLightHelper(spotLight);
scene.add(spotHelper);
// ============================================
// 5. HEMISPHERE LIGHT
// Sky/ground ambient lighting (different colors from top/bottom)
// Use case: Outdoor scenes with sky lighting, subtle ambient variation.
// Creates natural-looking ambient from above (sky) and below (ground bounce).
// Performance: Cheap, great alternative to plain ambient light.
// ============================================
const hemisphereLight = new THREE.HemisphereLight(
0x0066ff, // Sky color (blue)
0xff6600, // Ground color (orange)
0.5 // Intensity
);
hemisphereLight.position.set(0, 10, 0);
scene.add(hemisphereLight);
// Helper shows sky and ground colors
const hemisphereHelper = new THREE.HemisphereLightHelper(
hemisphereLight,
2 // Helper size
);
scene.add(hemisphereHelper);
// Store references for toggling
const helpers = {
directional: directionalHelper,
point: pointHelper,
spot: spotHelper,
hemisphere: hemisphereHelper,
};
// Initially hide some lights to avoid overwhelming the scene
hemisphereLight.visible = false;
hemisphereHelper.visible = false;
Step 4: Interactive Controls
Let's wire up all the controls to adjust lights in real-time:
// Ambient Light Controls
const ambientToggle = document.getElementById("ambientToggle");
const ambientSlider = document.getElementById("ambientSlider");
const ambientIntensityDisplay = document.getElementById("ambientIntensity");
ambientToggle.addEventListener("change", (e) => {
ambientLight.visible = e.target.checked;
});
ambientSlider.addEventListener("input", (e) => {
const value = parseFloat(e.target.value);
ambientLight.intensity = value;
ambientIntensityDisplay.textContent = value.toFixed(1);
});
// Directional Light Controls
const directionalToggle = document.getElementById("directionalToggle");
const directionalSlider = document.getElementById("directionalSlider");
const directionalIntensityDisplay = document.getElementById(
"directionalIntensity"
);
directionalToggle.addEventListener("change", (e) => {
directionalLight.visible = e.target.checked;
directionalHelper.visible = e.target.checked;
});
directionalSlider.addEventListener("input", (e) => {
const value = parseFloat(e.target.value);
directionalLight.intensity = value;
directionalIntensityDisplay.textContent = value.toFixed(1);
});
// Point Light Controls
const pointToggle = document.getElementById("pointToggle");
const pointSlider = document.getElementById("pointSlider");
const pointIntensityDisplay = document.getElementById("pointIntensity");
pointToggle.addEventListener("change", (e) => {
pointLight.visible = e.target.checked;
pointHelper.visible = e.target.checked;
});
pointSlider.addEventListener("input", (e) => {
const value = parseFloat(e.target.value);
pointLight.intensity = value;
pointIntensityDisplay.textContent = value.toFixed(1);
});
// Spot Light Controls
const spotToggle = document.getElementById("spotToggle");
const spotSlider = document.getElementById("spotSlider");
const spotAngleSlider = document.getElementById("spotAngleSlider");
const spotIntensityDisplay = document.getElementById("spotIntensity");
const spotAngleDisplay = document.getElementById("spotAngle");
spotToggle.addEventListener("change", (e) => {
spotLight.visible = e.target.checked;
spotHelper.visible = e.target.checked;
});
spotSlider.addEventListener("input", (e) => {
const value = parseFloat(e.target.value);
spotLight.intensity = value;
spotIntensityDisplay.textContent = value.toFixed(1);
});
spotAngleSlider.addEventListener("input", (e) => {
const value = parseFloat(e.target.value);
spotLight.angle = value;
spotAngleDisplay.textContent = value.toFixed(2);
});
// Hemisphere Light Controls
const hemisphereToggle = document.getElementById("hemisphereToggle");
const hemisphereSlider = document.getElementById("hemisphereSlider");
const hemisphereIntensityDisplay = document.getElementById(
"hemisphereIntensity"
);
hemisphereToggle.addEventListener("change", (e) => {
hemisphereLight.visible = e.target.checked;
hemisphereHelper.visible = e.target.checked;
});
hemisphereSlider.addEventListener("input", (e) => {
const value = parseFloat(e.target.value);
hemisphereLight.intensity = value;
hemisphereIntensityDisplay.textContent = value.toFixed(1);
});
// Helpers Toggle
const helpersToggle = document.getElementById("helpersToggle");
helpersToggle.addEventListener("change", (e) => {
const visible = e.target.checked;
Object.values(helpers).forEach((helper) => {
// Only show helper if its light is also visible
const lightVisible = helper.light ? helper.light.visible : true;
helper.visible = visible && lightVisible;
});
});
Step 5: Renderer and Animation
// Renderer setup
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true,
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
// Animation loop
function animate() {
requestAnimationFrame(animate);
const time = Date.now() * 0.001;
// Rotate center sphere
sphere.rotation.y = time * 0.3;
// Rotate torus
torus.rotation.x = time * 0.5;
torus.rotation.y = time * 0.05;
// Gently bob cubes up and down
cubes.forEach((cube, index) => {
cube.position.y = -0.5 + Math.sin(time + index) * 0.3;
cube.rotation.y = time * 0.5;
});
// Animate point light in a circle
pointLight.position.x = Math.cos(time * 0.5) * 8;
pointLight.position.z = Math.sin(time * 0.5) * 8;
// Update helpers
if (spotHelper.visible) spotHelper.update();
if (directionalHelper.visible) directionalHelper.update();
renderer.render(scene, camera);
}
animate();
// Handle window resize
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
Understanding Light Properties
Common Properties (Most Lights)
Colour - The colour of the light
light.color = new THREE.Color(0xff0000); // Red light
intensity - How bright the light is
light.intensity = 1.5; // 150% brightness
Distance-Based Properties (Point & Spot Lights)
distance - Maximum range of the light (0 = infinite)
pointLight.distance = 50; // Light fades to 0 at 50 units
decay - How quickly light diminishes with distance
pointLight.decay = 2; // Physically accurate (inverse square law)
// 1 = linear falloff
// 2 = realistic falloff (default)
Spot Light Specific
angle - Width of the light cone (in radians)
spotLight.angle = Math.PI / 6; // 30 degrees
penumbra - Softness of the cone edge (0-1)
spotLight.penumbra = 0.5; // 50% soft edge
// 0 = hard edge
// 1 = very soft edge
target - What the spotlight points at
spotLight.target.position.set(0, 0, 0);
scene.add(spotLight.target); // Must add target to scene!
Hemisphere Light Specific
groundColor - Colour of light from below
hemisphereLight.groundColor = new THREE.Color(0x080820);
When to Use Each Light Type
AmbientLight
Performance: ⚡⚡⚡⚡⚡ (Cheapest)
When to use:
Base lighting, so objects aren't completely black
Fill light to soften harsh shadows
Quick prototyping before adding complex lighting
Best practices:
Keep intensity low (0.2-0.4)
Never use as the only light source
Combine with directional lights
Real-world equivalent: Overcast day, diffused light everywhere
DirectionalLight
Performance: ⚡⚡⚡⚡ (Very Cheap)
When to use:
Outdoor scenes (sun or moon)
Main key light in any scene
When you need parallel light rays
Best practices:
Position far from scene center
Use as primary light source
Great for casting consistent shadows
Real-world equivalent: Sunlight, moonlight
PointLight
Performance: ⚡⚡⚡ (Medium)
When to use:
Light bulbs, lamps, candles
Explosions or fire effects
Small localized light sources
Best practices:
Set appropriate distance and decay
Don't use too many (expensive with many)
Great for accent lighting
Real-world equivalent: Light bulb, candle, torch
SpotLight
Performance: ⚡⚡ (Expensive)
When to use:
Flashlights, car headlights
Stage lighting, theatrical effects
Drawing attention to specific objects
Best practices:
Use sparingly (most expensive)
Adjust angle and penumbra for desired effect
Remember to add target to scene
Real-world equivalent: Flashlight, stage spotlight, car headlight
HemisphereLight
Performance: ⚡⚡⚡⚡ (Cheap)
When to use:
Outdoor scenes with sky
Natural-looking ambient lighting
When you want colour variation (sky vs ground)
Best practices:
Better than plain Ambient Light for realism
Use sky and ground colours that make sense
Great for outdoor environments
Real-world equivalent: Natural sky lighting with ground bounce
RectAreaLight
Performance: ⚡ (Most Expensive)
When to use:
Windows or doors letting in light
TV screens, monitors, LED panels
Soft box photography lighting
When you need realistic area lighting
Best practices:
Only works with Standard/Physical materials!
Use very sparingly (heavy performance cost)
Great for hero objects or key scenes
Real-world equivalent: Window, LED panel, soft box light
Performance Tips and Light Combinations
Performance Hierarchy (Cheapest to Most Expensive)
AmbientLight / HemisphereLight - No calculations
DirectionalLight - Simple directional calculations
PointLight - Distance-based calculations
SpotLight - Cone + distance calculations
RectAreaLight - Complex area calculations
Recommended Light Combinations
Basic Scene (Best Performance)
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
const directional = new THREE.DirectionalLight(0xffffff, 1.0);
Outdoor Scene
const hemisphere = new THREE.HemisphereLight(0x87CEEB, 0x8B4513, 0.6);
const directional = new THREE.DirectionalLight(0xffffff, 0.8);
Indoor Scene
const ambient = new THREE.AmbientLight(0xffffff, 0.3);
const pointLight1 = new THREE.PointLight(0xffffee, 1.0, 20);
const pointLight2 = new THREE.PointLight(0xffffee, 0.8, 15);
Dramatic/Cinematic
const ambient = new THREE.AmbientLight(0xffffff, 0.2);
const spot1 = new THREE.SpotLight(0xffffff, 1.5); // Key light
const spot2 = new THREE.SpotLight(0x4444ff, 0.5); // Rim light
High-Quality Product Visualization
const ambient = new THREE.AmbientLight(0xffffff, 0.3);
const rectArea = new THREE.RectAreaLight(0xffffff, 2.0, 10, 10);
const directional = new THREE.DirectionalLight(0xffffff, 0.8);
What I Learned
1. Lighting Makes or Breaks a Scene
The same geometry and materials look entirely different under different lighting. Good lighting is often more important than complex geometry!
2. Combine Multiple Lights
Professional scenes rarely use just one light. The "three-point lighting" technique from photography (key, fill, rim) applies to 3D too!
3. Light Helpers Are Essential
Always use light helpers when setting up your scene. They show exactly where lights are positioned and what they're illuminating. Turn them off for final render.
4. Performance Matters
More lights = worse performance. Start with cheap lights (Ambient, Directional) and add expensive ones (Spot, RectArea) only where needed.
5. Distance and Decay Are Critical
For Point and Spot lights, setting appropriate distance and decay values is crucial. A decay of 2 is physically accurate and usually looks best.
6. RectAreaLight Limitations
RectAreaLight only works with MeshStandardMaterial and MeshPhysicalMaterial. If your objects look unlit, check your material type!
7. Colour Temperature Matters
Real lights have colour! Sunlight is slightly yellow (0xffffee), fluorescent lights are cooler (0xeeeeff), candlelight is warm orange (0xff8844). Adding subtle colour makes scenes more realistic.
8. Light Position Is Key
The same light at different positions creates entirely different moods. Experiment with positioning lights high, low, front, back, and sides.
9. Hemisphere Light Is Underrated
Instead of plain AmbientLight, HemisphereLight adds subtle colour variation from sky and ground that makes outdoor scenes look much more natural.
10. Start Simple, Then Add
Begin with just ambient + directional light. Once that looks good, add accent lights (point, spot) for drama and interest.
Common Lighting Recipes
Here are some lighting setups I found useful:
Sunny Day
const hemisphere = new THREE.HemisphereLight(0x87CEEB, 0x8B7355, 0.6);
const sun = new THREE.DirectionalLight(0xffffee, 1.0);
sun.position.set(10, 20, 10);
Overcast Day
const ambient = new THREE.AmbientLight(0xcccccc, 0.8);
const directional = new THREE.DirectionalLight(0xffffff, 0.3);
Night Scene with Moon
const ambient = new THREE.AmbientLight(0x111144, 0.2);
const moon = new THREE.DirectionalLight(0xaaaaff, 0.5);
moon.position.set(-10, 20, 5);
Indoor Warm Lighting
const ambient = new THREE.AmbientLight(0x332211, 0.3);
const lamp1 = new THREE.PointLight(0xffaa66, 1.0, 15, 2);
const lamp2 = new THREE.PointLight(0xffaa66, 0.8, 12, 2);
Studio/Gallery Lighting
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
const key = new THREE.SpotLight(0xffffff, 1.5, 30, Math.PI/6, 0.3);
const fill = new THREE.SpotLight(0xffffff, 0.5, 25, Math.PI/4, 0.5);
const rim = new THREE.DirectionalLight(0x8888ff, 0.6);
Debugging Lighting Issues
If your scene looks wrong, check these:
Objects Are Completely Black
Are lights added to the scene?
Do materials need lights? (Basic doesn't, others do)
Is ambient light intensity too low?
Lights Don't Seem to Work
Check light visibility (light.visible = true)
Verify light helpers are showing correct positions
For SpotLight, did you add the target to scene?
For RectAreaLight, are you using Standard/Physical material?
Scene Is Too Dark/Bright
Adjust ambient light intensity (start around 0.3)
Change directional light intensity (start around 1.0)
Check camera exposure (some renderers support this)
Point/Spot Lights Fade Too Fast
Increase distance property
Check decay value (2 is realistic but you can adjust)
Increase intensity
Next Steps
Now that I understand lighting, here's what I'm planning to explore:
Adding shadows to lights for more realism
Using textures on materials
Environment maps for realistic reflections
Post-processing effects (bloom, ambient occlusion)
Combining great lighting with textured materials
Try experimenting with:
Different light colour combinations
Three-point lighting setups key (directional), fill (ambient) and rim (spot light)
Animating light positions for dynamic effects
Adjusting intensity to create different moods
Combining lights to simulate real environments
Understanding lighting is a game-changer. Happy illuminating! 💡



