第二十四章:3D节点详解
"掌握各类3D节点,是构建复杂3D场景的基础。"
Godot提供了丰富的3D节点类型,每种节点都有其特定用途。本章将详细介绍常用的3D节点及其使用方法。
24.1 节点层级结构
24.1.1 3D节点继承树
Node3D (所有3D节点的基类)
├── VisualInstance3D (可视节点)
│ ├── GeometryInstance3D
│ │ ├── MeshInstance3D (网格实例)
│ │ ├── MultiMeshInstance3D (多重网格)
│ │ ├── SpriteBase3D
│ │ │ ├── Sprite3D (3D精灵)
│ │ │ └── AnimatedSprite3D
│ │ ├── Label3D (3D文本)
│ │ └── GPUParticles3D (GPU粒子)
│ ├── Light3D (光源)
│ │ ├── DirectionalLight3D
│ │ ├── OmniLight3D
│ │ └── SpotLight3D
│ ├── Decal (贴花)
│ ├── FogVolume (雾体积)
│ └── ReflectionProbe (反射探针)
├── Camera3D (相机)
├── CollisionObject3D (碰撞物体)
│ ├── PhysicsBody3D
│ │ ├── StaticBody3D
│ │ ├── AnimatableBody3D
│ │ ├── RigidBody3D
│ │ └── CharacterBody3D
│ └── Area3D
├── Skeleton3D (骨骼)
├── NavigationRegion3D (导航区域)
├── Path3D (路径)
└── AudioStreamPlayer3D (3D音频)
24.2 MeshInstance3D
24.2.1 基本使用
extends MeshInstance3D
func _ready():
# 设置网格
mesh = BoxMesh.new()
# 材质覆盖(应用于所有表面)
material_override = StandardMaterial3D.new()
material_override.albedo_color = Color.RED
# 设置特定表面的材质
var mat = StandardMaterial3D.new()
mat.albedo_color = Color.BLUE
set_surface_override_material(0, mat)
# 可见性
visible = true
cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_ON
# SHADOW_CASTING_SETTING_OFF - 不投射阴影
# SHADOW_CASTING_SETTING_ON - 投射阴影
# SHADOW_CASTING_SETTING_DOUBLE_SIDED - 双面阴影
# SHADOW_CASTING_SETTING_SHADOWS_ONLY - 仅阴影(不可见)
# LOD设置
lod_bias = 1.0 # LOD偏移(>1更快切换低LOD)
# GI模式
gi_mode = GeometryInstance3D.GI_MODE_STATIC # 静态GI
24.2.2 网格数据访问
extends MeshInstance3D
func analyze_mesh():
if not mesh:
return
# 获取表面数量
var surface_count = mesh.get_surface_count()
print("表面数量:", surface_count)
# 获取AABB(轴对齐边界框)
var aabb = mesh.get_aabb()
print("边界框:", aabb)
print("尺寸:", aabb.size)
print("中心:", aabb.get_center())
# 获取全局AABB
var global_aabb = get_aabb()
func get_mesh_data(surface_idx: int = 0) -> Dictionary:
var arrays = mesh.surface_get_arrays(surface_idx)
return {
"vertices": arrays[Mesh.ARRAY_VERTEX],
"normals": arrays[Mesh.ARRAY_NORMAL],
"uvs": arrays[Mesh.ARRAY_TEX_UV],
"indices": arrays[Mesh.ARRAY_INDEX]
}
24.3 MultiMeshInstance3D
24.3.1 高效渲染大量物体
extends MultiMeshInstance3D
@export var instance_count: int = 1000
@export var spread: float = 50.0
func _ready():
setup_multimesh()
func setup_multimesh():
# 创建MultiMesh
multimesh = MultiMesh.new()
multimesh.transform_format = MultiMesh.TRANSFORM_3D
multimesh.instance_count = instance_count
# 设置网格
var mesh = BoxMesh.new()
mesh.size = Vector3(0.5, 0.5, 0.5)
multimesh.mesh = mesh
# 设置每个实例的变换
for i in range(instance_count):
var pos = Vector3(
randf_range(-spread, spread),
randf_range(0, 5),
randf_range(-spread, spread)
)
var transform = Transform3D(Basis(), pos)
multimesh.set_instance_transform(i, transform)
# 动态更新实例
func update_instances():
for i in range(multimesh.instance_count):
var t = multimesh.get_instance_transform(i)
t.origin.y = sin(Time.get_ticks_msec() * 0.001 + i * 0.1)
multimesh.set_instance_transform(i, t)
24.3.2 带颜色的MultiMesh
func setup_colored_multimesh():
multimesh = MultiMesh.new()
multimesh.transform_format = MultiMesh.TRANSFORM_3D
multimesh.use_colors = true # 启用颜色
multimesh.instance_count = 100
var mesh = SphereMesh.new()
multimesh.mesh = mesh
for i in range(100):
var pos = Vector3(i % 10 * 2, 0, i / 10 * 2)
multimesh.set_instance_transform(i, Transform3D(Basis(), pos))
# 设置颜色
var color = Color.from_hsv(float(i) / 100.0, 0.8, 1.0)
multimesh.set_instance_color(i, color)
# 材质需要启用vertex_color
func setup_material():
var mat = StandardMaterial3D.new()
mat.vertex_color_use_as_albedo = true
material_override = mat
24.4 Sprite3D与Label3D
24.4.1 Sprite3D
extends Sprite3D
func _ready():
# 纹理
texture = preload("res://icon.png")
# 像素大小
pixel_size = 0.01 # 1像素 = 0.01单位
# 朝向模式
billboard = BaseMaterial3D.BILLBOARD_ENABLED # 始终面向相机
# BILLBOARD_DISABLED - 禁用
# BILLBOARD_ENABLED - 启用
# BILLBOARD_FIXED_Y - Y轴固定
# BILLBOARD_PARTICLES - 粒子模式
# 透明度
transparent = true
alpha_cut = SpriteBase3D.ALPHA_CUT_DISABLED
# ALPHA_CUT_DISABLED - 禁用裁剪
# ALPHA_CUT_DISCARD - 丢弃透明像素
# ALPHA_CUT_OPAQUE_PREPASS - 不透明预通道
# 双面显示
double_sided = true
# 着色
shaded = true # 受光照影响
modulate = Color.WHITE
24.4.2 AnimatedSprite3D
extends AnimatedSprite3D
func _ready():
# 使用SpriteFrames资源
sprite_frames = preload("res://animations/character.tres")
# 播放动画
play("idle")
# 信号
animation_finished.connect(_on_animation_finished)
frame_changed.connect(_on_frame_changed)
func _on_animation_finished():
if animation == "attack":
play("idle")
24.4.3 Label3D
extends Label3D
func _ready():
# 文本内容
text = "Hello 3D World!"
# 字体
font = preload("res://fonts/my_font.ttf")
font_size = 32
# 外观
modulate = Color.WHITE
outline_modulate = Color.BLACK
outline_size = 2
# 朝向
billboard = BaseMaterial3D.BILLBOARD_ENABLED
# 对齐
horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
vertical_alignment = VERTICAL_ALIGNMENT_CENTER
# 像素大小
pixel_size = 0.005
# 动态更新文本
func set_damage_text(damage: int):
text = str(damage)
modulate = Color.RED if damage > 50 else Color.YELLOW
# 浮动动画
var tween = create_tween()
tween.tween_property(self, "position:y", position.y + 2, 1.0)
tween.parallel().tween_property(self, "modulate:a", 0.0, 1.0)
tween.tween_callback(queue_free)
24.5 GPUParticles3D
24.5.1 基本粒子系统
extends GPUParticles3D
func _ready():
# 基本设置
emitting = true
amount = 100
lifetime = 2.0
one_shot = false
# 发射器形状(通过ParticleProcessMaterial设置)
var material = ParticleProcessMaterial.new()
# 发射形状
material.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_SPHERE
material.emission_sphere_radius = 1.0
# 方向和速度
material.direction = Vector3(0, 1, 0)
material.spread = 15.0
material.initial_velocity_min = 5.0
material.initial_velocity_max = 10.0
# 重力
material.gravity = Vector3(0, -9.8, 0)
# 缩放
material.scale_min = 0.5
material.scale_max = 1.5
# 颜色
var gradient = Gradient.new()
gradient.set_color(0, Color.WHITE)
gradient.set_color(1, Color(1, 1, 1, 0))
material.color_ramp = GradientTexture1D.new()
material.color_ramp.gradient = gradient
process_material = material
# 粒子网格
var mesh = SphereMesh.new()
mesh.radius = 0.1
mesh.height = 0.2
draw_pass_1 = mesh
24.5.2 预设粒子效果
# 火焰效果
func create_fire_particles() -> GPUParticles3D:
var particles = GPUParticles3D.new()
particles.amount = 50
particles.lifetime = 1.0
var mat = ParticleProcessMaterial.new()
mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX
mat.emission_box_extents = Vector3(0.2, 0, 0.2)
mat.direction = Vector3(0, 1, 0)
mat.spread = 10.0
mat.initial_velocity_min = 2.0
mat.initial_velocity_max = 4.0
mat.gravity = Vector3(0, 0, 0)
mat.scale_min = 0.3
mat.scale_max = 0.8
# 颜色渐变:黄色 -> 橙色 -> 红色 -> 透明
var gradient = Gradient.new()
gradient.set_color(0, Color(1, 1, 0.3))
gradient.add_point(0.3, Color(1, 0.5, 0))
gradient.add_point(0.6, Color(1, 0.2, 0))
gradient.set_color(1, Color(0.5, 0, 0, 0))
var gradient_tex = GradientTexture1D.new()
gradient_tex.gradient = gradient
mat.color_ramp = gradient_tex
particles.process_material = mat
# 使用Billboard quad
var quad = QuadMesh.new()
quad.size = Vector2(0.5, 0.5)
particles.draw_pass_1 = quad
return particles
# 爆炸效果
func create_explosion() -> GPUParticles3D:
var particles = GPUParticles3D.new()
particles.amount = 100
particles.lifetime = 0.8
particles.one_shot = true
particles.explosiveness = 1.0 # 同时发射所有粒子
var mat = ParticleProcessMaterial.new()
mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_SPHERE
mat.emission_sphere_radius = 0.1
mat.direction = Vector3(0, 0, 0)
mat.spread = 180.0
mat.initial_velocity_min = 5.0
mat.initial_velocity_max = 15.0
mat.damping_min = 2.0
mat.damping_max = 4.0
mat.scale_min = 0.2
mat.scale_max = 0.5
particles.process_material = mat
return particles
24.6 Area3D
24.6.1 区域检测
extends Area3D
signal player_entered
signal player_exited
func _ready():
# 碰撞设置
collision_layer = 4
collision_mask = 1 # 检测玩家
# 监测设置
monitoring = true
monitorable = true
# 信号连接
body_entered.connect(_on_body_entered)
body_exited.connect(_on_body_exited)
area_entered.connect(_on_area_entered)
area_exited.connect(_on_area_exited)
func _on_body_entered(body: Node3D):
if body.is_in_group("player"):
player_entered.emit()
func _on_body_exited(body: Node3D):
if body.is_in_group("player"):
player_exited.emit()
# 手动检测
func get_overlapping_players() -> Array:
var players = []
for body in get_overlapping_bodies():
if body.is_in_group("player"):
players.append(body)
return players
24.6.2 物理属性覆盖
extends Area3D
func _ready():
# 重力覆盖
gravity_space_override = SPACE_OVERRIDE_REPLACE
gravity_direction = Vector3.UP # 反重力
gravity = 9.8
# 或使用重力点
gravity_point = true
gravity_point_center = Vector3.ZERO
gravity_point_unit_distance = 10.0
# 阻尼覆盖
linear_damp_space_override = SPACE_OVERRIDE_COMBINE
linear_damp = 2.0 # 减速区域
angular_damp_space_override = SPACE_OVERRIDE_COMBINE
angular_damp = 1.0
# 优先级(重叠区域时)
priority = 1
24.7 Path3D与PathFollow3D
24.7.1 路径创建
extends Path3D
func _ready():
# 创建曲线
curve = Curve3D.new()
# 添加点
curve.add_point(Vector3(0, 0, 0))
curve.add_point(Vector3(5, 2, 0), Vector3(-1, 0, 0), Vector3(1, 0, 0))
curve.add_point(Vector3(10, 0, 5))
curve.add_point(Vector3(5, 1, 10))
curve.add_point(Vector3(0, 0, 5))
# 闭合曲线
curve.add_point(Vector3(0, 0, 0))
func get_point_at_offset(offset: float) -> Vector3:
return curve.sample_baked(offset)
func get_path_length() -> float:
return curve.get_baked_length()
24.7.2 路径跟随
extends PathFollow3D
@export var speed: float = 5.0
@export var loop: bool = true
func _ready():
# 设置
rotates = true # 跟随路径旋转
cubic_interp = true # 三次插值
loop = loop
func _process(delta: float):
progress += speed * delta
# 非循环模式下检查结束
if not loop and progress_ratio >= 1.0:
progress_ratio = 1.0
# 获取当前信息
func get_current_direction() -> Vector3:
var parent_path = get_parent() as Path3D
if parent_path and parent_path.curve:
return parent_path.curve.sample_baked_with_rotation(progress).basis.z
return Vector3.FORWARD
24.8 AudioStreamPlayer3D
24.8.1 3D音频
extends AudioStreamPlayer3D
func _ready():
# 音频流
stream = preload("res://audio/ambient.ogg")
# 播放设置
autoplay = true
# 音量
volume_db = 0.0
unit_size = 10.0 # 参考距离
max_db = 3.0
# 衰减
attenuation_model = ATTENUATION_INVERSE_DISTANCE
# ATTENUATION_INVERSE_DISTANCE - 反距离
# ATTENUATION_INVERSE_SQUARE_DISTANCE - 反距离平方
# ATTENUATION_LOGARITHMIC - 对数
# ATTENUATION_DISABLED - 禁用衰减
# 距离范围
max_distance = 50.0
# 多普勒效果
doppler_tracking = DOPPLER_TRACKING_PHYSICS_STEP
# 区域混响
area_mask = 1
func play_sound(audio: AudioStream):
stream = audio
play()
func fade_out(duration: float = 1.0):
var tween = create_tween()
tween.tween_property(self, "volume_db", -80, duration)
tween.tween_callback(stop)
24.9 实际案例
24.9.1 可交互物体基类
# interactable_3d.gd
class_name Interactable3D
extends StaticBody3D
signal interacted(interactor: Node3D)
signal hover_started
signal hover_ended
@export var interaction_prompt: String = "按E交互"
@export var highlight_color: Color = Color(1.2, 1.2, 1.2)
@onready var mesh: MeshInstance3D = $MeshInstance3D
@onready var collision: CollisionShape3D = $CollisionShape3D
@onready var interaction_area: Area3D = $InteractionArea
var original_material: Material
var is_highlighted: bool = false
func _ready():
add_to_group("interactable")
if mesh.material_override:
original_material = mesh.material_override.duplicate()
interaction_area.body_entered.connect(_on_body_entered)
interaction_area.body_exited.connect(_on_body_exited)
func _on_body_entered(body: Node3D):
if body.is_in_group("player"):
highlight(true)
hover_started.emit()
func _on_body_exited(body: Node3D):
if body.is_in_group("player"):
highlight(false)
hover_ended.emit()
func highlight(enabled: bool):
is_highlighted = enabled
if mesh.material_override:
if enabled:
mesh.material_override.albedo_color *= highlight_color
else:
mesh.material_override = original_material.duplicate()
func interact(interactor: Node3D):
interacted.emit(interactor)
_on_interact(interactor)
func _on_interact(interactor: Node3D):
# 由子类重写
pass
24.9.2 3D物体生成器
# object_spawner_3d.gd
class_name ObjectSpawner3D
extends Node3D
@export var spawn_scene: PackedScene
@export var spawn_count: int = 10
@export var spawn_area: Vector3 = Vector3(20, 0, 20)
@export var spawn_interval: float = 0.5
@export var auto_start: bool = true
var spawned_objects: Array[Node3D] = []
var spawn_timer: Timer
func _ready():
spawn_timer = Timer.new()
spawn_timer.wait_time = spawn_interval
spawn_timer.timeout.connect(_spawn_one)
add_child(spawn_timer)
if auto_start:
start_spawning()
func start_spawning():
spawn_timer.start()
func stop_spawning():
spawn_timer.stop()
func _spawn_one():
if spawned_objects.size() >= spawn_count:
stop_spawning()
return
var instance = spawn_scene.instantiate()
var spawn_pos = Vector3(
randf_range(-spawn_area.x / 2, spawn_area.x / 2),
spawn_area.y,
randf_range(-spawn_area.z / 2, spawn_area.z / 2)
)
instance.global_position = global_position + spawn_pos
get_parent().add_child(instance)
spawned_objects.append(instance)
# 监听销毁
instance.tree_exited.connect(func(): spawned_objects.erase(instance))
func clear_all():
for obj in spawned_objects:
if is_instance_valid(obj):
obj.queue_free()
spawned_objects.clear()
本章小结
本章详细介绍了Godot的各类3D节点:
- 节点层级:3D节点继承结构概览
- MeshInstance3D:网格渲染、材质设置
- MultiMeshInstance3D:高效批量渲染
- Sprite3D/Label3D:3D精灵和文本
- GPUParticles3D:GPU粒子系统
- Area3D:区域检测、物理覆盖
- Path3D:路径创建和跟随
- AudioStreamPlayer3D:3D空间音频
- 实际案例:可交互物体、物体生成器
下一章将深入学习3D变换与坐标系统。
上一章:3D场景基础
下一章:3D变换与坐标系