上一篇:渲染目标和可配置渲染通道 | 下一篇:渲染视图系统 | 返回目录
📚 快速导航
目录
📖 简介
在之前的教程中,我们一直在硬编码相机的视图和投影矩阵。这限制了我们的灵活性 - 无法轻松改变视角、切换相机,或实现相机动画。
本教程将实现一个完整的相机系统 (Camera System),提供:
- 灵活的相机组件
- 多种投影模式(透视/正交)
- 相机管理和切换
- 常见的相机控制模式
Camera Controls 相机控制 Camera Component 相机组件 Camera System 相机系统 First Person
第一人称 Orbit
轨道 Free Fly
自由飞行 Transform
位置+旋转 Projection
投影参数 View Matrix
视图矩阵 Projection Matrix
投影矩阵 Camera System
相机系统 Camera 1
主相机 Camera 2
UI 相机 Camera 3
调试相机
为什么需要相机系统?
硬编码相机 (旧方式):
┌────────────────────────────────────┐
│ mat4 view = mat4_look_at(...); │
│ mat4 proj = mat4_perspective(...); │
│ │
│ 问题: │
│ ✗ 参数分散在代码中 │
│ ✗ 无法保存/加载相机配置 │
│ ✗ 难以实现相机切换 │
│ ✗ 无法管理多个相机 │
└────────────────────────────────────┘
相机系统 (新方式):
┌────────────────────────────────────┐
│ camera* cam = camera_system_get_default(); │
│ mat4 view = camera_get_view(cam); │
│ mat4 proj = camera_get_projection(cam); │
│ │
│ 优点: │
│ ✓ 相机是独立对象 │
│ ✓ 统一管理和配置 │
│ ✓ 轻松切换和动画 │
│ ✓ 支持多相机场景 │
└────────────────────────────────────┘
🎯 学习目标
| 目标 | 描述 |
|---|---|
| 理解相机概念 | 掌握视图变换和投影变换 |
| 实现相机组件 | 创建独立的相机对象 |
| 构建相机系统 | 管理多个相机 |
| 实现投影矩阵 | 透视和正交投影 |
| 相机控制 | 第一人称、轨道等控制模式 |
🎥 相机基础概念
什么是相机
在 3D 图形中,相机定义了观察者的视角:
相机的作用:
┌────────────────────────────────────┐
│ World Space (世界空间) │
│ │
│ 🎄 Tree │
│ (10, 0, 5) │
│ │
│ 🏠 House │
│ (0, 0, 0) │
│ │
│ 📷 Camera │
│ Position: (5, 2, 10) │
│ LookAt: (0, 0, 0) │
└──────────────┬─────────────────────┘
│ View Transform
▼
┌────────────────────────────────────┐
│ View Space (视图空间) │
│ │
│ 🎄 Tree │
│ (-5, -2, -5) │
│ │
│ 🏠 House │
│ (-5, -2, -10) │
│ │
│ 📷 Camera │
│ (0, 0, 0) ← 原点 │
└──────────────┬─────────────────────┘
│ Projection Transform
▼
┌────────────────────────────────────┐
│ Clip Space (裁剪空间) │
│ [-1, 1] × [-1, 1] × [0, 1] │
└────────────────────────────────────┘
相机空间变换
完整的相机变换管线:
glsl
// Vertex Shader
vec4 world_pos = model * vec4(in_position, 1.0);
vec4 view_pos = view * world_pos; // 视图变换
vec4 clip_pos = projection * view_pos; // 投影变换
gl_Position = clip_pos;
变换矩阵:
| 矩阵 | 作用 | 输入空间 | 输出空间 |
|---|---|---|---|
| Model | 物体变换 | 模型空间 | 世界空间 |
| View | 相机变换 | 世界空间 | 视图空间 |
| Projection | 投影变换 | 视图空间 | 裁剪空间 |
MVP 矩阵:
c
// 组合矩阵 (从右到左)
mat4 mvp = projection * view * model;
// Vertex Shader
gl_Position = mvp * vec4(in_position, 1.0);
投影类型
两种主要的投影模式:
1. 透视投影 (Perspective):
透视投影 (有深度感):
╱│╲
╱ │ ╲
╱ │ ╲ ← 视锥体
╱ │ ╲
╱ │ ╲
╱─────┼─────╲
📷 Camera Far Plane
特点:
• 远处物体看起来更小
• 有灭点 (Vanishing Point)
• 适合 3D 场景
使用场景:
✓ 第一人称游戏
✓ 第三人称游戏
✓ 3D 场景渲染
2. 正交投影 (Orthographic):
正交投影 (无深度感):
┌──────────┐
│ │
│ │ ← 盒子
│ │
│ │
└──────────┘
📷 Camera
特点:
• 远近物体大小相同
• 无灭点
• 适合 2D 渲染
使用场景:
✓ UI 渲染
✓ 2D 游戏
✓ CAD/建模工具
✓ 地图/小地图
📐 相机组件
相机数据结构
完整的相机组件定义:
c
// engine/src/renderer/camera.h
typedef enum camera_projection_type {
CAMERA_PROJECTION_PERSPECTIVE = 0,
CAMERA_PROJECTION_ORTHOGRAPHIC
} camera_projection_type;
typedef struct camera {
u32 id; // 相机 ID
char name[256]; // 相机名称
// 变换
vec3 position; // 位置
vec3 euler_rotation; // 欧拉角旋转 (pitch, yaw, roll)
// 投影参数
camera_projection_type projection_type;
union {
struct {
f32 fov; // 视野角 (Field of View,度数)
f32 aspect_ratio; // 宽高比
f32 near_clip; // 近裁剪面
f32 far_clip; // 远裁剪面
} perspective;
struct {
f32 left, right; // 左右边界
f32 bottom, top; // 上下边界
f32 near_clip, far_clip; // 近远裁剪面
} orthographic;
};
// 缓存的矩阵
mat4 view; // 视图矩阵
mat4 projection; // 投影矩阵
// Dirty 标记
b8 is_dirty; // 是否需要重新计算矩阵
} camera;
设计要点:
- Union:透视和正交参数互斥,节省内存
- 矩阵缓存:避免每帧重新计算
- Dirty 标记:仅在参数改变时重新计算
相机创建和销毁
c
// engine/src/renderer/camera.c
/**
* @brief 创建透视相机
*/
camera* camera_create_perspective(
vec3 position,
f32 fov,
f32 aspect_ratio,
f32 near_clip,
f32 far_clip
) {
camera* cam = kallocate(sizeof(camera), MEMORY_TAG_CAMERA);
cam->position = position;
cam->euler_rotation = (vec3){0, 0, 0};
cam->projection_type = CAMERA_PROJECTION_PERSPECTIVE;
cam->perspective.fov = fov;
cam->perspective.aspect_ratio = aspect_ratio;
cam->perspective.near_clip = near_clip;
cam->perspective.far_clip = far_clip;
cam->is_dirty = true;
// 初始化矩阵
camera_update(cam);
return cam;
}
/**
* @brief 创建正交相机
*/
camera* camera_create_orthographic(
vec3 position,
f32 left, f32 right,
f32 bottom, f32 top,
f32 near_clip, f32 far_clip
) {
camera* cam = kallocate(sizeof(camera), MEMORY_TAG_CAMERA);
cam->position = position;
cam->euler_rotation = (vec3){0, 0, 0};
cam->projection_type = CAMERA_PROJECTION_ORTHOGRAPHIC;
cam->orthographic.left = left;
cam->orthographic.right = right;
cam->orthographic.bottom = bottom;
cam->orthographic.top = top;
cam->orthographic.near_clip = near_clip;
cam->orthographic.far_clip = far_clip;
cam->is_dirty = true;
camera_update(cam);
return cam;
}
/**
* @brief 销毁相机
*/
void camera_destroy(camera* cam) {
if (cam) {
kfree(cam, sizeof(camera), MEMORY_TAG_CAMERA);
}
}
相机变换
设置相机位置和旋转:
c
/**
* @brief 设置相机位置
*/
void camera_set_position(camera* cam, vec3 position) {
if (!vec3_compare(cam->position, position, 0.0001f)) {
cam->position = position;
cam->is_dirty = true;
}
}
/**
* @brief 设置相机旋转 (欧拉角,度数)
*/
void camera_set_rotation(camera* cam, vec3 euler_degrees) {
if (!vec3_compare(cam->euler_rotation, euler_degrees, 0.0001f)) {
cam->euler_rotation = euler_degrees;
cam->is_dirty = true;
}
}
/**
* @brief 平移相机
*/
void camera_translate(camera* cam, vec3 translation) {
cam->position = vec3_add(cam->position, translation);
cam->is_dirty = true;
}
/**
* @brief 旋转相机
*/
void camera_rotate(camera* cam, vec3 euler_delta_degrees) {
cam->euler_rotation = vec3_add(cam->euler_rotation, euler_delta_degrees);
cam->is_dirty = true;
}
🔍 视图矩阵
LookAt矩阵
最常用的视图矩阵构建方法:
c
/**
* @brief 创建 LookAt 视图矩阵
* @param position 相机位置
* @param target 观察目标点
* @param up 上方向 (通常是 (0, 1, 0))
*/
mat4 mat4_look_at(vec3 position, vec3 target, vec3 up) {
// 1. 计算相机坐标系的三个轴
vec3 forward = vec3_normalized(vec3_sub(target, position)); // Z 轴 (前)
vec3 right = vec3_normalized(vec3_cross(forward, up)); // X 轴 (右)
vec3 actual_up = vec3_cross(right, forward); // Y 轴 (上)
// 2. 构建旋转矩阵 (相机空间 → 世界空间的逆)
mat4 result = mat4_identity();
// 右向量
result.data[0] = right.x;
result.data[1] = actual_up.x;
result.data[2] = -forward.x; // OpenGL 使用右手坐标系,-Z 是前方
// 上向量
result.data[4] = right.y;
result.data[5] = actual_up.y;
result.data[6] = -forward.y;
// 前向量
result.data[8] = right.z;
result.data[9] = actual_up.z;
result.data[10] = -forward.z;
// 3. 平移部分 (负相机位置)
result.data[12] = -vec3_dot(right, position);
result.data[13] = -vec3_dot(actual_up, position);
result.data[14] = vec3_dot(forward, position);
return result;
}
LookAt 原理:
相机空间构建:
↑ Up (0, 1, 0)
│
│
───────●────── Right
╱│
╱ │
╱ │ Forward
╱ │
Target 📷 Camera
步骤:
1. Forward = normalize(target - position)
2. Right = normalize(cross(forward, up))
3. Up = cross(right, forward)
4. 构建旋转 + 平移矩阵
欧拉角相机
使用欧拉角 (Pitch, Yaw, Roll) 控制相机:
c
/**
* @brief 从欧拉角计算视图矩阵
*/
void camera_update_view_euler(camera* cam) {
// 1. 将欧拉角转换为弧度
f32 pitch = deg_to_rad(cam->euler_rotation.x);
f32 yaw = deg_to_rad(cam->euler_rotation.y);
f32 roll = deg_to_rad(cam->euler_rotation.z);
// 2. 计算前向量
vec3 forward;
forward.x = cosf(pitch) * sinf(yaw);
forward.y = sinf(pitch);
forward.z = cosf(pitch) * cosf(yaw);
forward = vec3_normalized(forward);
// 3. 计算目标点
vec3 target = vec3_add(cam->position, forward);
// 4. 使用 LookAt
cam->view = mat4_look_at(cam->position, target, (vec3){0, 1, 0});
}
欧拉角定义:
Pitch (俯仰): 绕 X 轴旋转
↑ (仰视)
📷
↓ (俯视)
Yaw (偏航): 绕 Y 轴旋转
← (左转)
📷
→ (右转)
Roll (翻滚): 绕 Z 轴旋转
↺ (逆时针)
📷
↻ (顺时针)
四元数相机
使用四元数避免万向锁:
c
/**
* @brief 从四元数计算视图矩阵
*/
void camera_update_view_quaternion(camera* cam, quat rotation) {
// 1. 将四元数转换为旋转矩阵
mat4 rotation_matrix = quat_to_mat4(rotation);
// 2. 提取前向量
vec3 forward = {
-rotation_matrix.data[8],
-rotation_matrix.data[9],
-rotation_matrix.data[10]
};
// 3. 计算目标点
vec3 target = vec3_add(cam->position, forward);
// 4. 提取上向量
vec3 up = {
rotation_matrix.data[4],
rotation_matrix.data[5],
rotation_matrix.data[6]
};
// 5. 使用 LookAt
cam->view = mat4_look_at(cam->position, target, up);
}
📊 投影矩阵
透视投影
实现透视投影矩阵:
c
/**
* @brief 创建透视投影矩阵
* @param fov_radians 垂直视野角 (弧度)
* @param aspect_ratio 宽高比 (width / height)
* @param near_clip 近裁剪面距离
* @param far_clip 远裁剪面距离
*/
mat4 mat4_perspective(f32 fov_radians, f32 aspect_ratio, f32 near_clip, f32 far_clip) {
f32 half_tan_fov = tanf(fov_radians * 0.5f);
mat4 result = {0}; // 零矩阵
result.data[0] = 1.0f / (aspect_ratio * half_tan_fov); // X 缩放
result.data[5] = 1.0f / half_tan_fov; // Y 缩放
result.data[10] = -((far_clip + near_clip) / (far_clip - near_clip)); // Z 映射
result.data[11] = -1.0f; // W = -Z (透视除法)
result.data[14] = -((2.0f * far_clip * near_clip) / (far_clip - near_clip)); // Z 偏移
return result;
}
透视投影参数:
FOV (Field of View 视野角):
┌──────────────────────────┐
│ ╱────╲ ← FOV = 90° │
│ ╱ ╲ (宽视野) │
│📷 │ │
└──────────────────────────┘
┌──────────────────────────┐
│ ╱──╲ ← FOV = 45° │
│ ╱ ╲ (窄视野) │
│ 📷 │ │
└──────────────────────────┘
推荐 FOV:
• 第一人称: 70-90°
• 第三人称: 60-75°
• 赛车游戏: 90-110°
Near/Far Clip:
┌────────────────────────────┐
│ Near Clip (0.1) │
│ ├───────┤ │
│ 📷 │
│ │
│ Far Clip (1000)│
│ ├──────────────┤
└────────────────────────────┘
推荐值:
• Near: 0.1 ~ 1.0
• Far: 100 ~ 10000
• Near/Far 比率尽量大
正交投影
实现正交投影矩阵:
c
/**
* @brief 创建正交投影矩阵
* @param left 左边界
* @param right 右边界
* @param bottom 底边界
* @param top 顶边界
* @param near_clip 近裁剪面
* @param far_clip 远裁剪面
*/
mat4 mat4_orthographic(f32 left, f32 right, f32 bottom, f32 top, f32 near_clip, f32 far_clip) {
mat4 result = mat4_identity();
f32 lr = 1.0f / (left - right);
f32 bt = 1.0f / (bottom - top);
f32 nf = 1.0f / (near_clip - far_clip);
result.data[0] = -2.0f * lr; // X 缩放
result.data[5] = -2.0f * bt; // Y 缩放
result.data[10] = 2.0f * nf; // Z 缩放
result.data[12] = (left + right) * lr; // X 平移
result.data[13] = (top + bottom) * bt; // Y 平移
result.data[14] = (far_clip + near_clip) * nf; // Z 平移
return result;
}
正交投影应用:
c
// UI 相机 (屏幕空间)
camera* ui_cam = camera_create_orthographic(
(vec3){0, 0, 0}, // 位置
0, 1920, // 左右 (屏幕宽度)
0, 1080, // 下上 (屏幕高度)
-100, 100 // 近远
);
// 2D 游戏相机
camera* game_2d_cam = camera_create_orthographic(
(vec3){0, 0, 0},
-10, 10, // 世界单位
-10, 10,
-1, 1
);
投影参数
设置和更新投影参数:
c
/**
* @brief 设置透视投影 FOV
*/
void camera_set_fov(camera* cam, f32 fov_degrees) {
if (cam->projection_type == CAMERA_PROJECTION_PERSPECTIVE) {
if (KABS(cam->perspective.fov - fov_degrees) > 0.001f) {
cam->perspective.fov = fov_degrees;
cam->is_dirty = true;
}
}
}
/**
* @brief 设置宽高比
*/
void camera_set_aspect_ratio(camera* cam, f32 aspect_ratio) {
if (cam->projection_type == CAMERA_PROJECTION_PERSPECTIVE) {
if (KABS(cam->perspective.aspect_ratio - aspect_ratio) > 0.001f) {
cam->perspective.aspect_ratio = aspect_ratio;
cam->is_dirty = true;
}
}
}
/**
* @brief 设置裁剪面
*/
void camera_set_clip_planes(camera* cam, f32 near_clip, f32 far_clip) {
if (cam->projection_type == CAMERA_PROJECTION_PERSPECTIVE) {
cam->perspective.near_clip = near_clip;
cam->perspective.far_clip = far_clip;
} else {
cam->orthographic.near_clip = near_clip;
cam->orthographic.far_clip = far_clip;
}
cam->is_dirty = true;
}
🎮 相机系统
系统架构
相机系统管理多个相机:
c
// engine/src/systems/camera_system.h
#define MAX_CAMERAS 256
typedef struct camera_system_config {
u32 max_camera_count; // 最大相机数量
} camera_system_config;
typedef struct camera_system_state {
camera_system_config config;
camera* cameras; // 相机数组
u32 camera_count; // 当前相机数量
camera* default_camera; // 默认相机
} camera_system_state;
系统初始化:
c
// engine/src/systems/camera_system.c
static camera_system_state* state = NULL;
/**
* @brief 初始化相机系统
*/
b8 camera_system_initialize(u64* memory_requirement, void* memory, camera_system_config config) {
if (config.max_camera_count == 0) {
KFATAL("camera_system_initialize: max_camera_count must be > 0");
return false;
}
// 计算内存需求
u64 struct_requirement = sizeof(camera_system_state);
u64 array_requirement = sizeof(camera) * config.max_camera_count;
*memory_requirement = struct_requirement + array_requirement;
if (!memory) {
return true; // 仅查询内存需求
}
// 分配状态
state = memory;
state->config = config;
state->cameras = (camera*)((u8*)memory + struct_requirement);
state->camera_count = 0;
// 创建默认相机
state->default_camera = camera_system_acquire("default");
camera_set_position(state->default_camera, (vec3){0, 0, 10});
return true;
}
相机管理
获取和释放相机:
c
/**
* @brief 获取相机 (如果不存在则创建)
*/
camera* camera_system_acquire(const char* name) {
// 1. 查找现有相机
for (u32 i = 0; i < state->camera_count; ++i) {
if (strings_equal(state->cameras[i].name, name)) {
return &state->cameras[i];
}
}
// 2. 创建新相机
if (state->camera_count >= state->config.max_camera_count) {
KERROR("camera_system_acquire: Maximum camera count reached");
return NULL;
}
camera* new_cam = &state->cameras[state->camera_count];
state->camera_count++;
// 初始化相机
string_ncopy(new_cam->name, name, 256);
new_cam->id = state->camera_count - 1;
// 默认为透视相机
new_cam->projection_type = CAMERA_PROJECTION_PERSPECTIVE;
new_cam->perspective.fov = 75.0f;
new_cam->perspective.aspect_ratio = 1920.0f / 1080.0f;
new_cam->perspective.near_clip = 0.1f;
new_cam->perspective.far_clip = 1000.0f;
new_cam->position = (vec3){0, 0, 0};
new_cam->euler_rotation = (vec3){0, 0, 0};
new_cam->is_dirty = true;
camera_update(new_cam);
KDEBUG("Camera acquired: %s (ID: %u)", name, new_cam->id);
return new_cam;
}
/**
* @brief 释放相机
*/
void camera_system_release(const char* name) {
for (u32 i = 0; i < state->camera_count; ++i) {
if (strings_equal(state->cameras[i].name, name)) {
// 移动后续相机
for (u32 j = i; j < state->camera_count - 1; ++j) {
state->cameras[j] = state->cameras[j + 1];
}
state->camera_count--;
KDEBUG("Camera released: %s", name);
return;
}
}
}
默认相机
提供默认相机:
c
/**
* @brief 获取默认相机
*/
camera* camera_system_get_default() {
if (state && state->default_camera) {
return state->default_camera;
}
KERROR("camera_system_get_default: No default camera");
return NULL;
}
/**
* @brief 更新所有相机
*/
void camera_system_update(f32 delta_time) {
for (u32 i = 0; i < state->camera_count; ++i) {
camera* cam = &state->cameras[i];
if (cam->is_dirty) {
camera_update(cam);
}
}
}
🕹️ 相机控制
第一人称控制
实现 FPS 风格的相机控制:
c
/**
* @brief 第一人称相机控制器
*/
typedef struct fps_camera_controller {
camera* camera;
f32 move_speed; // 移动速度
f32 look_speed; // 视角速度
f32 mouse_sensitivity;
} fps_camera_controller;
/**
* @brief 更新 FPS 相机
*/
void fps_camera_update(fps_camera_controller* controller, f32 delta_time) {
camera* cam = controller->camera;
// 1. 键盘移动
vec3 forward = camera_get_forward(cam);
vec3 right = camera_get_right(cam);
if (input_is_key_down(KEY_W)) {
vec3 move = vec3_mul_scalar(forward, controller->move_speed * delta_time);
camera_translate(cam, move);
}
if (input_is_key_down(KEY_S)) {
vec3 move = vec3_mul_scalar(forward, -controller->move_speed * delta_time);
camera_translate(cam, move);
}
if (input_is_key_down(KEY_A)) {
vec3 move = vec3_mul_scalar(right, -controller->move_speed * delta_time);
camera_translate(cam, move);
}
if (input_is_key_down(KEY_D)) {
vec3 move = vec3_mul_scalar(right, controller->move_speed * delta_time);
camera_translate(cam, move);
}
// 2. 鼠标视角
vec2 mouse_delta = input_get_mouse_delta();
vec3 rotation = cam->euler_rotation;
rotation.y += mouse_delta.x * controller->mouse_sensitivity; // Yaw
rotation.x += mouse_delta.y * controller->mouse_sensitivity; // Pitch
// 限制 Pitch (-89° ~ 89°)
rotation.x = KCLAMP(rotation.x, -89.0f, 89.0f);
camera_set_rotation(cam, rotation);
}
轨道相机
围绕目标旋转的相机:
c
/**
* @brief 轨道相机控制器
*/
typedef struct orbit_camera_controller {
camera* camera;
vec3 target; // 观察目标
f32 distance; // 距离目标的距离
f32 min_distance; // 最小距离
f32 max_distance; // 最大距离
f32 yaw; // 水平旋转
f32 pitch; // 垂直旋转
f32 orbit_speed; // 旋转速度
f32 zoom_speed; // 缩放速度
} orbit_camera_controller;
/**
* @brief 更新轨道相机
*/
void orbit_camera_update(orbit_camera_controller* controller, f32 delta_time) {
// 1. 鼠标拖拽旋转
if (input_is_button_down(BUTTON_RIGHT)) {
vec2 mouse_delta = input_get_mouse_delta();
controller->yaw -= mouse_delta.x * controller->orbit_speed;
controller->pitch -= mouse_delta.y * controller->orbit_speed;
// 限制 Pitch
controller->pitch = KCLAMP(controller->pitch, -89.0f, 89.0f);
}
// 2. 鼠标滚轮缩放
f32 scroll = input_get_mouse_wheel();
controller->distance -= scroll * controller->zoom_speed;
controller->distance = KCLAMP(controller->distance,
controller->min_distance,
controller->max_distance);
// 3. 计算相机位置
f32 yaw_rad = deg_to_rad(controller->yaw);
f32 pitch_rad = deg_to_rad(controller->pitch);
vec3 offset;
offset.x = controller->distance * cosf(pitch_rad) * sinf(yaw_rad);
offset.y = controller->distance * sinf(pitch_rad);
offset.z = controller->distance * cosf(pitch_rad) * cosf(yaw_rad);
vec3 position = vec3_add(controller->target, offset);
camera_set_position(controller->camera, position);
// 4. 看向目标
camera_look_at(controller->camera, controller->target);
}
自由飞行
无重力的自由飞行相机:
c
/**
* @brief 自由飞行相机控制器
*/
typedef struct freefly_camera_controller {
camera* camera;
f32 move_speed;
f32 boost_multiplier; // Shift 加速倍数
f32 look_speed;
} freefly_camera_controller;
/**
* @brief 更新自由飞行相机
*/
void freefly_camera_update(freefly_camera_controller* controller, f32 delta_time) {
camera* cam = controller->camera;
// 速度调整
f32 speed = controller->move_speed;
if (input_is_key_down(KEY_LSHIFT)) {
speed *= controller->boost_multiplier;
}
// 六自由度移动
vec3 forward = camera_get_forward(cam);
vec3 right = camera_get_right(cam);
vec3 up = (vec3){0, 1, 0}; // 世界上方向
// 前后
if (input_is_key_down(KEY_W)) {
camera_translate(cam, vec3_mul_scalar(forward, speed * delta_time));
}
if (input_is_key_down(KEY_S)) {
camera_translate(cam, vec3_mul_scalar(forward, -speed * delta_time));
}
// 左右
if (input_is_key_down(KEY_A)) {
camera_translate(cam, vec3_mul_scalar(right, -speed * delta_time));
}
if (input_is_key_down(KEY_D)) {
camera_translate(cam, vec3_mul_scalar(right, speed * delta_time));
}
// 上下
if (input_is_key_down(KEY_SPACE)) {
camera_translate(cam, vec3_mul_scalar(up, speed * delta_time));
}
if (input_is_key_down(KEY_LCONTROL)) {
camera_translate(cam, vec3_mul_scalar(up, -speed * delta_time));
}
// 鼠标视角
vec2 mouse_delta = input_get_mouse_delta();
vec3 rotation = cam->euler_rotation;
rotation.y += mouse_delta.x * controller->look_speed;
rotation.x += mouse_delta.y * controller->look_speed;
rotation.x = KCLAMP(rotation.x, -89.0f, 89.0f);
camera_set_rotation(cam, rotation);
}
🎮 实际应用
完整的游戏相机示例:
c
// game.h
typedef struct game_state {
camera* main_camera;
fps_camera_controller* camera_controller;
} game_state;
// game.c
b8 game_initialize(game* game_inst) {
game_state* state = (game_state*)game_inst->state;
// 创建主相机
state->main_camera = camera_system_acquire("main");
// 设置初始位置
camera_set_position(state->main_camera, (vec3){0, 2, 10});
camera_set_rotation(state->main_camera, (vec3){0, 0, 0});
// 创建控制器
state->camera_controller = kallocate(sizeof(fps_camera_controller), MEMORY_TAG_GAME);
state->camera_controller->camera = state->main_camera;
state->camera_controller->move_speed = 5.0f;
state->camera_controller->look_speed = 0.2f;
state->camera_controller->mouse_sensitivity = 0.1f;
return true;
}
b8 game_update(game* game_inst, f32 delta_time) {
game_state* state = (game_state*)game_inst->state;
// 更新相机控制器
fps_camera_update(state->camera_controller, delta_time);
// 更新相机系统
camera_system_update(delta_time);
return true;
}
b8 game_render(game* game_inst, f32 delta_time) {
game_state* state = (game_state*)game_inst->state;
// 获取相机矩阵
mat4 view = camera_get_view(state->main_camera);
mat4 projection = camera_get_projection(state->main_camera);
// 传递给渲染器
shader_set_uniform(shader, "view", &view);
shader_set_uniform(shader, "projection", &projection);
// 渲染场景
render_scene(state->scene);
return true;
}
❓ 常见问题
1. 如何避免万向锁?
问题:
c
// 欧拉角 Pitch = 90° 时出现万向锁
camera_set_rotation(cam, (vec3){90, 0, 0});
// Yaw 和 Roll 轴重合,失去一个自由度
解决方案:
c
// 方案 1: 限制 Pitch 角度
rotation.x = KCLAMP(rotation.x, -89.0f, 89.0f); // 避免 ±90°
// 方案 2: 使用四元数
typedef struct camera {
quat rotation; // 用四元数代替欧拉角
// ...
} camera;
2. 如何实现平滑相机移动?
线性插值 (Lerp):
c
/**
* @brief 平滑移动相机
*/
void camera_smooth_move_to(camera* cam, vec3 target_pos, f32 speed, f32 delta_time) {
f32 t = KMIN(speed * delta_time, 1.0f);
vec3 new_pos = vec3_lerp(cam->position, target_pos, t);
camera_set_position(cam, new_pos);
}
// 使用
vec3 target = {10, 2, 5};
camera_smooth_move_to(main_camera, target, 5.0f, delta_time);
弹簧阻尼:
c
typedef struct smooth_camera {
vec3 position;
vec3 velocity;
f32 damping; // 阻尼系数 (0.1 ~ 0.3)
} smooth_camera;
void smooth_camera_update(smooth_camera* sc, vec3 target, f32 delta_time) {
vec3 displacement = vec3_sub(target, sc->position);
vec3 spring_force = vec3_mul_scalar(displacement, 10.0f); // 弹簧力
vec3 damping_force = vec3_mul_scalar(sc->velocity, -sc->damping); // 阻尼力
vec3 acceleration = vec3_add(spring_force, damping_force);
sc->velocity = vec3_add(sc->velocity, vec3_mul_scalar(acceleration, delta_time));
sc->position = vec3_add(sc->position, vec3_mul_scalar(sc->velocity, delta_time));
}
3. 如何实现相机抖动效果?
简单抖动:
c
typedef struct camera_shake {
f32 intensity; // 强度
f32 duration; // 持续时间
f32 elapsed; // 已经过时间
} camera_shake;
void camera_shake_update(camera* cam, camera_shake* shake, f32 delta_time) {
if (shake->elapsed >= shake->duration) {
return; // 抖动结束
}
shake->elapsed += delta_time;
// 衰减强度
f32 t = shake->elapsed / shake->duration;
f32 current_intensity = shake->intensity * (1.0f - t);
// 随机偏移
vec3 offset;
offset.x = (random_float() - 0.5f) * 2.0f * current_intensity;
offset.y = (random_float() - 0.5f) * 2.0f * current_intensity;
offset.z = 0;
camera_translate(cam, offset);
}
// 触发抖动
camera_shake shake = {0};
shake.intensity = 0.5f;
shake.duration = 0.3f;
shake.elapsed = 0.0f;
4. 如何实现相机碰撞检测?
简单球体碰撞:
c
void camera_handle_collisions(camera* cam, vec3 desired_position, f32 radius) {
vec3 actual_position = desired_position;
// 检查与场景中所有碰撞体
for (u32 i = 0; i < collision_object_count; ++i) {
collision_object* obj = &collision_objects[i];
vec3 closest_point = find_closest_point_on_object(obj, desired_position);
f32 distance = vec3_distance(closest_point, desired_position);
if (distance < radius) {
// 碰撞!推开相机
vec3 push_dir = vec3_normalized(vec3_sub(desired_position, closest_point));
vec3 push = vec3_mul_scalar(push_dir, radius - distance);
actual_position = vec3_add(actual_position, push);
}
}
camera_set_position(cam, actual_position);
}
5. 如何优化相机矩阵计算?
Dirty 标记优化:
c
void camera_update(camera* cam) {
if (!cam->is_dirty) {
return; // 无需更新
}
// 更新视图矩阵
camera_update_view_euler(cam);
// 更新投影矩阵
if (cam->projection_type == CAMERA_PROJECTION_PERSPECTIVE) {
cam->projection = mat4_perspective(
deg_to_rad(cam->perspective.fov),
cam->perspective.aspect_ratio,
cam->perspective.near_clip,
cam->perspective.far_clip
);
} else {
cam->projection = mat4_orthographic(
cam->orthographic.left,
cam->orthographic.right,
cam->orthographic.bottom,
cam->orthographic.top,
cam->orthographic.near_clip,
cam->orthographic.far_clip
);
}
cam->is_dirty = false;
}
性能对比:
| 方式 | 每帧计算次数 | 性能 |
|---|---|---|
| 无缓存 | 每帧重新计算 | 慢 |
| 缓存 + Dirty | 仅在改变时计算 | 快 10-100x |
📝 练习
练习 1: 实现第三人称相机
任务: 创建跟随角色的第三人称相机。
c
typedef struct third_person_camera {
camera* camera;
transform* target; // 跟随目标
f32 distance; // 距离
f32 height_offset; // 高度偏移
f32 follow_speed; // 跟随速度
} third_person_camera;
void third_person_camera_update(third_person_camera* tpc, f32 delta_time) {
// 计算理想位置
vec3 target_pos = tpc->target->position;
vec3 target_forward = transform_get_forward(tpc->target);
vec3 ideal_pos = vec3_sub(target_pos, vec3_mul_scalar(target_forward, tpc->distance));
ideal_pos.y += tpc->height_offset;
// 平滑跟随
vec3 current_pos = tpc->camera->position;
vec3 new_pos = vec3_lerp(current_pos, ideal_pos, tpc->follow_speed * delta_time);
camera_set_position(tpc->camera, new_pos);
// 看向目标
vec3 look_target = vec3_add(target_pos, (vec3){0, 1.0f, 0}); // 看向目标的头部
camera_look_at(tpc->camera, look_target);
}
练习 2: 实现相机路径动画
任务: 沿预定义路径移动相机。
c
typedef struct camera_path {
vec3* waypoints; // 路径点
u32 waypoint_count;
f32 duration; // 总时长
} camera_path;
typedef struct camera_path_animator {
camera* camera;
camera_path* path;
f32 time; // 当前时间
b8 is_playing;
b8 loop;
} camera_path_animator;
void camera_path_animator_update(camera_path_animator* anim, f32 delta_time) {
if (!anim->is_playing) return;
anim->time += delta_time;
// 循环
if (anim->time >= anim->path->duration) {
if (anim->loop) {
anim->time = fmodf(anim->time, anim->path->duration);
} else {
anim->is_playing = false;
return;
}
}
// 计算当前段
f32 t = anim->time / anim->path->duration;
f32 segment_length = 1.0f / (anim->path->waypoint_count - 1);
u32 segment = (u32)(t / segment_length);
f32 segment_t = (t - segment * segment_length) / segment_length;
// Catmull-Rom 样条插值
vec3 p0 = anim->path->waypoints[KMAX(0, segment - 1)];
vec3 p1 = anim->path->waypoints[segment];
vec3 p2 = anim->path->waypoints[KMIN(segment + 1, anim->path->waypoint_count - 1)];
vec3 p3 = anim->path->waypoints[KMIN(segment + 2, anim->path->waypoint_count - 1)];
vec3 position = vec3_catmull_rom(p0, p1, p2, p3, segment_t);
camera_set_position(anim->camera, position);
// 切线方向
vec3 tangent = vec3_normalized(vec3_sub(p2, p1));
vec3 target = vec3_add(position, tangent);
camera_look_at(anim->camera, target);
}
练习 3: 实现多相机切换
任务: 支持在多个相机间平滑切换。
c
typedef struct camera_switcher {
camera* cameras[8];
u32 camera_count;
u32 active_index;
f32 blend_duration;
f32 blend_time;
b8 is_blending;
camera blend_from;
} camera_switcher;
void camera_switcher_switch_to(camera_switcher* cs, u32 index) {
if (index >= cs->camera_count) return;
cs->blend_from = *cs->cameras[cs->active_index]; // 保存当前状态
cs->active_index = index;
cs->is_blending = true;
cs->blend_time = 0.0f;
}
void camera_switcher_update(camera_switcher* cs, f32 delta_time) {
if (!cs->is_blending) return;
cs->blend_time += delta_time;
f32 t = KMIN(cs->blend_time / cs->blend_duration, 1.0f);
camera* target = cs->cameras[cs->active_index];
// 插值位置
vec3 pos = vec3_lerp(cs->blend_from.position, target->position, t);
camera_set_position(target, pos);
// 插值旋转 (四元数 Slerp)
quat from_rot = euler_to_quat(cs->blend_from.euler_rotation);
quat to_rot = euler_to_quat(target->euler_rotation);
quat blended_rot = quat_slerp(from_rot, to_rot, t);
vec3 euler = quat_to_euler(blended_rot);
camera_set_rotation(target, euler);
if (t >= 1.0f) {
cs->is_blending = false;
}
}
camera* camera_switcher_get_active(camera_switcher* cs) {
return cs->cameras[cs->active_index];
}
恭喜!你已经掌握了相机系统! 🎉
Tutorial written by 上手实验室