第二十四章:3D节点详解

第二十四章: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节点:

  1. 节点层级:3D节点继承结构概览
  2. MeshInstance3D:网格渲染、材质设置
  3. MultiMeshInstance3D:高效批量渲染
  4. Sprite3D/Label3D:3D精灵和文本
  5. GPUParticles3D:GPU粒子系统
  6. Area3D:区域检测、物理覆盖
  7. Path3D:路径创建和跟随
  8. AudioStreamPlayer3D:3D空间音频
  9. 实际案例:可交互物体、物体生成器

下一章将深入学习3D变换与坐标系统。


上一章:3D场景基础

下一章:3D变换与坐标系

← 返回目录