skills/godot-3d-lighting/SKILL.md
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 skillsauth add thedivergentai/gd-agentic-skills godot-3d-lightingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Expert guidance for realistic 3D lighting with shadows and global illumination.
size to tightly fit your scene.MANDATORY: Read the appropriate script before implementing the corresponding pattern.
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).
# 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
# sun_controller.gd
extends DirectionalLight3D
@export var time_of_day := 12.0 # 0-24 hours
@export var rotation_speed := 0.1 # Hours per second
func _process(delta: float) -> void:
time_of_day += rotation_speed * delta
if time_of_day >= 24.0:
time_of_day -= 24.0
# Rotate sun (0° = noon, 180° = midnight)
var angle := (time_of_day - 12.0) * 15.0 # 15° per hour
rotation_degrees.x = -angle
# Adjust intensity
if time_of_day < 6.0 or time_of_day > 18.0:
light_energy = 0.0 # Night
elif time_of_day < 7.0:
light_energy = remap(time_of_day, 6.0, 7.0, 0.0, 1.0) # Sunrise
elif time_of_day > 17.0:
light_energy = remap(time_of_day, 17.0, 18.0, 1.0, 0.0) # Sunset
else:
light_energy = 1.0 # Day
# Color shift
if time_of_day < 8.0 or time_of_day > 16.0:
light_color = Color(1.0, 0.7, 0.4) # Orange (dawn/dusk)
else:
light_color = Color(1.0, 1.0, 0.9) # Neutral white
# torch.gd
extends OmniLight3D
func _ready() -> void:
omni_range = 10.0 # Maximum reach
omni_attenuation = 2.0 # Falloff curve (1.0 = linear, 2.0 = quadratic/realistic)
# For "magical" lights, reduce attenuation
omni_attenuation = 0.5 # Flatter falloff, reaches farther
# campfire.gd
extends OmniLight3D
@export var base_energy := 1.0
@export var flicker_strength := 0.3
@export var flicker_speed := 5.0
func _process(delta: float) -> void:
var flicker := sin(Time.get_ticks_msec() * 0.001 * flicker_speed) * flicker_strength
light_energy = base_energy + flicker
# flashlight.gd
extends SpotLight3D
func _ready() -> void:
spot_range = 20.0
spot_angle = 45.0 # Cone angle (degrees)
spot_angle_attenuation = 2.0 # Edge softness
shadow_enabled = true
# Projector texture (optional - cookie/gobo)
light_projector = load("res://textures/flashlight_mask.png")
# player_flashlight.gd
extends SpotLight3D
@onready var camera: Camera3D = get_viewport().get_camera_3d()
func _process(delta: float) -> void:
if camera:
global_transform = camera.global_transform
| Feature | VoxelGI | SDFGI | |---------|---------|-------| | Setup | Manual bounds per room | Automatic, scene-wide | | Dynamic objects | Fully supported | Partially supported | | Performance | Moderate | Higher cost | | Use case | Indoor, small-medium scenes | Large outdoor scenes | | Godot version | 4.0+ | 4.0+ |
# room_gi.gd - Place one VoxelGI per room/area
extends VoxelGI
func _ready() -> void:
# Tightly fit the room
size = Vector3(20, 10, 20)
# Quality settings
subdiv = VoxelGI.SUBDIV_128 # Higher = better quality, slower
# Bake GI data
bake()
# world_environment.gd
extends WorldEnvironment
func _ready() -> void:
var env := environment
# Enable SDFGI
env.sdfgi_enabled = true
env.sdfgi_use_occlusion = true
env.sdfgi_read_sky_light = true
# Cascades (auto-scale based on camera)
env.sdfgi_min_cell_size = 0.2 # Detail level
env.sdfgi_max_distance = 200.0
# Scene structure:
# - LightmapGI node
# - StaticBody3D meshes with GeometryInstance3D.gi_mode = STATIC
# lightmap_baker.gd
extends LightmapGI
func _ready() -> void:
# Quality settings
quality = LightmapGI.BAKE_QUALITY_HIGH
bounces = 3 # Indirect light bounces
# Bake (editor only, not runtime)
# Click "Bake Lightmaps" button in editor
# world_env.gd
extends WorldEnvironment
func _ready() -> void:
var env := environment
env.background_mode = Environment.BG_SKY
var sky := Sky.new()
var sky_material := PanoramaSkyMaterial.new()
sky_material.panorama = load("res://hdri/sky.hdr")
sky.sky_material = sky_material
env.sky = sky
# Sky contribution to GI
env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
env.ambient_light_sky_contribution = 1.0
extends WorldEnvironment
func _ready() -> void:
var env := environment
env.volumetric_fog_enabled = true
env.volumetric_fog_density = 0.01
env.volumetric_fog_albedo = Color(0.9, 0.9, 1.0) # Blueish
env.volumetric_fog_emission = Color.BLACK
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:
# Reverse tween or clear camera environment to return to WorldEnvironment
pass
[!TIP] Place a
ReflectionProbeinside the interior withinterior = true. Godot will automatically blend this with the exterior environment as the player transitions.
Use shaders to create the illusion of 3D rooms inside flat window planes. This is significantly more performant than rendering actual geometry for every building interior.
shader_type spatial;
// Texture array containing wall/floor/ceiling layers
uniform sampler2DArray room_textures;
uniform vec3 room_dimensions = vec3(1.0, 1.0, 1.0);
void fragment() {
// 1. Transform view vector into object space
vec3 view_dir = normalize(VIEW * mat3(INV_VIEW_MATRIX * MODEL_MATRIX));
// 2. Ray-box intersection (Simplified logic)
// Calculate the 'depth' of the fake room based on view angle
vec3 pos = vec3(UV * 2.0 - 1.0, 0.0);
vec3 id = 1.0 / view_dir;
// ... complex ray-casting math ...
// 3. Sample the texture array
// Z component selects the specific room variation or wall type
ALBEDO = texture(room_textures, vec3(UV, 0.0)).rgb;
}
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)
development
Godot Expert Auditor: Aurelius. Exhaustive never-list enforcement and architectural slap-down for Godot 4.6 projects.
development
--- name:# Godot Expert Analyst: Anara ## Visionary Architect of Godot 4.6+ Excellence > "Scale is not a feature; it is a philosophy. I don't look at what your game is today; I look at whether it can survive tomorrow." — Anara You are **Anara**, the visionary architect of Godot 4.6+ excellence. You evaluate projects not for "if they work", but for "how well they scale". Your purpose is to certify professional-grade projects and provide the blueprint for architectural transcendence. Your voice
development
Expert blueprint for AI pathfinding (tower defense, RTS, stealth) using NavigationAgent2D/3D, NavigationServer, avoidance, and dynamic navigation mesh generation. Use when implementing enemy AI, NPC movement, or obstacle avoidance. Keywords NavigationAgent2D, NavigationRegion2D, pathfinding, NavigationServer, avoidance, baking, NavigationObstacle.
development
Expert patterns for Godot audio including AudioStreamPlayer variants (2D positional, 3D spatial), AudioBus mixing architecture, dynamic effects (reverb, EQ,compression), audio pooling for performance, music transitions (crossfade, bpm-sync), and procedural audio generation. Use for music systems, sound effects, spatial audio, or audio-reactive gameplay. Trigger keywords: AudioStreamPlayer, AudioStreamPlayer2D, AudioStreamPlayer3D, AudioBus, AudioServer, AudioEffect, music_crossfade, audio_pool, positional_audio, reverb, bus_volume.