第二十八章:3D光照系统

第二十八章:3D光照系统

光照是3D渲染中最重要的视觉元素之一,直接决定了场景的氛围、深度感和真实性。Godot 4提供了功能强大的光照系统,支持实时光照、全局光照、光照贴图等多种技术。本章将深入讲解Godot的3D光照系统,帮助开发者创建视觉震撼的游戏场景。

28.1 光照基础理论

28.1.1 光照模型概述

# lighting_theory.gd
# 光照理论演示

extends Node3D

## 光照类型枚举
enum LightType {
    DIRECTIONAL,  # 平行光(太阳光)
    OMNI,         # 点光源(灯泡)
    SPOT          # 聚光灯(手电筒)
}

## 光照分量
class LightComponents:
    var ambient: Color      # 环境光
    var diffuse: Color      # 漫反射
    var specular: Color     # 镜面反射
    var emission: Color     # 自发光
    
    func calculate_final_color(
        base_color: Color,
        normal: Vector3,
        light_dir: Vector3,
        view_dir: Vector3,
        shininess: float
    ) -> Color:
        # 环境光分量
        var ambient_component = ambient * base_color
        
        # 漫反射分量(Lambert)
        var ndotl = max(normal.dot(-light_dir), 0.0)
        var diffuse_component = diffuse * base_color * ndotl
        
        # 镜面反射分量(Blinn-Phong)
        var half_dir = (-light_dir + view_dir).normalized()
        var ndoth = max(normal.dot(half_dir), 0.0)
        var specular_component = specular * pow(ndoth, shininess)
        
        return ambient_component + diffuse_component + specular_component + emission

## 光照衰减
class LightAttenuation:
    var constant: float = 1.0
    var linear: float = 0.09
    var quadratic: float = 0.032
    
    func calculate(distance: float) -> float:
        return 1.0 / (constant + linear * distance + quadratic * distance * distance)
    
    ## 基于物理的衰减(平方反比)
    func calculate_physical(distance: float, range_value: float) -> float:
        var normalized_dist = distance / range_value
        var attenuation = max(1.0 - normalized_dist * normalized_dist, 0.0)
        return attenuation * attenuation

## 阴影计算基础
class ShadowCalculation:
    var bias: float = 0.005
    var normal_bias: float = 1.0
    
    func is_in_shadow(
        fragment_pos: Vector3,
        light_pos: Vector3,
        shadow_map_depth: float
    ) -> bool:
        var light_dir = (light_pos - fragment_pos).normalized()
        var distance_to_light = fragment_pos.distance_to(light_pos)
        
        # 应用偏移防止阴影痤疮
        var biased_depth = shadow_map_depth + bias
        
        return distance_to_light > biased_depth

28.1.2 光照空间与坐标

# light_space.gd
# 光照空间计算

extends Node3D

## 计算光照空间矩阵
func calculate_light_space_matrix(light: Light3D) -> Transform3D:
    match light.get_class():
        "DirectionalLight3D":
            return _calculate_directional_light_matrix(light)
        "OmniLight3D":
            return _calculate_omni_light_matrix(light)
        "SpotLight3D":
            return _calculate_spot_light_matrix(light)
    return Transform3D.IDENTITY

func _calculate_directional_light_matrix(light: DirectionalLight3D) -> Transform3D:
    # 平行光使用正交投影
    var light_transform = light.global_transform
    var light_direction = -light_transform.basis.z
    
    # 创建视图矩阵
    var up = Vector3.UP
    if abs(light_direction.dot(up)) > 0.99:
        up = Vector3.FORWARD
    
    return light_transform.looking_at(
        light_transform.origin + light_direction,
        up
    )

func _calculate_omni_light_matrix(light: OmniLight3D) -> Transform3D:
    # 点光源需要6个方向的投影(立方体贴图)
    return light.global_transform

func _calculate_spot_light_matrix(light: SpotLight3D) -> Transform3D:
    # 聚光灯使用透视投影
    var light_transform = light.global_transform
    var light_direction = -light_transform.basis.z
    
    return light_transform.looking_at(
        light_transform.origin + light_direction,
        Vector3.UP
    )

28.2 DirectionalLight3D 平行光

28.2.1 平行光基础

# directional_light_setup.gd
# 平行光配置

extends DirectionalLight3D

## 预设类型
enum SunPreset {
    SUNRISE,
    MORNING,
    NOON,
    AFTERNOON,
    SUNSET,
    NIGHT
}

## 当前预设
@export var sun_preset: SunPreset = SunPreset.NOON:
    set(value):
        sun_preset = value
        _apply_preset()

## 光照强度
@export_range(0.0, 16.0) var intensity: float = 1.0:
    set(value):
        intensity = value
        light_energy = value

## 阴影设置
@export var enable_shadows: bool = true:
    set(value):
        enable_shadows = value
        shadow_enabled = value

func _ready():
    _apply_preset()
    _configure_shadows()

func _apply_preset():
    match sun_preset:
        SunPreset.SUNRISE:
            light_color = Color(1.0, 0.6, 0.4)
            light_energy = 0.5
            rotation_degrees = Vector3(-10, -90, 0)
        
        SunPreset.MORNING:
            light_color = Color(1.0, 0.9, 0.8)
            light_energy = 0.8
            rotation_degrees = Vector3(-30, -60, 0)
        
        SunPreset.NOON:
            light_color = Color(1.0, 1.0, 0.95)
            light_energy = 1.0
            rotation_degrees = Vector3(-60, 0, 0)
        
        SunPreset.AFTERNOON:
            light_color = Color(1.0, 0.95, 0.85)
            light_energy = 0.9
            rotation_degrees = Vector3(-45, 45, 0)
        
        SunPreset.SUNSET:
            light_color = Color(1.0, 0.5, 0.3)
            light_energy = 0.6
            rotation_degrees = Vector3(-15, 90, 0)
        
        SunPreset.NIGHT:
            light_color = Color(0.4, 0.5, 0.7)
            light_energy = 0.1
            rotation_degrees = Vector3(-30, 180, 0)

func _configure_shadows():
    # 阴影基础设置
    shadow_enabled = enable_shadows
    shadow_bias = 0.03
    shadow_normal_bias = 1.0
    
    # 方向光阴影模式
    directional_shadow_mode = DirectionalLight3D.SHADOW_PARALLEL_4_SPLITS
    
    # 阴影距离
    directional_shadow_max_distance = 100.0
    
    # 阴影分割
    directional_shadow_split_1 = 0.1
    directional_shadow_split_2 = 0.2
    directional_shadow_split_3 = 0.5
    
    # 阴影淡出
    directional_shadow_fade_start = 0.8

## 动态更新太阳位置
func update_sun_position(time_of_day: float):
    # time_of_day: 0-24小时
    var normalized_time = time_of_day / 24.0
    
    # 计算太阳角度
    var sun_angle = (normalized_time - 0.25) * 360.0  # 6点日出
    rotation_degrees.x = -sin(deg_to_rad(sun_angle)) * 90.0
    rotation_degrees.y = cos(deg_to_rad(sun_angle)) * 180.0
    
    # 计算光照颜色和强度
    var sun_height = sin(deg_to_rad(sun_angle))
    
    if sun_height > 0:
        # 白天
        var day_progress = sun_height
        light_color = Color(1.0, 0.9 + day_progress * 0.1, 0.8 + day_progress * 0.15)
        light_energy = 0.3 + day_progress * 0.7
    else:
        # 夜晚
        light_color = Color(0.3, 0.4, 0.6)
        light_energy = 0.05

28.2.2 级联阴影贴图(CSM)

# cascaded_shadow_maps.gd
# 级联阴影贴图配置

extends DirectionalLight3D

## CSM配置
@export_group("Cascaded Shadow Maps")
@export var csm_enabled: bool = true
@export_range(1, 4) var cascade_count: int = 4
@export var shadow_distance: float = 100.0
@export var cascade_distribution: float = 0.5

## 各级联设置
var cascade_settings = [
    {"distance": 0.1, "bias": 0.01, "resolution": 4096},
    {"distance": 0.2, "bias": 0.02, "resolution": 2048},
    {"distance": 0.5, "bias": 0.03, "resolution": 1024},
    {"distance": 1.0, "bias": 0.04, "resolution": 512}
]

func _ready():
    configure_csm()

func configure_csm():
    if not csm_enabled:
        directional_shadow_mode = DirectionalLight3D.SHADOW_ORTHOGONAL
        return
    
    # 设置级联数量
    match cascade_count:
        1:
            directional_shadow_mode = DirectionalLight3D.SHADOW_ORTHOGONAL
        2:
            directional_shadow_mode = DirectionalLight3D.SHADOW_PARALLEL_2_SPLITS
        4:
            directional_shadow_mode = DirectionalLight3D.SHADOW_PARALLEL_4_SPLITS
    
    # 配置阴影距离
    directional_shadow_max_distance = shadow_distance
    
    # 配置级联分割点(使用对数分布)
    var splits = calculate_cascade_splits()
    directional_shadow_split_1 = splits[0]
    directional_shadow_split_2 = splits[1]
    directional_shadow_split_3 = splits[2]

## 计算级联分割点
func calculate_cascade_splits() -> Array[float]:
    var splits: Array[float] = []
    var lambda = cascade_distribution
    
    for i in range(cascade_count - 1):
        var p = float(i + 1) / float(cascade_count)
        
        # 对数分布
        var log_split = pow(shadow_distance, p)
        
        # 均匀分布
        var uniform_split = shadow_distance * p
        
        # 混合
        var split = lambda * log_split + (1.0 - lambda) * uniform_split
        splits.append(split / shadow_distance)
    
    return splits

## 根据相机位置优化CSM
func optimize_for_camera(camera: Camera3D):
    var camera_pos = camera.global_position
    var view_distance = camera.far
    
    # 动态调整阴影距离
    directional_shadow_max_distance = min(shadow_distance, view_distance * 0.8)
    
    # 更新分割点
    var splits = calculate_cascade_splits()
    directional_shadow_split_1 = splits[0]
    directional_shadow_split_2 = splits[1]
    directional_shadow_split_3 = splits[2]

28.3 OmniLight3D 点光源

28.3.1 点光源基础

# omni_light_setup.gd
# 点光源配置

extends OmniLight3D

## 光源类型预设
enum OmniPreset {
    CANDLE,
    TORCH,
    LAMP,
    BULB,
    NEON,
    FIRE
}

@export var preset: OmniPreset = OmniPreset.LAMP:
    set(value):
        preset = value
        _apply_preset()

## 闪烁效果
@export var enable_flicker: bool = false
@export var flicker_intensity: float = 0.2
@export var flicker_speed: float = 10.0

var base_energy: float

func _ready():
    _apply_preset()
    base_energy = light_energy

func _process(delta):
    if enable_flicker:
        _update_flicker(delta)

func _apply_preset():
    match preset:
        OmniPreset.CANDLE:
            light_color = Color(1.0, 0.7, 0.4)
            light_energy = 0.5
            omni_range = 3.0
            omni_attenuation = 1.5
            enable_flicker = true
            flicker_intensity = 0.3
        
        OmniPreset.TORCH:
            light_color = Color(1.0, 0.6, 0.3)
            light_energy = 1.5
            omni_range = 8.0
            omni_attenuation = 1.2
            enable_flicker = true
            flicker_intensity = 0.2
        
        OmniPreset.LAMP:
            light_color = Color(1.0, 0.95, 0.85)
            light_energy = 2.0
            omni_range = 10.0
            omni_attenuation = 1.0
            enable_flicker = false
        
        OmniPreset.BULB:
            light_color = Color(1.0, 0.98, 0.9)
            light_energy = 3.0
            omni_range = 15.0
            omni_attenuation = 0.8
            enable_flicker = false
        
        OmniPreset.NEON:
            light_color = Color(0.5, 0.8, 1.0)
            light_energy = 4.0
            omni_range = 12.0
            omni_attenuation = 0.5
            enable_flicker = true
            flicker_intensity = 0.05
            flicker_speed = 30.0
        
        OmniPreset.FIRE:
            light_color = Color(1.0, 0.5, 0.2)
            light_energy = 2.5
            omni_range = 10.0
            omni_attenuation = 1.3
            enable_flicker = true
            flicker_intensity = 0.4
            flicker_speed = 15.0

func _update_flicker(delta):
    # 使用多层噪声模拟自然闪烁
    var time = Time.get_ticks_msec() / 1000.0
    
    var noise1 = sin(time * flicker_speed) * 0.5
    var noise2 = sin(time * flicker_speed * 2.3) * 0.3
    var noise3 = sin(time * flicker_speed * 5.7) * 0.2
    
    var flicker = (noise1 + noise2 + noise3) * flicker_intensity
    light_energy = base_energy * (1.0 + flicker)
    
    # 轻微颜色变化
    var color_shift = flicker * 0.1
    light_color.r = clamp(light_color.r + color_shift, 0.0, 1.0)

## 设置阴影
func configure_shadows(enabled: bool = true, quality: int = 1):
    shadow_enabled = enabled
    
    match quality:
        0:  # 低
            shadow_bias = 0.05
            omni_shadow_mode = OmniLight3D.SHADOW_DUAL_PARABOLOID
        1:  # 中
            shadow_bias = 0.03
            omni_shadow_mode = OmniLight3D.SHADOW_DUAL_PARABOLOID
        2:  # 高
            shadow_bias = 0.02
            omni_shadow_mode = OmniLight3D.SHADOW_CUBE

28.3.2 动态点光源管理

# omni_light_manager.gd
# 点光源管理器

extends Node3D

## 最大活动光源数
@export var max_active_lights: int = 8

## 光源优先级权重
@export var distance_weight: float = 1.0
@export var intensity_weight: float = 0.5
@export var visibility_weight: float = 2.0

## 所有管理的光源
var managed_lights: Array[OmniLight3D] = []
var active_lights: Array[OmniLight3D] = []
var camera: Camera3D

func _ready():
    camera = get_viewport().get_camera_3d()
    _collect_lights()

func _collect_lights():
    managed_lights.clear()
    for child in get_children():
        if child is OmniLight3D:
            managed_lights.append(child)

func _process(_delta):
    if camera:
        _update_active_lights()

func _update_active_lights():
    # 计算每个光源的优先级
    var light_priorities: Array[Dictionary] = []
    
    for light in managed_lights:
        var priority = _calculate_priority(light)
        light_priorities.append({
            "light": light,
            "priority": priority
        })
    
    # 按优先级排序
    light_priorities.sort_custom(func(a, b): return a.priority > b.priority)
    
    # 激活前N个光源
    active_lights.clear()
    for i in range(min(max_active_lights, light_priorities.size())):
        var light = light_priorities[i].light
        light.visible = true
        active_lights.append(light)
    
    # 禁用其余光源
    for i in range(max_active_lights, light_priorities.size()):
        light_priorities[i].light.visible = false

func _calculate_priority(light: OmniLight3D) -> float:
    var priority = 0.0
    
    # 距离因素(越近优先级越高)
    var distance = light.global_position.distance_to(camera.global_position)
    var max_distance = light.omni_range * 2.0
    var distance_factor = 1.0 - clamp(distance / max_distance, 0.0, 1.0)
    priority += distance_factor * distance_weight
    
    # 强度因素
    var intensity_factor = light.light_energy / 10.0
    priority += intensity_factor * intensity_weight
    
    # 可见性因素(在相机视锥内)
    if _is_in_camera_frustum(light.global_position):
        priority += visibility_weight
    
    return priority

func _is_in_camera_frustum(position: Vector3) -> bool:
    var screen_pos = camera.unproject_position(position)
    var viewport_rect = get_viewport().get_visible_rect()
    
    # 检查是否在屏幕范围内
    if not viewport_rect.has_point(screen_pos):
        return false
    
    # 检查是否在相机前方
    var to_light = position - camera.global_position
    var forward = -camera.global_transform.basis.z
    
    return to_light.dot(forward) > 0

## 添加临时光源(如爆炸闪光)
func add_temporary_light(
    position: Vector3,
    color: Color,
    energy: float,
    duration: float
) -> OmniLight3D:
    var light = OmniLight3D.new()
    light.global_position = position
    light.light_color = color
    light.light_energy = energy
    light.omni_range = energy * 5.0
    
    add_child(light)
    managed_lights.append(light)
    
    # 淡出并移除
    var tween = create_tween()
    tween.tween_property(light, "light_energy", 0.0, duration)
    tween.tween_callback(func():
        managed_lights.erase(light)
        light.queue_free()
    )
    
    return light

28.4 SpotLight3D 聚光灯

28.4.1 聚光灯配置

# spot_light_setup.gd
# 聚光灯配置

extends SpotLight3D

## 聚光灯预设
enum SpotPreset {
    FLASHLIGHT,
    STAGE_LIGHT,
    CAR_HEADLIGHT,
    STREET_LAMP,
    SEARCHLIGHT
}

@export var preset: SpotPreset = SpotPreset.FLASHLIGHT:
    set(value):
        preset = value
        _apply_preset()

## Cookie纹理
@export var cookie_texture: Texture2D:
    set(value):
        cookie_texture = value
        light_projector = value

func _ready():
    _apply_preset()

func _apply_preset():
    match preset:
        SpotPreset.FLASHLIGHT:
            light_color = Color(1.0, 0.98, 0.95)
            light_energy = 3.0
            spot_range = 20.0
            spot_angle = 25.0
            spot_angle_attenuation = 0.8
            spot_attenuation = 1.0
        
        SpotPreset.STAGE_LIGHT:
            light_color = Color(1.0, 1.0, 1.0)
            light_energy = 8.0
            spot_range = 50.0
            spot_angle = 15.0
            spot_angle_attenuation = 0.5
            spot_attenuation = 0.5
        
        SpotPreset.CAR_HEADLIGHT:
            light_color = Color(1.0, 1.0, 0.9)
            light_energy = 5.0
            spot_range = 40.0
            spot_angle = 35.0
            spot_angle_attenuation = 0.9
            spot_attenuation = 0.8
        
        SpotPreset.STREET_LAMP:
            light_color = Color(1.0, 0.9, 0.7)
            light_energy = 4.0
            spot_range = 15.0
            spot_angle = 60.0
            spot_angle_attenuation = 0.7
            spot_attenuation = 1.2
        
        SpotPreset.SEARCHLIGHT:
            light_color = Color(1.0, 1.0, 1.0)
            light_energy = 15.0
            spot_range = 100.0
            spot_angle = 10.0
            spot_angle_attenuation = 0.3
            spot_attenuation = 0.3

## 聚光灯跟踪目标
func track_target(target: Node3D, smooth: float = 5.0, delta: float = 0.0):
    if not target:
        return
    
    var direction = target.global_position - global_position
    var target_transform = global_transform.looking_at(
        global_position + direction,
        Vector3.UP
    )
    
    if delta > 0:
        global_transform = global_transform.interpolate_with(
            target_transform,
            smooth * delta
        )
    else:
        global_transform = target_transform

## 光束扫描动画
func sweep_animation(
    start_angle: float,
    end_angle: float,
    duration: float,
    axis: Vector3 = Vector3.UP
):
    var tween = create_tween()
    tween.set_loops()
    
    var start_rotation = global_rotation
    var end_rotation = start_rotation + axis * deg_to_rad(end_angle - start_angle)
    
    tween.tween_property(
        self, "global_rotation",
        end_rotation, duration / 2
    ).set_ease(Tween.EASE_IN_OUT)
    
    tween.tween_property(
        self, "global_rotation",
        start_rotation, duration / 2
    ).set_ease(Tween.EASE_IN_OUT)

28.4.2 体积光效果

# volumetric_spotlight.gd
# 体积光聚光灯

extends SpotLight3D

## 体积光设置
@export var volumetric_enabled: bool = true
@export var fog_density: float = 0.05
@export var fog_color: Color = Color(1.0, 1.0, 1.0, 0.3)

## 体积光网格
var fog_mesh: MeshInstance3D
var fog_material: ShaderMaterial

func _ready():
    if volumetric_enabled:
        _create_volumetric_cone()

func _create_volumetric_cone():
    # 创建锥形网格
    fog_mesh = MeshInstance3D.new()
    
    var cone = CylinderMesh.new()
    cone.top_radius = 0.0
    cone.bottom_radius = tan(deg_to_rad(spot_angle)) * spot_range
    cone.height = spot_range
    cone.radial_segments = 32
    cone.rings = 1
    
    fog_mesh.mesh = cone
    fog_mesh.position = Vector3(0, 0, -spot_range / 2)
    fog_mesh.rotation_degrees.x = 90
    
    # 创建体积光着色器材质
    fog_material = ShaderMaterial.new()
    fog_material.shader = _create_volumetric_shader()
    fog_material.set_shader_parameter("fog_color", fog_color)
    fog_material.set_shader_parameter("fog_density", fog_density)
    
    fog_mesh.material_override = fog_material
    fog_mesh.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
    
    add_child(fog_mesh)

func _create_volumetric_shader() -> Shader:
    var shader = Shader.new()
    shader.code = """
shader_type spatial;
render_mode blend_add, depth_draw_never, unshaded, cull_disabled;

uniform vec4 fog_color : source_color = vec4(1.0, 1.0, 1.0, 0.3);
uniform float fog_density : hint_range(0.0, 1.0) = 0.05;

varying vec3 world_pos;
varying vec3 local_pos;

void vertex() {
    world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
    local_pos = VERTEX;
}

void fragment() {
    // 计算到光源的距离(沿锥体轴)
    float cone_height = length(local_pos);
    float fade = 1.0 - (cone_height / 1.0);  // 假设标准化高度
    
    // 边缘淡出
    float edge_fade = 1.0 - abs(UV.x - 0.5) * 2.0;
    edge_fade = pow(edge_fade, 2.0);
    
    // 深度淡出
    float depth_fade = 1.0 - FRAGCOORD.z;
    
    // 组合
    float alpha = fog_density * fade * edge_fade * depth_fade;
    
    ALBEDO = fog_color.rgb;
    ALPHA = clamp(alpha, 0.0, fog_color.a);
}
"""
    return shader

func _process(_delta):
    if fog_material:
        # 同步参数
        fog_material.set_shader_parameter("fog_density", fog_density * light_energy / 10.0)

28.5 环境光与天空

28.5.1 WorldEnvironment配置

# world_environment_setup.gd
# 世界环境配置

extends WorldEnvironment

## 环境预设
enum EnvironmentPreset {
    OUTDOOR_DAY,
    OUTDOOR_SUNSET,
    OUTDOOR_NIGHT,
    INDOOR_BRIGHT,
    INDOOR_DARK,
    UNDERWATER,
    FOGGY
}

@export var preset: EnvironmentPreset = EnvironmentPreset.OUTDOOR_DAY:
    set(value):
        preset = value
        _apply_preset()

func _ready():
    if not environment:
        environment = Environment.new()
    _apply_preset()

func _apply_preset():
    match preset:
        EnvironmentPreset.OUTDOOR_DAY:
            _setup_outdoor_day()
        EnvironmentPreset.OUTDOOR_SUNSET:
            _setup_outdoor_sunset()
        EnvironmentPreset.OUTDOOR_NIGHT:
            _setup_outdoor_night()
        EnvironmentPreset.INDOOR_BRIGHT:
            _setup_indoor_bright()
        EnvironmentPreset.INDOOR_DARK:
            _setup_indoor_dark()
        EnvironmentPreset.UNDERWATER:
            _setup_underwater()
        EnvironmentPreset.FOGGY:
            _setup_foggy()

func _setup_outdoor_day():
    # 背景
    environment.background_mode = Environment.BG_SKY
    environment.sky = _create_procedural_sky(
        Color(0.4, 0.6, 0.9),  # 天顶色
        Color(0.7, 0.8, 0.95), # 地平线色
        Color(0.8, 0.9, 1.0)   # 地面色
    )
    
    # 环境光
    environment.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
    environment.ambient_light_energy = 0.5
    
    # 色调映射
    environment.tonemap_mode = Environment.TONE_MAPPER_FILMIC
    environment.tonemap_exposure = 1.0
    
    # 泛光
    environment.glow_enabled = true
    environment.glow_intensity = 0.3
    environment.glow_bloom = 0.1
    
    # SSAO
    environment.ssao_enabled = true
    environment.ssao_radius = 1.0
    environment.ssao_intensity = 1.0

func _setup_outdoor_sunset():
    environment.background_mode = Environment.BG_SKY
    environment.sky = _create_procedural_sky(
        Color(0.2, 0.1, 0.3),  # 天顶色
        Color(1.0, 0.5, 0.3),  # 地平线色
        Color(0.4, 0.2, 0.1)   # 地面色
    )
    
    environment.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
    environment.ambient_light_energy = 0.3
    environment.ambient_light_sky_contribution = 0.8
    
    environment.tonemap_mode = Environment.TONE_MAPPER_FILMIC
    environment.tonemap_exposure = 1.2
    
    environment.glow_enabled = true
    environment.glow_intensity = 0.5
    environment.glow_bloom = 0.3

func _setup_outdoor_night():
    environment.background_mode = Environment.BG_SKY
    environment.sky = _create_procedural_sky(
        Color(0.02, 0.02, 0.05),
        Color(0.05, 0.05, 0.1),
        Color(0.02, 0.02, 0.03)
    )
    
    environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
    environment.ambient_light_color = Color(0.1, 0.1, 0.2)
    environment.ambient_light_energy = 0.1
    
    environment.tonemap_mode = Environment.TONE_MAPPER_ACES
    environment.tonemap_exposure = 2.0
    
    environment.glow_enabled = true
    environment.glow_intensity = 0.8
    environment.glow_bloom = 0.5

func _setup_indoor_bright():
    environment.background_mode = Environment.BG_COLOR
    environment.background_color = Color(0.9, 0.9, 0.9)
    
    environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
    environment.ambient_light_color = Color(1.0, 0.98, 0.95)
    environment.ambient_light_energy = 0.4
    
    environment.tonemap_mode = Environment.TONE_MAPPER_LINEAR
    environment.ssao_enabled = true
    environment.ssao_intensity = 0.5

func _setup_indoor_dark():
    environment.background_mode = Environment.BG_COLOR
    environment.background_color = Color(0.05, 0.05, 0.05)
    
    environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
    environment.ambient_light_color = Color(0.15, 0.15, 0.2)
    environment.ambient_light_energy = 0.1
    
    environment.tonemap_mode = Environment.TONE_MAPPER_ACES
    environment.tonemap_exposure = 1.5
    
    environment.ssao_enabled = true
    environment.ssao_intensity = 2.0
    
    environment.glow_enabled = true
    environment.glow_intensity = 1.0

func _setup_underwater():
    environment.background_mode = Environment.BG_COLOR
    environment.background_color = Color(0.0, 0.2, 0.4)
    
    environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
    environment.ambient_light_color = Color(0.2, 0.5, 0.6)
    environment.ambient_light_energy = 0.3
    
    # 雾气
    environment.fog_enabled = true
    environment.fog_light_color = Color(0.1, 0.4, 0.5)
    environment.fog_density = 0.02
    
    environment.tonemap_mode = Environment.TONE_MAPPER_FILMIC

func _setup_foggy():
    environment.background_mode = Environment.BG_COLOR
    environment.background_color = Color(0.7, 0.7, 0.75)
    
    environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
    environment.ambient_light_color = Color(0.8, 0.8, 0.85)
    environment.ambient_light_energy = 0.6
    
    environment.fog_enabled = true
    environment.fog_light_color = Color(0.8, 0.8, 0.85)
    environment.fog_density = 0.05
    environment.fog_aerial_perspective = 0.5

func _create_procedural_sky(
    sky_top: Color,
    sky_horizon: Color,
    ground: Color
) -> Sky:
    var sky = Sky.new()
    var sky_material = ProceduralSkyMaterial.new()
    
    sky_material.sky_top_color = sky_top
    sky_material.sky_horizon_color = sky_horizon
    sky_material.ground_bottom_color = ground
    sky_material.ground_horizon_color = sky_horizon
    
    sky_material.sun_angle_max = 30.0
    sky_material.sun_curve = 0.15
    
    sky.sky_material = sky_material
    
    return sky

28.5.2 HDRI天空盒

# hdri_sky_manager.gd
# HDRI天空管理器

extends Node

## HDRI资源路径
@export var hdri_textures: Array[Texture2D] = []
@export var current_index: int = 0

var world_environment: WorldEnvironment
var transition_tween: Tween

func _ready():
    world_environment = get_node_or_null("../WorldEnvironment")
    if world_environment and hdri_textures.size() > 0:
        _apply_hdri(hdri_textures[current_index])

func _apply_hdri(hdri: Texture2D):
    if not world_environment or not world_environment.environment:
        return
    
    var env = world_environment.environment
    
    # 创建全景天空
    var sky = Sky.new()
    var sky_material = PanoramaSkyMaterial.new()
    sky_material.panorama = hdri
    sky.sky_material = sky_material
    
    # 设置天空属性
    sky.radiance_size = Sky.RADIANCE_SIZE_256
    
    env.background_mode = Environment.BG_SKY
    env.sky = sky
    
    # 设置反射探针使用天空
    env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
    env.reflected_light_source = Environment.REFLECTED_SOURCE_SKY

## 切换到下一个HDRI
func next_hdri():
    current_index = (current_index + 1) % hdri_textures.size()
    _transition_to_hdri(hdri_textures[current_index])

## 平滑过渡到新HDRI
func _transition_to_hdri(new_hdri: Texture2D, duration: float = 1.0):
    if transition_tween:
        transition_tween.kill()
    
    var env = world_environment.environment
    var old_energy = env.ambient_light_energy
    
    transition_tween = create_tween()
    
    # 淡出
    transition_tween.tween_property(
        env, "ambient_light_energy",
        0.0, duration / 2
    )
    
    # 切换HDRI
    transition_tween.tween_callback(func():
        _apply_hdri(new_hdri)
    )
    
    # 淡入
    transition_tween.tween_property(
        env, "ambient_light_energy",
        old_energy, duration / 2
    )

## 旋转天空盒
func rotate_sky(angle: float):
    if not world_environment or not world_environment.environment:
        return
    
    var sky = world_environment.environment.sky
    if sky and sky.sky_material is PanoramaSkyMaterial:
        # Godot 4中需要通过着色器或其他方式实现旋转
        pass

28.6 全局光照(GI)

28.6.1 SDFGI配置

# sdfgi_setup.gd
# SDFGI全局光照配置

extends WorldEnvironment

## SDFGI设置
@export_group("SDFGI Settings")
@export var sdfgi_enabled: bool = true
@export var sdfgi_cascades: int = 4
@export var sdfgi_min_cell_size: float = 0.2
@export var sdfgi_bounce_feedback: float = 0.5
@export var sdfgi_energy: float = 1.0

## 性能预设
enum SDFGIQuality {
    LOW,
    MEDIUM,
    HIGH,
    ULTRA
}

@export var quality: SDFGIQuality = SDFGIQuality.MEDIUM:
    set(value):
        quality = value
        _apply_quality_preset()

func _ready():
    _configure_sdfgi()

func _configure_sdfgi():
    if not environment:
        environment = Environment.new()
    
    environment.sdfgi_enabled = sdfgi_enabled
    environment.sdfgi_cascades = sdfgi_cascades
    environment.sdfgi_min_cell_size = sdfgi_min_cell_size
    environment.sdfgi_bounce_feedback = sdfgi_bounce_feedback
    environment.sdfgi_energy = sdfgi_energy
    
    _apply_quality_preset()

func _apply_quality_preset():
    match quality:
        SDFGIQuality.LOW:
            environment.sdfgi_cascades = 2
            environment.sdfgi_min_cell_size = 0.4
            environment.sdfgi_y_scale = Environment.SDFGI_Y_SCALE_50_PERCENT
            environment.sdfgi_probe_bias = 1.1
        
        SDFGIQuality.MEDIUM:
            environment.sdfgi_cascades = 4
            environment.sdfgi_min_cell_size = 0.2
            environment.sdfgi_y_scale = Environment.SDFGI_Y_SCALE_75_PERCENT
            environment.sdfgi_probe_bias = 1.1
        
        SDFGIQuality.HIGH:
            environment.sdfgi_cascades = 6
            environment.sdfgi_min_cell_size = 0.1
            environment.sdfgi_y_scale = Environment.SDFGI_Y_SCALE_100_PERCENT
            environment.sdfgi_probe_bias = 1.05
        
        SDFGIQuality.ULTRA:
            environment.sdfgi_cascades = 8
            environment.sdfgi_min_cell_size = 0.05
            environment.sdfgi_y_scale = Environment.SDFGI_Y_SCALE_100_PERCENT
            environment.sdfgi_probe_bias = 1.0

## 动态调整SDFGI
func adjust_for_performance(fps: float, target_fps: float = 60.0):
    if fps < target_fps * 0.8:
        # 性能不足,降低质量
        if quality > SDFGIQuality.LOW:
            quality = quality - 1
            print("SDFGI quality reduced to: ", SDFGIQuality.keys()[quality])
    elif fps > target_fps * 1.2:
        # 性能充足,提升质量
        if quality < SDFGIQuality.ULTRA:
            quality = quality + 1
            print("SDFGI quality increased to: ", SDFGIQuality.keys()[quality])

28.6.2 LightmapGI烘焙

# lightmap_gi_setup.gd
# 光照贴图GI配置

extends LightmapGI

## 烘焙设置
@export_group("Bake Settings")
@export var bake_quality: BakeQuality = BakeQuality.BAKE_QUALITY_MEDIUM
@export var bounces: int = 3
@export var directional: bool = true
@export var interior: bool = false

## 光照贴图纹理设置
@export_group("Texture Settings")
@export var max_texture_size: int = 2048
@export var texel_scale: float = 1.0

func _ready():
    _configure_lightmap()

func _configure_lightmap():
    # 基础设置
    quality = bake_quality
    self.bounces = bounces
    self.directional = directional
    self.interior = interior
    
    # 纹理设置
    self.max_texture_size = max_texture_size
    self.texel_scale = texel_scale

## 烘焙光照贴图(仅编辑器中使用)
func bake_lightmaps():
    if not Engine.is_editor_hint():
        push_warning("Lightmap baking only available in editor")
        return
    
    print("Starting lightmap bake...")
    # 实际烘焙通过编辑器UI触发
    # 这里只是配置参考

## 设置静态网格为光照贴图接收者
static func setup_mesh_for_lightmap(mesh_instance: MeshInstance3D):
    # 启用全局光照
    mesh_instance.gi_mode = GeometryInstance3D.GI_MODE_STATIC
    
    # 确保有UV2用于光照贴图
    if mesh_instance.mesh:
        var mesh = mesh_instance.mesh
        if mesh is ArrayMesh:
            # 检查是否有UV2
            var surface_count = mesh.get_surface_count()
            for i in range(surface_count):
                var arrays = mesh.surface_get_arrays(i)
                if arrays[Mesh.ARRAY_TEX_UV2] == null:
                    print("Warning: Mesh missing UV2 for lightmapping")
                    break

## 光照贴图质量预设
func apply_quality_preset(preset: String):
    match preset:
        "preview":
            quality = BakeQuality.BAKE_QUALITY_LOW
            self.bounces = 1
            max_texture_size = 512
            texel_scale = 0.5
        
        "production":
            quality = BakeQuality.BAKE_QUALITY_HIGH
            self.bounces = 4
            max_texture_size = 4096
            texel_scale = 2.0
        
        "mobile":
            quality = BakeQuality.BAKE_QUALITY_MEDIUM
            self.bounces = 2
            max_texture_size = 1024
            texel_scale = 0.8

28.6.3 VoxelGI实时全局光照

# voxel_gi_setup.gd
# VoxelGI配置

extends VoxelGI

## 体素GI设置
@export_group("Voxel GI Settings")
@export var subdiv: Subdiv = Subdiv.SUBDIV_128
@export var gi_extents: Vector3 = Vector3(20, 10, 20)
@export var dynamic_range: float = 4.0
@export var propagation: float = 0.7
@export var energy: float = 1.0

func _ready():
    _configure_voxel_gi()

func _configure_voxel_gi():
    # 基础配置
    self.subdiv = subdiv
    extents = gi_extents
    
    # GI数据配置(如果已烘焙)
    if data:
        data.dynamic_range = dynamic_range
        data.propagation = propagation
        data.energy = energy

## 动态更新GI区域
func update_extents_for_player(player_position: Vector3, radius: float = 20.0):
    global_position = player_position
    extents = Vector3(radius, radius * 0.5, radius)

## VoxelGI性能优化
func optimize_for_target(target_fps: float):
    var current_fps = Engine.get_frames_per_second()
    
    if current_fps < target_fps * 0.7:
        # 降低细分
        if subdiv > Subdiv.SUBDIV_64:
            subdiv = subdiv - 1
            _configure_voxel_gi()
    elif current_fps > target_fps * 1.3:
        # 提升细分
        if subdiv < Subdiv.SUBDIV_512:
            subdiv = subdiv + 1
            _configure_voxel_gi()

28.7 反射探针

28.7.1 ReflectionProbe配置

# reflection_probe_setup.gd
# 反射探针配置

extends ReflectionProbe

## 反射探针设置
@export_group("Probe Settings")
@export var probe_size: Vector3 = Vector3(10, 5, 10)
@export var update_mode_setting: UpdateMode = UpdateMode.UPDATE_ONCE
@export var box_projection_enabled: bool = true
@export var reflection_intensity: float = 1.0

## 自动配置
@export var auto_fit_to_room: bool = false

func _ready():
    _configure_probe()
    
    if auto_fit_to_room:
        _fit_to_room()

func _configure_probe():
    size = probe_size
    update_mode = update_mode_setting
    box_projection = box_projection_enabled
    intensity = reflection_intensity
    
    # 内部配置
    interior = true
    enable_shadows = true
    
    # 剔除遮罩
    cull_mask = 0xFFFFFFFF  # 渲染所有层

## 自动适应房间大小
func _fit_to_room():
    # 向六个方向发射射线检测墙壁
    var space_state = get_world_3d().direct_space_state
    var extents = Vector3.ZERO
    
    var directions = [
        Vector3.RIGHT, Vector3.LEFT,
        Vector3.UP, Vector3.DOWN,
        Vector3.FORWARD, Vector3.BACK
    ]
    
    for i in range(6):
        var dir = directions[i]
        var query = PhysicsRayQueryParameters3D.create(
            global_position,
            global_position + dir * 100.0
        )
        query.collision_mask = 1  # 静态几何体层
        
        var result = space_state.intersect_ray(query)
        if result:
            var distance = global_position.distance_to(result.position)
            match i:
                0, 1: extents.x = max(extents.x, distance)
                2, 3: extents.y = max(extents.y, distance)
                4, 5: extents.z = max(extents.z, distance)
    
    # 应用检测到的尺寸
    size = extents * 2.0
    print("Auto-fitted probe size: ", size)

## 手动触发更新
func refresh_probe():
    if update_mode == UpdateMode.UPDATE_ONCE:
        # 临时切换到总是更新,然后切回
        update_mode = UpdateMode.UPDATE_ALWAYS
        await get_tree().process_frame
        update_mode = UpdateMode.UPDATE_ONCE

28.7.2 反射探针管理器

# reflection_probe_manager.gd
# 反射探针管理系统

extends Node3D

## 最大活动探针数
@export var max_active_probes: int = 4
@export var update_interval: float = 0.5

## 探针集合
var all_probes: Array[ReflectionProbe] = []
var active_probes: Array[ReflectionProbe] = []
var camera: Camera3D

var update_timer: float = 0.0
var probe_update_queue: Array[ReflectionProbe] = []

func _ready():
    camera = get_viewport().get_camera_3d()
    _collect_probes()

func _collect_probes():
    all_probes.clear()
    _find_probes_recursive(self)

func _find_probes_recursive(node: Node):
    if node is ReflectionProbe:
        all_probes.append(node)
    for child in node.get_children():
        _find_probes_recursive(child)

func _process(delta):
    update_timer += delta
    
    if update_timer >= update_interval:
        update_timer = 0.0
        _update_active_probes()
        _process_update_queue()

func _update_active_probes():
    if not camera:
        return
    
    # 计算每个探针的优先级
    var probe_priorities: Array[Dictionary] = []
    
    for probe in all_probes:
        var priority = _calculate_probe_priority(probe)
        probe_priorities.append({
            "probe": probe,
            "priority": priority
        })
    
    # 排序
    probe_priorities.sort_custom(func(a, b): return a.priority > b.priority)
    
    # 激活前N个探针
    active_probes.clear()
    for i in range(min(max_active_probes, probe_priorities.size())):
        var probe = probe_priorities[i].probe
        probe.visible = true
        active_probes.append(probe)
        
        # 加入更新队列
        if not probe_update_queue.has(probe):
            probe_update_queue.append(probe)
    
    # 禁用其余探针
    for i in range(max_active_probes, probe_priorities.size()):
        probe_priorities[i].probe.visible = false

func _calculate_probe_priority(probe: ReflectionProbe) -> float:
    var priority = 0.0
    
    # 距离因素
    var distance = probe.global_position.distance_to(camera.global_position)
    var max_dist = probe.size.length()
    priority += (1.0 - clamp(distance / max_dist, 0.0, 1.0)) * 2.0
    
    # 是否在探针范围内
    var local_pos = probe.to_local(camera.global_position)
    var half_size = probe.size / 2.0
    if abs(local_pos.x) < half_size.x and \
       abs(local_pos.y) < half_size.y and \
       abs(local_pos.z) < half_size.z:
        priority += 5.0
    
    # 强度因素
    priority += probe.intensity * 0.5
    
    return priority

func _process_update_queue():
    if probe_update_queue.is_empty():
        return
    
    # 每帧更新一个探针
    var probe = probe_update_queue.pop_front()
    if probe and probe.update_mode == ReflectionProbe.UPDATE_ONCE:
        # 触发更新
        probe.update_mode = ReflectionProbe.UPDATE_ALWAYS
        await get_tree().process_frame
        probe.update_mode = ReflectionProbe.UPDATE_ONCE

## 创建新探针
func create_probe_at(
    position: Vector3,
    probe_size: Vector3 = Vector3(10, 5, 10)
) -> ReflectionProbe:
    var probe = ReflectionProbe.new()
    probe.global_position = position
    probe.size = probe_size
    probe.box_projection = true
    probe.update_mode = ReflectionProbe.UPDATE_ONCE
    
    add_child(probe)
    all_probes.append(probe)
    
    return probe

28.8 屏幕空间效果

28.8.1 SSAO配置

# ssao_setup.gd
# 屏幕空间环境光遮蔽配置

extends Node

## SSAO设置
@export_group("SSAO Settings")
@export var ssao_enabled: bool = true
@export var ssao_radius: float = 1.0
@export var ssao_intensity: float = 1.5
@export var ssao_power: float = 1.5
@export var ssao_detail: float = 0.5
@export var ssao_horizon: float = 0.06
@export var ssao_sharpness: float = 0.98
@export var ssao_light_affect: float = 0.0

var world_environment: WorldEnvironment

func _ready():
    world_environment = get_node_or_null("../WorldEnvironment")
    if world_environment:
        _configure_ssao()

func _configure_ssao():
    var env = world_environment.environment
    if not env:
        return
    
    env.ssao_enabled = ssao_enabled
    env.ssao_radius = ssao_radius
    env.ssao_intensity = ssao_intensity
    env.ssao_power = ssao_power
    env.ssao_detail = ssao_detail
    env.ssao_horizon = ssao_horizon
    env.ssao_sharpness = ssao_sharpness
    env.ssao_light_affect = ssao_light_affect

## SSAO质量预设
func apply_preset(preset: String):
    match preset:
        "low":
            ssao_radius = 0.5
            ssao_intensity = 1.0
            ssao_detail = 0.3
        "medium":
            ssao_radius = 1.0
            ssao_intensity = 1.5
            ssao_detail = 0.5
        "high":
            ssao_radius = 1.5
            ssao_intensity = 2.0
            ssao_detail = 0.8
        "ultra":
            ssao_radius = 2.0
            ssao_intensity = 2.5
            ssao_detail = 1.0
    
    _configure_ssao()

28.8.2 SSR与SSIL

# screen_space_effects.gd
# 屏幕空间反射与间接光照

extends Node

## SSR设置
@export_group("Screen Space Reflections")
@export var ssr_enabled: bool = true
@export var ssr_max_steps: int = 64
@export var ssr_fade_in: float = 0.15
@export var ssr_fade_out: float = 2.0
@export var ssr_depth_tolerance: float = 0.2

## SSIL设置
@export_group("Screen Space Indirect Lighting")
@export var ssil_enabled: bool = true
@export var ssil_radius: float = 5.0
@export var ssil_intensity: float = 1.0
@export var ssil_sharpness: float = 0.98
@export var ssil_normal_rejection: float = 1.0

var world_environment: WorldEnvironment

func _ready():
    world_environment = get_node_or_null("../WorldEnvironment")
    if world_environment:
        _configure_effects()

func _configure_effects():
    var env = world_environment.environment
    if not env:
        return
    
    # SSR配置
    env.ssr_enabled = ssr_enabled
    env.ssr_max_steps = ssr_max_steps
    env.ssr_fade_in = ssr_fade_in
    env.ssr_fade_out = ssr_fade_out
    env.ssr_depth_tolerance = ssr_depth_tolerance
    
    # SSIL配置
    env.ssil_enabled = ssil_enabled
    env.ssil_radius = ssil_radius
    env.ssil_intensity = ssil_intensity
    env.ssil_sharpness = ssil_sharpness
    env.ssil_normal_rejection = ssil_normal_rejection

## 动态质量调整
func adjust_quality_for_performance(fps: float, target_fps: float = 60.0):
    var env = world_environment.environment
    if not env:
        return
    
    if fps < target_fps * 0.7:
        # 禁用高开销效果
        env.ssr_enabled = false
        env.ssil_enabled = false
    elif fps < target_fps * 0.9:
        # 降低质量
        env.ssr_enabled = true
        env.ssr_max_steps = 32
        env.ssil_enabled = false
    else:
        # 恢复完整质量
        _configure_effects()

28.9 光照优化

28.9.1 光照LOD系统

# light_lod_system.gd
# 光照LOD系统

extends Node3D

## LOD距离
@export var lod_distances: Array[float] = [10.0, 30.0, 60.0, 100.0]

## LOD设置
class LightLODSettings:
    var shadow_enabled: bool
    var energy_multiplier: float
    var attenuation_multiplier: float

var camera: Camera3D
var managed_lights: Array[Dictionary] = []

func _ready():
    camera = get_viewport().get_camera_3d()
    _collect_lights()

func _collect_lights():
    for child in get_children():
        if child is Light3D:
            var original_settings = {
                "light": child,
                "original_energy": child.light_energy,
                "original_shadow": child.shadow_enabled
            }
            managed_lights.append(original_settings)

func _process(_delta):
    if not camera:
        return
    
    for light_data in managed_lights:
        var light = light_data.light as Light3D
        var distance = light.global_position.distance_to(camera.global_position)
        
        _apply_lod(light, light_data, distance)

func _apply_lod(light: Light3D, original_data: Dictionary, distance: float):
    var lod_level = _get_lod_level(distance)
    
    match lod_level:
        0:  # 最高质量
            light.light_energy = original_data.original_energy
            light.shadow_enabled = original_data.original_shadow
        1:  # 高质量
            light.light_energy = original_data.original_energy * 0.9
            light.shadow_enabled = original_data.original_shadow
        2:  # 中等质量
            light.light_energy = original_data.original_energy * 0.7
            light.shadow_enabled = false
        3:  # 低质量
            light.light_energy = original_data.original_energy * 0.5
            light.shadow_enabled = false
        4:  # 禁用
            light.visible = false
            return
    
    light.visible = true

func _get_lod_level(distance: float) -> int:
    for i in range(lod_distances.size()):
        if distance < lod_distances[i]:
            return i
    return lod_distances.size()

28.9.2 光照剔除系统

# light_culling_system.gd
# 光照剔除系统

extends Node3D

## 剔除设置
@export var culling_distance: float = 100.0
@export var frustum_culling: bool = true
@export var occlusion_culling: bool = false

var camera: Camera3D
var all_lights: Array[Light3D] = []
var visible_lights: Array[Light3D] = []

func _ready():
    camera = get_viewport().get_camera_3d()
    _collect_all_lights()

func _collect_all_lights():
    all_lights.clear()
    _find_lights_recursive(get_tree().root)

func _find_lights_recursive(node: Node):
    if node is Light3D:
        all_lights.append(node)
    for child in node.get_children():
        _find_lights_recursive(child)

func _process(_delta):
    if not camera:
        return
    
    visible_lights.clear()
    
    for light in all_lights:
        var should_render = _should_render_light(light)
        light.visible = should_render
        if should_render:
            visible_lights.append(light)

func _should_render_light(light: Light3D) -> bool:
    # 距离剔除
    var distance = light.global_position.distance_to(camera.global_position)
    
    # 获取光照范围
    var light_range = _get_light_range(light)
    if distance > light_range + culling_distance:
        return false
    
    # 视锥剔除
    if frustum_culling and not _is_in_frustum(light):
        return false
    
    # 遮挡剔除
    if occlusion_culling and _is_occluded(light):
        return false
    
    return true

func _get_light_range(light: Light3D) -> float:
    if light is OmniLight3D:
        return light.omni_range
    elif light is SpotLight3D:
        return light.spot_range
    elif light is DirectionalLight3D:
        return INF  # 方向光不距离剔除
    return 50.0

func _is_in_frustum(light: Light3D) -> bool:
    # 简化的视锥检测
    var screen_pos = camera.unproject_position(light.global_position)
    var viewport_rect = get_viewport().get_visible_rect()
    
    # 扩展边界以包含光照影响范围
    var expanded_rect = viewport_rect.grow(200)
    
    # 检查是否在相机前方
    var to_light = light.global_position - camera.global_position
    var forward = -camera.global_transform.basis.z
    if to_light.dot(forward) < 0:
        return false
    
    return expanded_rect.has_point(screen_pos)

func _is_occluded(light: Light3D) -> bool:
    # 简单的射线遮挡检测
    var space_state = get_world_3d().direct_space_state
    var query = PhysicsRayQueryParameters3D.create(
        camera.global_position,
        light.global_position
    )
    query.collision_mask = 1  # 静态几何体
    
    var result = space_state.intersect_ray(query)
    return result.size() > 0

## 获取统计信息
func get_stats() -> Dictionary:
    return {
        "total_lights": all_lights.size(),
        "visible_lights": visible_lights.size(),
        "culled_lights": all_lights.size() - visible_lights.size()
    }

28.9.3 动态光照质量调整

# dynamic_light_quality.gd
# 动态光照质量调整

extends Node

## 目标帧率
@export var target_fps: float = 60.0
@export var quality_adjustment_speed: float = 0.1

## 质量级别
enum QualityLevel { ULTRA, HIGH, MEDIUM, LOW, VERY_LOW }
var current_quality: QualityLevel = QualityLevel.HIGH

## FPS采样
var fps_samples: Array[float] = []
var sample_count: int = 30

var world_environment: WorldEnvironment

func _ready():
    world_environment = get_node_or_null("/root/Main/WorldEnvironment")

func _process(delta):
    # 采样FPS
    fps_samples.append(1.0 / delta)
    if fps_samples.size() > sample_count:
        fps_samples.pop_front()
    
    # 定期调整质量
    if Engine.get_frames_drawn() % 60 == 0:
        _adjust_quality()

func _adjust_quality():
    var avg_fps = _get_average_fps()
    
    if avg_fps < target_fps * 0.7:
        # 严重低于目标,大幅降低质量
        _decrease_quality(2)
    elif avg_fps < target_fps * 0.9:
        # 略低于目标,小幅降低质量
        _decrease_quality(1)
    elif avg_fps > target_fps * 1.2:
        # 高于目标,提升质量
        _increase_quality(1)

func _get_average_fps() -> float:
    if fps_samples.is_empty():
        return target_fps
    
    var sum = 0.0
    for fps in fps_samples:
        sum += fps
    return sum / fps_samples.size()

func _decrease_quality(steps: int):
    var new_quality = min(current_quality + steps, QualityLevel.VERY_LOW)
    if new_quality != current_quality:
        current_quality = new_quality
        _apply_quality_settings()
        print("Light quality decreased to: ", QualityLevel.keys()[current_quality])

func _increase_quality(steps: int):
    var new_quality = max(current_quality - steps, QualityLevel.ULTRA)
    if new_quality != current_quality:
        current_quality = new_quality
        _apply_quality_settings()
        print("Light quality increased to: ", QualityLevel.keys()[current_quality])

func _apply_quality_settings():
    if not world_environment or not world_environment.environment:
        return
    
    var env = world_environment.environment
    
    match current_quality:
        QualityLevel.ULTRA:
            env.ssao_enabled = true
            env.ssao_intensity = 2.0
            env.ssr_enabled = true
            env.ssil_enabled = true
            env.sdfgi_enabled = true
            env.glow_enabled = true
        
        QualityLevel.HIGH:
            env.ssao_enabled = true
            env.ssao_intensity = 1.5
            env.ssr_enabled = true
            env.ssil_enabled = false
            env.sdfgi_enabled = true
            env.glow_enabled = true
        
        QualityLevel.MEDIUM:
            env.ssao_enabled = true
            env.ssao_intensity = 1.0
            env.ssr_enabled = false
            env.ssil_enabled = false
            env.sdfgi_enabled = true
            env.glow_enabled = true
        
        QualityLevel.LOW:
            env.ssao_enabled = true
            env.ssao_intensity = 0.5
            env.ssr_enabled = false
            env.ssil_enabled = false
            env.sdfgi_enabled = false
            env.glow_enabled = false
        
        QualityLevel.VERY_LOW:
            env.ssao_enabled = false
            env.ssr_enabled = false
            env.ssil_enabled = false
            env.sdfgi_enabled = false
            env.glow_enabled = false
    
    # 同时调整阴影质量
    _adjust_shadow_quality()

func _adjust_shadow_quality():
    # 调整所有方向光的阴影
    for light in get_tree().get_nodes_in_group("directional_lights"):
        if light is DirectionalLight3D:
            match current_quality:
                QualityLevel.ULTRA, QualityLevel.HIGH:
                    light.directional_shadow_mode = DirectionalLight3D.SHADOW_PARALLEL_4_SPLITS
                QualityLevel.MEDIUM:
                    light.directional_shadow_mode = DirectionalLight3D.SHADOW_PARALLEL_2_SPLITS
                _:
                    light.directional_shadow_mode = DirectionalLight3D.SHADOW_ORTHOGONAL

28.10 实际案例:日夜循环系统

# day_night_cycle.gd
# 完整的日夜循环系统

extends Node3D

## 时间设置
@export_group("Time Settings")
@export var day_duration_minutes: float = 10.0  # 游戏中一天的实际时间(分钟)
@export var start_hour: float = 8.0  # 游戏开始时间
@export var time_scale: float = 1.0  # 时间流速

## 光照组件
@export_group("Light Components")
@export var sun_light: DirectionalLight3D
@export var moon_light: DirectionalLight3D
@export var world_environment: WorldEnvironment

## 天空设置
@export_group("Sky Settings")
@export var sky_gradient: GradientTexture1D

## 当前时间(0-24小时)
var current_hour: float = 0.0

## 时间相关信号
signal hour_changed(hour: int)
signal day_phase_changed(phase: String)
signal sun_rise()
signal sun_set()

## 日出日落时间
var sunrise_hour: float = 6.0
var sunset_hour: float = 18.0

## 颜色配置
var sun_colors = {
    "night": Color(0.2, 0.2, 0.4),
    "dawn": Color(1.0, 0.6, 0.4),
    "morning": Color(1.0, 0.95, 0.9),
    "noon": Color(1.0, 1.0, 0.98),
    "afternoon": Color(1.0, 0.95, 0.85),
    "dusk": Color(1.0, 0.5, 0.3),
}

var ambient_colors = {
    "night": Color(0.05, 0.05, 0.1),
    "dawn": Color(0.3, 0.25, 0.3),
    "morning": Color(0.5, 0.5, 0.55),
    "noon": Color(0.6, 0.6, 0.6),
    "afternoon": Color(0.55, 0.5, 0.5),
    "dusk": Color(0.3, 0.2, 0.25),
}

var previous_hour: int = -1
var previous_phase: String = ""

func _ready():
    current_hour = start_hour
    _update_lighting()

func _process(delta):
    # 更新时间
    var hours_per_second = 24.0 / (day_duration_minutes * 60.0)
    current_hour += hours_per_second * delta * time_scale
    
    # 循环时间
    if current_hour >= 24.0:
        current_hour -= 24.0
    
    # 更新光照
    _update_lighting()
    
    # 发送信号
    _check_signals()

func _update_lighting():
    _update_sun_position()
    _update_sun_color()
    _update_moon()
    _update_environment()
    _update_sky()

func _update_sun_position():
    if not sun_light:
        return
    
    # 计算太阳角度(基于时间)
    var day_progress = (current_hour - sunrise_hour) / (sunset_hour - sunrise_hour)
    day_progress = clamp(day_progress, 0.0, 1.0)
    
    # 太阳从东升到西落的弧线
    var sun_angle = day_progress * PI  # 0到180度
    
    # 设置太阳位置
    var sun_height = sin(sun_angle) * 80.0  # 最高80度
    var sun_horizontal = cos(sun_angle)
    
    sun_light.rotation_degrees.x = -sun_height
    sun_light.rotation_degrees.y = lerp(-90.0, 90.0, day_progress)
    
    # 太阳在地平线以下时禁用
    sun_light.visible = sun_height > -5
    
    # 根据高度调整强度
    if sun_height > 0:
        var intensity = sin(sun_angle)
        sun_light.light_energy = intensity * 1.2
    else:
        sun_light.light_energy = 0.0

func _update_sun_color():
    if not sun_light:
        return
    
    var phase = _get_day_phase()
    var next_phase = _get_next_phase(phase)
    var blend = _get_phase_blend(phase)
    
    # 混合颜色
    var current_color = sun_colors.get(phase, Color.WHITE)
    var next_color = sun_colors.get(next_phase, Color.WHITE)
    
    sun_light.light_color = current_color.lerp(next_color, blend)

func _update_moon():
    if not moon_light:
        return
    
    # 月亮在太阳对面
    var is_night = current_hour < sunrise_hour or current_hour > sunset_hour
    moon_light.visible = is_night
    
    if is_night:
        # 计算月亮位置
        var night_progress: float
        if current_hour > sunset_hour:
            night_progress = (current_hour - sunset_hour) / (24.0 - sunset_hour + sunrise_hour)
        else:
            night_progress = (current_hour + 24.0 - sunset_hour) / (24.0 - sunset_hour + sunrise_hour)
        
        var moon_angle = night_progress * PI
        moon_light.rotation_degrees.x = -sin(moon_angle) * 60.0
        moon_light.rotation_degrees.y = lerp(90.0, -90.0, night_progress)
        
        # 月光强度
        moon_light.light_energy = sin(moon_angle) * 0.2

func _update_environment():
    if not world_environment or not world_environment.environment:
        return
    
    var env = world_environment.environment
    var phase = _get_day_phase()
    var blend = _get_phase_blend(phase)
    var next_phase = _get_next_phase(phase)
    
    # 环境光颜色
    var current_ambient = ambient_colors.get(phase, Color.WHITE)
    var next_ambient = ambient_colors.get(next_phase, Color.WHITE)
    env.ambient_light_color = current_ambient.lerp(next_ambient, blend)
    
    # 环境光强度
    var is_day = current_hour >= sunrise_hour and current_hour <= sunset_hour
    env.ambient_light_energy = 0.5 if is_day else 0.15
    
    # 雾气颜色
    if env.fog_enabled:
        env.fog_light_color = env.ambient_light_color

func _update_sky():
    if not world_environment or not world_environment.environment:
        return
    
    var env = world_environment.environment
    if not env.sky or not env.sky.sky_material is ProceduralSkyMaterial:
        return
    
    var sky_mat = env.sky.sky_material as ProceduralSkyMaterial
    var phase = _get_day_phase()
    
    # 根据时段更新天空颜色
    match phase:
        "night":
            sky_mat.sky_top_color = Color(0.02, 0.02, 0.05)
            sky_mat.sky_horizon_color = Color(0.05, 0.05, 0.1)
        "dawn":
            sky_mat.sky_top_color = Color(0.2, 0.15, 0.3)
            sky_mat.sky_horizon_color = Color(1.0, 0.6, 0.4)
        "morning":
            sky_mat.sky_top_color = Color(0.3, 0.5, 0.8)
            sky_mat.sky_horizon_color = Color(0.7, 0.8, 0.9)
        "noon":
            sky_mat.sky_top_color = Color(0.3, 0.5, 0.9)
            sky_mat.sky_horizon_color = Color(0.6, 0.8, 0.95)
        "afternoon":
            sky_mat.sky_top_color = Color(0.4, 0.5, 0.8)
            sky_mat.sky_horizon_color = Color(0.7, 0.75, 0.85)
        "dusk":
            sky_mat.sky_top_color = Color(0.2, 0.1, 0.3)
            sky_mat.sky_horizon_color = Color(1.0, 0.4, 0.2)

func _get_day_phase() -> String:
    if current_hour < 5:
        return "night"
    elif current_hour < 7:
        return "dawn"
    elif current_hour < 11:
        return "morning"
    elif current_hour < 15:
        return "noon"
    elif current_hour < 18:
        return "afternoon"
    elif current_hour < 20:
        return "dusk"
    else:
        return "night"

func _get_next_phase(current_phase: String) -> String:
    var phases = ["night", "dawn", "morning", "noon", "afternoon", "dusk"]
    var index = phases.find(current_phase)
    return phases[(index + 1) % phases.size()]

func _get_phase_blend(phase: String) -> float:
    var phase_ranges = {
        "night": [20.0, 5.0],
        "dawn": [5.0, 7.0],
        "morning": [7.0, 11.0],
        "noon": [11.0, 15.0],
        "afternoon": [15.0, 18.0],
        "dusk": [18.0, 20.0]
    }
    
    var range_data = phase_ranges.get(phase, [0.0, 24.0])
    var start = range_data[0]
    var end = range_data[1]
    
    # 处理跨越午夜的情况
    if start > end:
        if current_hour >= start:
            return (current_hour - start) / (24.0 - start + end)
        else:
            return (current_hour + 24.0 - start) / (24.0 - start + end)
    
    return (current_hour - start) / (end - start)

func _check_signals():
    var current_int_hour = int(current_hour)
    
    # 整点变化
    if current_int_hour != previous_hour:
        previous_hour = current_int_hour
        emit_signal("hour_changed", current_int_hour)
        
        # 检查日出日落
        if current_int_hour == int(sunrise_hour):
            emit_signal("sun_rise")
        elif current_int_hour == int(sunset_hour):
            emit_signal("sun_set")
    
    # 时段变化
    var current_phase = _get_day_phase()
    if current_phase != previous_phase:
        previous_phase = current_phase
        emit_signal("day_phase_changed", current_phase)

## 公共API
func set_time(hour: float):
    current_hour = fmod(hour, 24.0)
    _update_lighting()

func get_time() -> float:
    return current_hour

func get_time_string() -> String:
    var hours = int(current_hour)
    var minutes = int((current_hour - hours) * 60)
    return "%02d:%02d" % [hours, minutes]

func is_day() -> bool:
    return current_hour >= sunrise_hour and current_hour < sunset_hour

func is_night() -> bool:
    return not is_day()

func skip_to_morning():
    set_time(8.0)

func skip_to_night():
    set_time(22.0)

28.11 本章小结

本章深入讲解了Godot 4的3D光照系统:

  1. 光照基础理论:理解光照模型、衰减和阴影计算原理
  2. DirectionalLight3D:太阳光模拟和级联阴影贴图配置
  3. OmniLight3D:点光源设置、闪烁效果和动态管理
  4. SpotLight3D:聚光灯配置和体积光效果
  5. 环境光与天空:WorldEnvironment和HDRI天空盒
  6. 全局光照:SDFGI、LightmapGI、VoxelGI的配置与选择
  7. 反射探针:配置和动态管理反射效果
  8. 屏幕空间效果:SSAO、SSR、SSIL的使用
  9. 光照优化:LOD系统、剔除系统和动态质量调整
  10. 实际案例:完整的日夜循环系统实现

光照是3D游戏视觉表现的核心,合理的光照设计能够显著提升游戏的氛围和沉浸感。下一章我们将学习Godot的3D物理系统,为游戏添加真实的物理交互。

← 返回目录