Godot 4 GDScript 教程 / 3D 基础与网格
3D 基础与网格
概述
Godot 4 的 3D 系统基于 Vulkan 渲染器,支持高质量的 PBR 材质、全局光照和后处理。本节涵盖 3D 场景搭建、网格、材质、纹理和相机控制的基础知识。
| 核心节点 | 说明 |
|---|
Node3D | 3D 空间基类(位置、旋转、缩放) |
MeshInstance3D | 网格实例显示 |
Camera3D | 3D 相机 |
DirectionalLight3D | 方向光 |
WorldEnvironment | 环境设置 |
StaticBody3D | 静态碰撞体 |
Node3D 基础
extends Node3D
func _ready():
# 位置
position = Vector3(1, 2, 3)
global_position = Vector3(0, 5, 0)
# 旋转(弧度)
rotation = Vector3(0, deg_to_rad(90), 0)
rotation_degrees = Vector3(0, 90, 0)
# 缩放
scale = Vector3(2, 2, 2)
# 四元数旋转
var quat = Quaternion(Vector3.UP, deg_to_rad(45))
transform.basis = Basis(quat)
# Look At
look_at(Vector3(10, 0, 10), Vector3.UP)
# 平滑旋转
func _process(delta):
rotate_y(deg_to_rad(45) * delta) # 每秒转 45 度
坐标转换
extends Node3D
func _ready():
# 本地坐标 → 全局坐标
var global_pos = to_global(Vector3(1, 0, 0))
# 全局坐标 → 本地坐标
var local_pos = to_local(Vector3(5, 0, 5))
# 获取前方方向(-Z 轴)
var forward = -global_transform.basis.z
# 获取右方方向(X 轴)
var right = global_transform.basis.x
# 获取上方方向(Y 轴)
var up = global_transform.basis.y
MeshInstance3D
基本网格类型
extends Node3D
func _ready():
# 创建网格实例
var mesh_instance = MeshInstance3D.new()
# 盒子网格
var box = BoxMesh.new()
box.size = Vector3(1, 1, 1)
# 球体网格
var sphere = SphereMesh.new()
sphere.radius = 0.5
sphere.height = 1.0
# 圆柱体
var cylinder = CylinderMesh.new()
cylinder.top_radius = 0.5
cylinder.bottom_radius = 0.5
cylinder.height = 2.0
# 平面
var plane = PlaneMesh.new()
plane.size = Vector2(10, 10)
# 胶囊体
var capsule = CapsuleMesh.new()
capsule.radius = 0.3
capsule.height = 1.0
mesh_instance.mesh = sphere
add_child(mesh_instance)
内置网格类型速查表
| 网格类型 | 说明 |
|---|
BoxMesh | 盒子 |
SphereMesh | 球体 |
CylinderMesh | 圆柱体 |
CapsuleMesh | 胶囊体 |
PlaneMesh | 平面 |
PrismMesh | 三棱柱 |
TorusMesh | 圆环 |
TorusMesh | 圆环 |
PointMesh | 点 |
RibbonTrailMesh | 飘带 |
TubeTrailMesh | 管状尾迹 |
TextMesh | 3D 文字 |
3D 导入设置
glTF 导入(推荐)
glTF 2.0 是 Godot 推荐的 3D 格式,支持完整的场景、材质和动画。
# glTF 导入设置(在编辑器 Import 面板中)
# Meshes:
# - Generate Lightmap UV: 启用(需要烘焙光照时)
# - Ensure Tangents: 启用(法线贴图需要)
# Animation:
# - Import: 启用
# - FPS: 30
# Materials:
# - Material Import: Standard(默认 PBR 材质)
FBX 导入
# FBX 需要 FBX2glTF 工具转换
# Godot 4.2+ 内置 FBX 导入支持
# 推荐将 FBX 转换为 glTF 后导入
💡 提示:优先使用 glTF 格式(.glb/.gltf),它与 Godot 的兼容性最好。Blender 可直接导出 glTF。
extends MeshInstance3D
func _ready():
mesh = create_triangle()
func create_triangle() -> ArrayMesh:
var st = SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
# 设置法线和 UV
st.set_normal(Vector3(0, 0, 1))
st.set_uv(Vector2(0.5, 0))
st.add_vertex(Vector3(0, 1, 0))
st.set_normal(Vector3(0, 0, 1))
st.set_uv(Vector2(0, 1))
st.add_vertex(Vector3(-1, -1, 0))
st.set_normal(Vector3(0, 0, 1))
st.set_uv(Vector2(1, 1))
st.add_vertex(Vector3(1, -1, 0))
st.commit()
return st.commit() # 返回 ArrayMesh
使用 ArrayMesh 创建网格
extends MeshInstance3D
func _ready():
mesh = create_quad()
func create_quad() -> ArrayMesh:
var vertices = PackedVector3Array([
Vector3(-0.5, 0.5, 0), Vector3(-0.5, -0.5, 0),
Vector3(0.5, -0.5, 0), Vector3(0.5, 0.5, 0)
])
var uvs = PackedVector2Array([
Vector2(0, 0), Vector2(0, 1), Vector2(1, 1), Vector2(1, 0)
])
var normals = PackedVector3Array([
Vector3(0, 0, 1), Vector3(0, 0, 1), Vector3(0, 0, 1), Vector3(0, 0, 1)
])
var indices = PackedInt32Array([0, 1, 2, 0, 2, 3])
var arrays = []
arrays.resize(Mesh.ARRAY_MAX)
arrays[Mesh.ARRAY_VERTEX] = vertices
arrays[Mesh.ARRAY_TEX_UV] = uvs
arrays[Mesh.ARRAY_NORMAL] = normals
arrays[Mesh.ARRAY_INDEX] = indices
var array_mesh = ArrayMesh.new()
array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
return array_mesh
材质系统(StandardMaterial3D)
PBR 材质基础
extends MeshInstance3D
func _ready():
var mat = StandardMaterial3D.new()
# 基础颜色(Albedo)
mat.albedo_color = Color(0.8, 0.2, 0.2)
mat.albedo_texture = preload("res://textures/wood_albedo.png")
# 金属度
mat.metallic = 0.8
# 粗糙度
mat.roughness = 0.3
# 法线贴图
mat.normal_texture = preload("res://textures/wood_normal.png")
mat.normal_scale = 1.0
# 环境光遮蔽
mat.ao_texture = preload("res://textures/wood_ao.png")
# 自发光
mat.emission_enabled = true
mat.emission = Color(0.2, 0.8, 0.2)
mat.emission_energy_multiplier = 2.0
# 透明度
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
mat.albedo_color.a = 0.5
# 双面渲染
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
# 纹理过滤
mat.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST # 像素风格
mesh.material_override = mat
材质属性速查表
| 属性 | 说明 |
|---|
albedo_color | 基础颜色 |
albedo_texture | 基础纹理 |
metallic | 金属度(0~1) |
roughness | 粗糙度(0~1) |
normal_texture | 法线贴图 |
emission | 自发光颜色 |
emission_enabled | 启用自发光 |
transparency | 透明模式 |
cull_mode | 面剔除模式 |
uv1_scale | UV 缩放 |
uv1_offset | UV 偏移 |
texture_filter | 纹理过滤模式 |
proximity_fade_enabled | 近距离淡出 |
distance_fade_mode | 远距离淡出 |
纹理类型
| 纹理类型 | 用途 | 说明 |
|---|
CompressedTexture2D | 标准纹理 | PNG/WebP 导入 |
ImageTexture | 运行时创建 | 代码生成 |
NoiseTexture2D | 噪声纹理 | 地形、云 |
GradientTexture1D | 1D 渐变 | 颜色映射 |
GradientTexture2D | 2D 渐变 | 背景 |
ViewportTexture | 视口纹理 | 实时画面 |
CurveTexture | 曲线纹理 | 动画映射 |
ProxyTexture2D | 代理纹理 | 资源管理 |
运行时创建纹理
extends Node3D
func _ready():
var image = Image.create(256, 256, false, Image.FORMAT_RGBA8)
image.fill(Color(0.5, 0.5, 1.0)) # 蓝灰色
# 绘制像素
for x in range(256):
for y in range(256):
var noise_val = (sin(x * 0.1) + cos(y * 0.1)) * 0.5 + 0.5
image.set_pixel(x, y, Color(noise_val, noise_val, noise_val))
var texture = ImageTexture.create_from_image(image)
var mat = StandardMaterial3D.new()
mat.albedo_texture = texture
$MeshInstance3D.material_override = mat
3D 场景搭建
extends Node3D
func _ready():
setup_environment()
create_scene()
func setup_environment():
# 环境光
var env = Environment.new()
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
env.ambient_light_color = Color(0.1, 0.1, 0.15)
env.ambient_light_energy = 0.5
# 天空
var sky = Sky.new()
var sky_mat = ProceduralSkyMaterial.new()
sky_mat.sky_top_color = Color(0.3, 0.5, 0.8)
sky_mat.sky_horizon_color = Color(0.6, 0.7, 0.9)
sky.sky_material = sky_mat
env.sky = sky
# 色调映射
env.tonemap_mode = Environment.TONE_MAP_ACES
env.tonemap_exposure = 1.0
# SSAO
env.ssao_enabled = true
env.ssao_radius = 1.0
# 后处理
env.glow_enabled = true
env.glow_intensity = 0.3
var world_env = WorldEnvironment.new()
world_env.environment = env
add_child(world_env)
func create_scene():
# 地面
var ground = StaticBody3D.new()
var ground_mesh = MeshInstance3D.new()
var plane = PlaneMesh.new()
plane.size = Vector2(50, 50)
ground_mesh.mesh = plane
ground.add_child(ground_mesh)
var shape = CollisionShape3D.new()
var plane_shape = WorldBoundaryShape3D.new()
shape.shape = plane_shape
ground.add_child(shape)
add_child(ground)
第/三人称相机
第三人称相机
extends Camera3D
@export var target_path: NodePath
@export var distance: float = 5.0
@export var height: float = 3.0
@export var smooth_speed: float = 5.0
@onready var target: Node3D = get_node(target_path)
func _process(delta):
if not target:
return
var target_pos = target.global_position
var desired_pos = target_pos + Vector3(0, height, distance)
# 平滑跟随
global_position = global_position.lerp(desired_pos, smooth_speed * delta)
# 始终看向目标
look_at(target_pos + Vector3(0, 1.5, 0)) # 略高于目标脚底
第一人称相机
extends Camera3D
@export var mouse_sensitivity: float = 0.002
var rotation_x: float = 0.0
var rotation_y: float = 0.0
func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _input(event):
if event is InputEventMouseMotion:
rotation_y -= event.relative.x * mouse_sensitivity
rotation_x -= event.relative.y * mouse_sensitivity
rotation_x = clamp(rotation_x, deg_to_rad(-89), deg_to_rad(89))
rotation.y = rotation_y
rotation.x = rotation_x
if event.is_action_pressed("ui_cancel"):
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
3D 角色基础移动
extends CharacterBody3D
@export var speed: float = 5.0
@export var jump_velocity: float = 4.5
@export var mouse_sensitivity: float = 0.002
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
var rot_y: float = 0.0
@onready var camera: Camera3D = $CameraPivot/Camera3D
func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _input(event):
if event is InputEventMouseMotion:
rot_y -= event.relative.x * mouse_sensitivity
rotation.y = rot_y
camera.rotation.x -= event.relative.y * mouse_sensitivity
camera.rotation.x = clamp(camera.rotation.x, deg_to_rad(-80), deg_to_rad(80))
func _physics_process(delta):
if not is_on_floor():
velocity.y -= gravity * delta
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = jump_velocity
var input_dir = Input.get_vector("left", "right", "forward", "back")
var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction:
velocity.x = direction.x * speed
velocity.z = direction.z * speed
else:
velocity.x = move_toward(velocity.x, 0, speed)
velocity.z = move_toward(velocity.z, 0, speed)
move_and_slide()
游戏开发场景
| 场景 | 推荐方案 |
|---|
| 场景搭建 | MeshInstance3D + StaticBody3D |
| 角色模型 | glTF 导入 + Skeleton3D |
| 程序化地形 | SurfaceTool + HeightMap |
| 第一人称 | Camera3D + 鼠标输入 |
| 第三人称 | Camera3D + SpringArm3D |
| 天空盒 | WorldEnvironment + Sky |
⚠️ 常见陷阱
- 旋转使用弧度,
rotation_degrees 可用角度但内部转弧度 - glTF 是推荐格式,FBX 需要额外工具
- 法线贴图需要切线空间,导入时确保 “Ensure Tangents” 开启
- Mesh 的
material_override 会覆盖所有材质槽,用 set_surface_override_material() 覆盖单个 - Node3D 的缩放不要为 0,会导致子节点不可见
扩展阅读