第二十八章: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光照系统:
- 光照基础理论:理解光照模型、衰减和阴影计算原理
- DirectionalLight3D:太阳光模拟和级联阴影贴图配置
- OmniLight3D:点光源设置、闪烁效果和动态管理
- SpotLight3D:聚光灯配置和体积光效果
- 环境光与天空:WorldEnvironment和HDRI天空盒
- 全局光照:SDFGI、LightmapGI、VoxelGI的配置与选择
- 反射探针:配置和动态管理反射效果
- 屏幕空间效果:SSAO、SSR、SSIL的使用
- 光照优化:LOD系统、剔除系统和动态质量调整
- 实际案例:完整的日夜循环系统实现
光照是3D游戏视觉表现的核心,合理的光照设计能够显著提升游戏的氛围和沉浸感。下一章我们将学习Godot的3D物理系统,为游戏添加真实的物理交互。