第二十三章:3D场景基础

第二十三章:3D场景基础

"从2D迈入3D,开启全新的游戏开发维度。"

从本章开始,我们进入3D游戏开发的学习。3D开发比2D更复杂,但Godot提供了强大而易用的3D工具集。本章将介绍3D场景的基础概念和核心组件。


23.1 3D与2D的区别

23.1.1 坐标系统

2D坐标系:
- X轴:向右为正
- Y轴:向下为正
- 单位:像素

3D坐标系(Godot使用右手坐标系):
- X轴:向右为正(红色)
- Y轴:向上为正(绿色)
- Z轴:向屏幕外为正(蓝色)
- 单位:米(默认)

      Y+
      │
      │
      └──────► X+
     /
    /
   Z+

23.1.2 核心概念对比

概念2D3D
基础节点Node2DNode3D
精灵Sprite2DMeshInstance3D
相机Camera2DCamera3D
物理体CharacterBody2DCharacterBody3D
光照PointLight2DOmniLight3D
碰撞CollisionShape2DCollisionShape3D

23.1.3 渲染管线

3D渲染管线简化流程:
1. 顶点处理 - 变换顶点位置
2. 图元组装 - 组成三角形
3. 光栅化 - 转换为像素
4. 片段处理 - 计算颜色
5. 输出合并 - 最终图像

Godot渲染器:
├── Forward+ (默认) - 适合大多数场景
├── Mobile - 移动设备优化
└── Compatibility - 兼容旧设备

23.2 3D场景结构

23.2.1 基本场景搭建

# 典型的3D场景结构
Main (Node3D)
├── WorldEnvironment      # 环境设置
├── DirectionalLight3D    # 主光源(太阳)
├── Camera3D              # 相机
├── World                 # 游戏世界
│   ├── Ground           # 地面
│   ├── Player           # 玩家
│   └── Enemies          # 敌人
└── UI (CanvasLayer)      # UI层

23.2.2 代码创建3D场景

extends Node3D

func _ready():
    # 创建相机
    var camera = Camera3D.new()
    camera.position = Vector3(0, 5, 10)
    camera.look_at(Vector3.ZERO)
    add_child(camera)
    camera.make_current()
    
    # 创建光源
    var light = DirectionalLight3D.new()
    light.rotation_degrees = Vector3(-45, 45, 0)
    add_child(light)
    
    # 创建地面
    var ground = create_ground()
    add_child(ground)
    
    # 创建测试立方体
    var cube = create_cube()
    cube.position = Vector3(0, 0.5, 0)
    add_child(cube)

func create_ground() -> MeshInstance3D:
    var mesh_instance = MeshInstance3D.new()
    var plane_mesh = PlaneMesh.new()
    plane_mesh.size = Vector2(20, 20)
    mesh_instance.mesh = plane_mesh
    
    # 添加材质
    var material = StandardMaterial3D.new()
    material.albedo_color = Color(0.3, 0.5, 0.3)
    mesh_instance.material_override = material
    
    return mesh_instance

func create_cube() -> MeshInstance3D:
    var mesh_instance = MeshInstance3D.new()
    var box_mesh = BoxMesh.new()
    box_mesh.size = Vector3(1, 1, 1)
    mesh_instance.mesh = box_mesh
    
    var material = StandardMaterial3D.new()
    material.albedo_color = Color.CORAL
    mesh_instance.material_override = material
    
    return mesh_instance

23.3 Node3D基础

23.3.1 变换属性

extends Node3D

func _ready():
    # 位置
    position = Vector3(1, 2, 3)
    global_position = Vector3(10, 0, 5)
    
    # 旋转(弧度)
    rotation = Vector3(0, PI/4, 0)  # 绕Y轴旋转45度
    rotation_degrees = Vector3(0, 45, 0)  # 使用角度
    
    # 全局旋转
    global_rotation = Vector3(0, PI/2, 0)
    global_rotation_degrees = Vector3(0, 90, 0)
    
    # 缩放
    scale = Vector3(2, 2, 2)  # 均匀放大2倍
    
    # 变换矩阵
    var t: Transform3D = transform
    var gt: Transform3D = global_transform

23.3.2 Transform3D详解

extends Node3D

func understand_transform():
    # Transform3D结构
    # - basis: Basis(3x3旋转缩放矩阵)
    # - origin: Vector3(位置)
    
    var t = Transform3D()
    
    # 基础向量
    print("X轴:", t.basis.x)  # 右方向
    print("Y轴:", t.basis.y)  # 上方向
    print("Z轴:", t.basis.z)  # 前方向(朝向屏幕外)
    print("原点:", t.origin)
    
    # 从变换提取信息
    var rotation = t.basis.get_euler()
    var scale = t.basis.get_scale()

func transform_operations():
    var t = Transform3D.IDENTITY
    
    # 平移
    t = t.translated(Vector3(1, 0, 0))
    
    # 旋转
    t = t.rotated(Vector3.UP, PI/4)  # 绕Y轴旋转
    
    # 缩放
    t = t.scaled(Vector3(2, 2, 2))
    
    # 应用变换
    transform = t
    
    # 逆变换
    var inverse = t.inverse()
    
    # 变换点
    var local_point = Vector3(1, 0, 0)
    var world_point = t * local_point

23.3.3 方向与朝向

extends Node3D

# 获取方向向量
func get_directions():
    # 本地方向(相对于自身旋转)
    var forward = -global_transform.basis.z  # 前方
    var right = global_transform.basis.x     # 右方
    var up = global_transform.basis.y        # 上方
    
    return {
        "forward": forward,
        "right": right,
        "up": up
    }

# 朝向目标
func look_at_target(target: Vector3):
    look_at(target, Vector3.UP)

# 平滑转向
func rotate_toward(target: Vector3, delta: float, speed: float = 5.0):
    var direction = (target - global_position).normalized()
    var target_transform = transform.looking_at(global_position + direction, Vector3.UP)
    transform = transform.interpolate_with(target_transform, speed * delta)

23.4 基本3D几何体

23.4.1 内置网格类型

extends Node3D

func create_primitives():
    # 立方体
    var box = BoxMesh.new()
    box.size = Vector3(1, 1, 1)
    
    # 球体
    var sphere = SphereMesh.new()
    sphere.radius = 0.5
    sphere.height = 1.0
    sphere.radial_segments = 32
    sphere.rings = 16
    
    # 圆柱体
    var cylinder = CylinderMesh.new()
    cylinder.top_radius = 0.5
    cylinder.bottom_radius = 0.5
    cylinder.height = 2.0
    
    # 胶囊体
    var capsule = CapsuleMesh.new()
    capsule.radius = 0.5
    capsule.height = 2.0
    
    # 平面
    var plane = PlaneMesh.new()
    plane.size = Vector2(10, 10)
    
    # 棱柱
    var prism = PrismMesh.new()
    prism.size = Vector3(1, 1, 1)
    
    # 圆环
    var torus = TorusMesh.new()
    torus.inner_radius = 0.3
    torus.outer_radius = 0.5

func create_mesh_instance(mesh: Mesh, pos: Vector3) -> MeshInstance3D:
    var instance = MeshInstance3D.new()
    instance.mesh = mesh
    instance.position = pos
    add_child(instance)
    return instance

23.4.2 程序化网格

extends MeshInstance3D

func _ready():
    mesh = create_custom_mesh()

func create_custom_mesh() -> ArrayMesh:
    var surface_array = []
    surface_array.resize(Mesh.ARRAY_MAX)
    
    # 顶点数据
    var vertices = PackedVector3Array([
        Vector3(-1, 0, -1),
        Vector3(1, 0, -1),
        Vector3(1, 0, 1),
        Vector3(-1, 0, 1)
    ])
    
    # UV坐标
    var uvs = PackedVector2Array([
        Vector2(0, 0),
        Vector2(1, 0),
        Vector2(1, 1),
        Vector2(0, 1)
    ])
    
    # 法线
    var normals = PackedVector3Array([
        Vector3.UP,
        Vector3.UP,
        Vector3.UP,
        Vector3.UP
    ])
    
    # 索引(两个三角形)
    var indices = PackedInt32Array([
        0, 1, 2,
        0, 2, 3
    ])
    
    surface_array[Mesh.ARRAY_VERTEX] = vertices
    surface_array[Mesh.ARRAY_TEX_UV] = uvs
    surface_array[Mesh.ARRAY_NORMAL] = normals
    surface_array[Mesh.ARRAY_INDEX] = indices
    
    var array_mesh = ArrayMesh.new()
    array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array)
    
    return array_mesh

23.5 环境设置

23.5.1 WorldEnvironment

extends Node3D

func setup_environment():
    var world_env = WorldEnvironment.new()
    var env = Environment.new()
    
    # 背景
    env.background_mode = Environment.BG_SKY
    
    # 创建天空
    var sky = Sky.new()
    var sky_material = ProceduralSkyMaterial.new()
    sky_material.sky_top_color = Color(0.4, 0.6, 0.9)
    sky_material.sky_horizon_color = Color(0.7, 0.8, 0.9)
    sky_material.ground_bottom_color = Color(0.2, 0.2, 0.2)
    sky_material.ground_horizon_color = Color(0.5, 0.5, 0.5)
    sky.sky_material = sky_material
    env.sky = sky
    
    # 环境光
    env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
    env.ambient_light_energy = 0.5
    
    # 色调映射
    env.tonemap_mode = Environment.TONE_MAPPER_ACES
    env.tonemap_exposure = 1.0
    
    world_env.environment = env
    add_child(world_env)

23.5.2 后处理效果

func setup_post_processing(env: Environment):
    # 辉光(Glow)
    env.glow_enabled = true
    env.glow_intensity = 0.5
    env.glow_bloom = 0.3
    env.glow_blend_mode = Environment.GLOW_BLEND_MODE_ADDITIVE
    
    # 屏幕空间反射(SSR)
    env.ssr_enabled = true
    env.ssr_max_steps = 64
    
    # 屏幕空间环境光遮蔽(SSAO)
    env.ssao_enabled = true
    env.ssao_radius = 1.0
    env.ssao_intensity = 2.0
    
    # 景深(DOF)
    env.dof_blur_far_enabled = true
    env.dof_blur_far_distance = 20.0
    env.dof_blur_far_transition = 5.0
    
    # 雾效
    env.fog_enabled = true
    env.fog_light_color = Color(0.5, 0.6, 0.7)
    env.fog_density = 0.01
    
    # 体积雾
    env.volumetric_fog_enabled = true
    env.volumetric_fog_density = 0.05

23.6 3D编辑器工具

23.6.1 视口导航

3D视口操作:
├── 旋转视角:鼠标中键拖动
├── 平移视角:Shift + 鼠标中键
├── 缩放:鼠标滚轮
├── 聚焦选中:F键
├── 切换视图:
│   ├── 小键盘1:前视图
│   ├── 小键盘3:右视图
│   ├── 小键盘7:顶视图
│   ├── 小键盘5:正交/透视切换
│   └── 小键盘0:相机视图
└── 自由导航:Shift + F(WASD移动)

23.6.2 变换工具

变换工具快捷键:
├── Q:选择模式
├── W:移动模式
├── E:旋转模式
├── R:缩放模式
├── T:显示/隐藏变换Gizmo
│
├── 轴向约束:
│   ├── X/Y/Z:约束到对应轴
│   ├── Shift + X/Y/Z:排除对应轴
│   └── 数字输入:精确值
│
└── 对齐选项:
    ├── 本地/全局坐标切换
    ├── 吸附网格
    └── 使用表面吸附

23.6.3 Gizmo设置

# 在编辑器中自定义Gizmo
# 通过EditorPlugin实现

# 项目设置中调整:
# Editor → 3D → Navigation
# - Orbit Sensitivity
# - Translation Sensitivity
# - Zoom Speed

23.7 场景组织

23.7.1 层级结构最佳实践

推荐的3D场景结构:
Root (Node3D)
├── Environment
│   ├── WorldEnvironment
│   ├── DirectionalLight3D (Sun)
│   └── Sky
├── Level
│   ├── Terrain
│   ├── Props
│   │   ├── Static (不动的物体)
│   │   └── Dynamic (可互动的物体)
│   └── Triggers
├── Entities
│   ├── Player
│   ├── Enemies
│   └── NPCs
├── Systems
│   ├── SpawnManager
│   └── AudioManager
└── UI (CanvasLayer)
    ├── HUD
    └── Menus

23.7.2 场景模块化

# 将场景拆分为可复用的子场景

# prop_barrel.tscn
# - Barrel (StaticBody3D)
#   - MeshInstance3D
#   - CollisionShape3D

# 在主场景中实例化
func spawn_barrels():
    var barrel_scene = preload("res://scenes/props/barrel.tscn")
    var positions = [
        Vector3(0, 0, 0),
        Vector3(2, 0, 0),
        Vector3(4, 0, 0)
    ]
    
    for pos in positions:
        var barrel = barrel_scene.instantiate()
        barrel.position = pos
        $Level/Props/Static.add_child(barrel)

23.8 实际案例

23.8.1 简单3D场景搭建器

# scene_builder.gd
class_name SceneBuilder3D
extends Node3D

@export var ground_size: Vector2 = Vector2(50, 50)
@export var ambient_color: Color = Color(0.3, 0.3, 0.4)

func _ready():
    build_scene()

func build_scene():
    _setup_environment()
    _setup_lighting()
    _setup_camera()
    _setup_ground()

func _setup_environment():
    var world_env = WorldEnvironment.new()
    var env = Environment.new()
    
    # 程序化天空
    var sky = Sky.new()
    var sky_mat = ProceduralSkyMaterial.new()
    sky.sky_material = sky_mat
    env.sky = sky
    env.background_mode = Environment.BG_SKY
    
    # 环境光
    env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
    env.ambient_light_color = ambient_color
    
    world_env.environment = env
    add_child(world_env)

func _setup_lighting():
    # 主光源(太阳)
    var sun = DirectionalLight3D.new()
    sun.name = "Sun"
    sun.rotation_degrees = Vector3(-45, 30, 0)
    sun.light_energy = 1.0
    sun.shadow_enabled = true
    add_child(sun)

func _setup_camera():
    var camera = Camera3D.new()
    camera.name = "MainCamera"
    camera.position = Vector3(0, 10, 20)
    camera.look_at(Vector3.ZERO)
    camera.make_current()
    add_child(camera)

func _setup_ground():
    var ground = MeshInstance3D.new()
    ground.name = "Ground"
    
    var mesh = PlaneMesh.new()
    mesh.size = ground_size
    ground.mesh = mesh
    
    # 材质
    var material = StandardMaterial3D.new()
    material.albedo_color = Color(0.4, 0.5, 0.3)
    ground.material_override = material
    
    # 碰撞
    var static_body = StaticBody3D.new()
    var collision = CollisionShape3D.new()
    var shape = BoxShape3D.new()
    shape.size = Vector3(ground_size.x, 0.1, ground_size.y)
    collision.shape = shape
    collision.position.y = -0.05
    static_body.add_child(collision)
    ground.add_child(static_body)
    
    add_child(ground)

# 工具方法
func spawn_primitive(type: String, pos: Vector3) -> MeshInstance3D:
    var instance = MeshInstance3D.new()
    
    match type:
        "box":
            instance.mesh = BoxMesh.new()
        "sphere":
            instance.mesh = SphereMesh.new()
        "cylinder":
            instance.mesh = CylinderMesh.new()
        "capsule":
            instance.mesh = CapsuleMesh.new()
    
    instance.position = pos
    add_child(instance)
    return instance

23.8.2 场景预览器

# scene_previewer.gd
extends Node3D

@export var rotate_speed: float = 0.5
@export var zoom_speed: float = 2.0
@export var min_distance: float = 2.0
@export var max_distance: float = 20.0

@onready var camera: Camera3D = $Camera3D
@onready var pivot: Node3D = $Pivot

var current_distance: float = 10.0
var is_rotating: bool = false

func _ready():
    camera.position.z = current_distance

func _unhandled_input(event: InputEvent):
    if event is InputEventMouseButton:
        if event.button_index == MOUSE_BUTTON_MIDDLE:
            is_rotating = event.pressed
        elif event.button_index == MOUSE_BUTTON_WHEEL_UP:
            zoom(-zoom_speed)
        elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
            zoom(zoom_speed)
    
    elif event is InputEventMouseMotion and is_rotating:
        pivot.rotate_y(-event.relative.x * rotate_speed * 0.01)
        pivot.rotate_object_local(Vector3.RIGHT, -event.relative.y * rotate_speed * 0.01)

func zoom(amount: float):
    current_distance = clamp(current_distance + amount, min_distance, max_distance)
    camera.position.z = current_distance

func focus_on(target: Node3D):
    pivot.global_position = target.global_position

本章小结

本章介绍了Godot 3D开发的基础知识:

  1. 3D与2D区别:坐标系、渲染管线、核心概念
  2. 3D场景结构:典型层级、代码创建
  3. Node3D基础:变换属性、Transform3D、方向控制
  4. 基本几何体:内置网格、程序化网格
  5. 环境设置:WorldEnvironment、后处理效果
  6. 编辑器工具:视口导航、变换工具
  7. 场景组织:层级结构、模块化设计
  8. 实际案例:场景搭建器、预览器

下一章将深入学习各种3D节点的详细用法。


上一章:2D游戏实战案例

下一章:3D节点详解

← 返回目录