thedivergentai/godot-3d-lighting
Expert patterns for Godot 3D lighting including DirectionalLight3D shadow cascades, OmniLight3D attenuation, SpotLight3D projectors, VoxelGI vs SDFGI, and LightmapGI baking. Use when implementing realistic 3D lighting, shadow optimization, global illumination, or light probes. Trigger keywords: DirectionalLight3D, OmniLight3D, SpotLight3D, shadow_enabled, directional_shadow_mode, directional_shadow_split, omni_range, omni_attenuation, spot_range, spot_angle, VoxelGI, SDFGI, LightmapGI, ReflectionProbe, Environment, WorldEnvironment.
npx skills add https://github.com/thedivergentai/GD-Agentic-Skills --skill godot-3d-lighting
Expert guidance for realistic 3D lighting with shadows and global illumination.
size to tightly fit your scene.light_lod_optimizer.gd / distance fade before raising atlas size.omni_range/spot_range, and distance_fade_* so far lights leave the cluster.> MANDATORY: Read the appropriate script before implementing the corresponding pattern.
| Scene | Load | Do NOT Load |
|-------|------|-------------|
| Outdoor Forward+ | shadow_cascade_tuner.gd + sdfgi_probe_manager.gd (+ day_night_cycle.gd if time-of-day) | fake_gi_bounce.gd, indoor VoxelGI bake unless hybrid; environment_blender.gd / fog unless atmosphere task |
| Indoor sealed geometry | MANDATORY VoxelGI via light_probe_manager.gd + lighting_manager.gd + ReflectionProbes | SDFGI on tiny interiors; Mobile fake GI unless targeting Mobile; sky/fog scripts unless zone blend |
| Mobile / Compatibility | MANDATORY fake_gi_bounce.gd + light budgets in light_lod_optimizer.gd | Real-time SDFGI (Forward+ only); HDR sky/volumetric body recipes |
| Lightmap bake / hybrid static | MANDATORY lightmap_bake_helper.gd (+ Shadowmasking for outdoor) | Runtime SDFGI as bake substitute; disabling lights via Visible |
| Sky / Environment blend only | environment_blender.gd | Cascade/GI managers when only tonemap/ambient/sky changes |
| Volumetric fog / shafts | volumetric_fx.gd (+ volumetric_fog_zones.gd for localized density) | Pure light-budget / shadow-atlas tuning tasks |
| Pure light budget / shadow LOD | light_lod_optimizer.gd, shadow_bias_tuner.gd | Do NOT Load environment_blender.gd, volumetric_fx.gd, day-night unless required |
Dynamic sun position and color based on time-of-day. Handles DirectionalLight3D rotation, color temperature, and intensity curves. Use for outdoor day/night systems.
VoxelGI and SDFGI management for global illumination setup.
Dynamic light pooling and LOD. Manages light culling and shadow toggling based on camera distance. Use for performance optimization with many lights.
Volumetric fog and god ray configuration. Runtime fog density/color adjustments and light shaft setup. Use for atmospheric effects.
Expert logic for adjusting DirectionalLight3D shadow split distances dynamically based on sun angle and camera tilt.
Advanced LightmapGI configuration pattern using Shadowmasking mode for hybrid static/dynamic shadowing.
Dynamic quality scaler for real-time Global Illumination (SDFGI). Adjusts cell size and occlusion for performance/quality trade-offs.
Smoothly transitioning localized fog density for cave entrances or forest clearings using Tweens and Area3D triggers.
Efficient 'Mobile-GI' pattern. Simulates light bouncing off the floor using non-shadowed directional fill lights.
Architectural pattern for transitioning WorldEnvironment parameters (Sky, Ambient, Tonemap) during gameplay.
Optimization script for correcting 'Peter Panning' and 'Shadow Acne' on high-fidelity directional lights.
Distance-based shadow and visibility culling for OmniLight3D nodes in dense environments.
Performance-aware ReflectionProbe handling using manual 'Update Once' triggers for large environmental changes.
High-detail lighting using Projector textures to fake complex shadow patterns (grates, glass ripples).
Area3D-driven camera Environment blend for cave/desert transitions (duplicate env resource; tween exposure/ambient).
Runtime RenderingServer profile: half-res SDFGI, shadow atlas size, volumetric fog density.
# For outdoor scenes with camera moving from near to far
extends DirectionalLight3D
func _ready() -> void:
shadow_enabled = true
directional_shadow_mode = SHADOW_PARALLEL_4_SPLITS
# Split distances (in meters from camera)
directional_shadow_split_1 = 10.0 # First cascade: 0-10m
directional_shadow_split_2 = 50.0 # Second: 10-50m
directional_shadow_split_3 = 200.0 # Third: 50-200m
# Fourth cascade: 200m - max shadow distance
directional_shadow_max_distance = 500.0
# Quality vs performance
directional_shadow_blend_splits = true # Smooth transitions
MANDATORY day_night_cycle.gd — do not re-inline sun energy/color recipes here.
Keep omni_range tight; prefer quadratic attenuation. Flicker/campfire loops belong in scene scripts — not this body. Shadowed Omni count is a hard budget (see NEVER).
MANDATORY spotlight_projector_setup.gd for range/angle/projector cookies and camera-follow flashlights. Do not paste Spot setup here.
Use the golden-path table above. Body decision only:
| Path | Script | When |
|------|--------|------|
| Indoor / sealed | MANDATORY light_probe_manager.gd (+ lighting_manager.gd) | Tight VoxelGI extents per room; never paper-thin walls |
| Outdoor Forward+ | MANDATORY sdfgi_probe_manager.gd | Real-time GI; Do NOT Load on Mobile/Compatibility |
| Static / Mobile bake | MANDATORY lightmap_bake_helper.gd | LightmapGI + Shadowmasking; bake mode ≠ Visible hide |
| No GI budget | MANDATORY fake_gi_bounce.gd | Fill lights only |
Do not re-inline VoxelGI/SDFGI/Lightmap property setup here.
Sky/ambient/tonemap transitions → MANDATORY environment_blender.gd. Volumetric fog / shafts → MANDATORY volumetric_fx.gd (+ volumetric_fog_zones.gd for caves/forests).
Do NOT Load these for pure light-budget / shadow-atlas / cascade tuning — stay on light_lod_optimizer.gd / shadow_cascade_tuner.gd.
For localized reflections (mirrors, shiny floors):
# reflection_probe.gd
extends ReflectionProbe
func _ready() -> void:
# Capture area
size = Vector3(10, 5, 10)
# Quality
resolution = ReflectionProbe.RESOLUTION_512
# Update mode
update_mode = ReflectionProbe.UPDATE_ONCE # Bake once
# or UPDATE_ALWAYS for dynamic reflections (expensive)
# Recommended limits:
# - DirectionalLight3D with shadows: 1-2
# - OmniLight3D with shadows: 3-5
# - SpotLight3D with shadows: 2-4
# - OmniLight3D without shadows: 20-30
# - SpotLight3D without shadows: 15-20
# Disable shadows on minor lights
@onready var candle_lights: Array = [$Candle1, $Candle2, $Candle3]
func _ready() -> void:
for light in candle_lights:
light.shadow_enabled = false # Save performance
# Disable shadows for distant lights
extends OmniLight3D
@export var shadow_max_distance := 50.0
func _process(delta: float) -> void:
var camera := get_viewport().get_camera_3d()
if camera:
var dist := global_position.distance_to(camera.global_position)
shadow_enabled = (dist < shadow_max_distance)
# Problem: Thin floors let shadows through
# Solution: Increase shadow bias
extends DirectionalLight3D
func _ready() -> void:
shadow_enabled = true
shadow_bias = 0.1 # Increase if shadows bleed through
shadow_normal_bias = 2.0
# Problem: VoxelGI light bleeds through walls
# Solution: Place VoxelGI nodes per-room, don't overlap
# Also: Ensure walls have proper thickness (not paper-thin)
Rendering real-time shadows for distant objects is too expensive. Use Shadowmasking by setting a DirectionalLight3D to the Dynamic bake mode while baking a LightmapGI. This bakes distant shadows into a texture while allowing dynamic objects to cast real-time shadows up close, preventing "double shadowing" artifacts.
If you cannot afford GI at all (e.g., strict mobile constraints), fake it! Duplicate your main DirectionalLight3D, rotate it 180 degrees (pointing up from the ground), turn Shadows OFF, set Specular to 0.0, and reduce Energy to 10-40%. This cheaply simulates bounced floor lighting.
Godot's OmniLight3D scaling can simulate Percentage-Closer Soft Shadows (blurrier shadows further from the caster).
extends OmniLight3D
func _ready() -> void:
# Simulates area lights and Percentage-Closer Soft Shadows (PCSS).
# Note: High performance cost. Keep the number of lights with light_size > 0.0 low.
light_size = 0.5
shadow_enabled = true
# Distance fade culls the light and shadow completely when out of range,
# preventing the clustered renderer from choking on too many overlapping PCSS lights.
distance_fade_enabled = true
distance_fade_begin = 20.0
distance_fade_length = 5.0
Smoothly transition between lighting environments (e.g., entering a dark cave from a bright desert) using Area3D triggers and Tween-driven Camera3D overrides.
class_name LightVolumeTrigger extends Area3D
@export var interior_environment: Environment
@export var transition_duration: float = 2.0
func _ready() -> void:
body_entered.connect(_on_body_entered)
body_exited.connect(_on_body_exited)
func _on_body_entered(body: Node3D) -> void:
if body.is_in_group("player"):
var camera := get_viewport().get_camera_3d()
# Duplicate to avoid modifying the original resource
if not camera.environment:
camera.environment = interior_environment.duplicate()
var tween := create_tween().set_parallel(true)
# Interpolate key properties for visual adaptation
tween.tween_property(camera.environment, "tonemap_exposure", interior_environment.tonemap_exposure, transition_duration)
tween.tween_property(camera.environment, "ambient_light_energy", interior_environment.ambient_light_energy, transition_duration)
func _on_body_exited(body: Node3D) -> void:
if not body.is_in_group("player"):
return
var camera := get_viewport().get_camera_3d()
if camera == null or camera.environment == null:
return
var world_env := get_tree().get_first_node_in_group("world_environment") as WorldEnvironment
var target := world_env.environment if world_env and world_env.environment else Environment.new()
var tween := create_tween().set_parallel(true)
tween.tween_property(camera.environment, "tonemap_exposure", target.tonemap_exposure, transition_duration)
tween.tween_property(camera.environment, "ambient_light_energy", target.ambient_light_energy, transition_duration)
tween.chain().tween_callback(func():
# Return to WorldEnvironment ownership when blend completes
camera.environment = null
)
> [!TIP]
> Place a ReflectionProbe inside the interior with interior = true. Godot will automatically blend this with the exterior environment as the player transitions.
Fake interior depth on window quads is a custom spatial-shader specialty — implement in a project shader or route to godot-shaders-basics. Do not keep incomplete ray-box stubs in this skill body.
Manage complex lighting features (Shadows, SDFGI, Fog) at runtime using the RenderingServer API for direct engine control.
class_name LightingQualityManager extends Node
func apply_low_quality_profile(env_rid: RID) -> void:
# 1. SDFGI Optimization
# Huge performance gain: Render GI buffers at half resolution
RenderingServer.gi_set_use_half_resolution(true)
RenderingServer.environment_set_sdfgi_ray_count(RenderingServer.ENV_SDFGI_RAY_COUNT_4)
RenderingServer.environment_set_sdfgi_frames_to_converge(RenderingServer.ENV_SDFGI_CONVERGE_IN_30_FRAMES)
# 2. Shadow Optimization
# Reduce global directional shadow atlas
RenderingServer.directional_shadow_atlas_set_size(2048, true)
RenderingServer.directional_soft_shadow_filter_set_quality(RenderingServer.SHADOW_QUALITY_SOFT_VERY_LOW)
# Reduce positional (Omni/Spot) shadows for current viewport
get_viewport().positional_shadow_atlas_size = 1024
# 3. Volumetric Fog
# Disable or heavily reduce fog detail
RenderingServer.environment_set_volumetric_fog(env_rid, false, 0.01, Color.WHITE, Color.BLACK, 0.0, 0.2, 64.0, 2.0, 1.0, true, 0.9, 0.0, 1.0)
| Topic | Reference / script |
|-------|-------------------|
| VoxelGI / SDFGI / LightmapGI setup | gi-and-bake-recipes.md |
| Sky / fog / HDR environment | environment-and-fog.md |
> Progressive disclosure: open Official Documentation links only when researching a specific API; load Related Skills when routing to a peer domain — do not preload the whole lattice.
gi_mode decide how surfaces receive bounced and baked light.environment overrides and exposure pairing for cave/interior light-volume transitions.Take thedivergentai/godot-3d-lighting from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.