第二十三章: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 核心概念对比
| 概念 | 2D | 3D |
|---|---|---|
| 基础节点 | Node2D | Node3D |
| 精灵 | Sprite2D | MeshInstance3D |
| 相机 | Camera2D | Camera3D |
| 物理体 | CharacterBody2D | CharacterBody3D |
| 光照 | PointLight2D | OmniLight3D |
| 碰撞 | CollisionShape2D | CollisionShape3D |
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开发的基础知识:
- 3D与2D区别:坐标系、渲染管线、核心概念
- 3D场景结构:典型层级、代码创建
- Node3D基础:变换属性、Transform3D、方向控制
- 基本几何体:内置网格、程序化网格
- 环境设置:WorldEnvironment、后处理效果
- 编辑器工具:视口导航、变换工具
- 场景组织:层级结构、模块化设计
- 实际案例:场景搭建器、预览器
下一章将深入学习各种3D节点的详细用法。
上一章:2D游戏实战案例
下一章:3D节点详解