中级OpenGL教程 010:Object 类设计与模型矩阵完全实现
- [Bilibili 同步视频](#Bilibili 同步视频)
- [一、Object 类:3D 物体的「空间管理者」](#一、Object 类:3D 物体的「空间管理者」)
-
- [1.1 文件结构与基础配置](#1.1 文件结构与基础配置)
- [二、成员变量设计:protected 权限的精妙选择](#二、成员变量设计:protected 权限的精妙选择)
-
- [2.1 三大核心变换变量](#2.1 三大核心变换变量)
- [三、旋转逻辑:本地坐标系 + Unity 标准顺序](#三、旋转逻辑:本地坐标系 + Unity 标准顺序)
-
- [3.1 本地坐标系旋转](#3.1 本地坐标系旋转)
- [3.2 旋转顺序:严格遵循 Unity 引擎标准](#3.2 旋转顺序:严格遵循 Unity 引擎标准)
- [四、接口封装:简洁易用的变换 API](#四、接口封装:简洁易用的变换 API)
-
- [4.1 核心接口声明](#4.1 核心接口声明)
- [4.2 接口实现(object.cpp)](#4.2 接口实现(object.cpp))
- 五、模型矩阵计算:渲染管线的「核心输出」
-
- [5.1 矩阵计算完整实现](#5.1 矩阵计算完整实现)
- [5.2 关键细节勘误(必看)](#5.2 关键细节勘误(必看))
- [六、Object 类的价值与扩展方向](#六、Object 类的价值与扩展方向)
-
- [6.1 核心价值](#6.1 核心价值)
- [6.2 扩展方向](#6.2 扩展方向)
- 结语
Bilibili 同步视频
在 3D 图形渲染的世界里,一切可见元素都可被抽象为「物体」------ 无论是场景中的模型、角色、道具,还是粒子特效,都需要一套统一的框架来管理其空间变换。Object 类 正是这套框架的核心,它封装了物体的位移、旋转、缩放三大基础变换,最终输出渲染管线必需的模型矩阵(Model Matrix),为后续 MVP 矩阵计算与片元着色打下坚实基础。
本文将从零拆解 Object 类的设计思路、成员变量定义、接口封装与模型矩阵计算逻辑,搭配可直接落地的 C++ 代码实现,帮你快速搭建 3D 引擎的物体管理模块✨。
一、Object 类:3D 物体的「空间管理者」
Object 类的核心使命只有一个:管理物体的空间变换,输出标准模型矩阵 。
它是所有可渲染物体的基类,场景中的模型、相机、灯光等实体,都可继承此类复用变换逻辑。因此在设计时,权限控制、变量封装、接口易用性是三大关键原则。
1.1 文件结构与基础配置
我们将 Object 类置于 GL framework 核心模块,方便全工程复用,创建两个核心文件:
-
object.h:类定义、变量声明、接口声明 -
object.cpp:函数实现、矩阵计算逻辑
头文件必须添加 #pragma once 预编译指令,杜绝头文件二次编译,避免重复定义报错;同时引入核心依赖库(GLM 数学库、框架核心文件),为矩阵运算、向量操作提供支撑。
cpp
// object.h 基础配置
#pragma once
#include "core.h" // 包含 GLM 数学库、框架核心工具
二、成员变量设计:protected 权限的精妙选择
物体的空间变换,本质是位置、旋转、缩放 三组数据的组合。
在权限设计上,我们放弃 private,选择 protected 修饰所有成员变量 ------ 这是为了让子类能直接访问父类变换数据,保证继承体系的灵活性(比如模型类、角色类可直接修改位置、旋转角度)。
2.1 三大核心变换变量
-
位置变量:m_position
记录物体在世界坐标系中的坐标,用
glm::vec3存储,初始化为(0.0f, 0.0f, 0.0f),代表物体默认位于世界原点。 -
旋转变量:m_angle_x /m_angle_y/m_angle_z
采用欧拉角旋转方案,分别记录物体绕 X、Y、Z 本地轴的旋转角度,这是游戏 / 引擎中最通用的旋转方式:
-
Pitch(绕 X 轴):上下俯仰(类比「点头」)
-
Yaw(绕 Y 轴):左右转向(类比「摇头」)
-
Roll(绕 Z 轴):滚筒旋转(类比「滚床单」)
-
-
缩放变量:m_scale
用
glm::vec3存储物体在 X、Y、Z 轴的缩放比例,默认值必须为 1.0f(代表无缩放),若设为 0 会导致物体完全消失。
cpp
// object.h 成员变量定义
class Object {
protected:
// 1. 位置:世界坐标系坐标
glm::vec3 m_position{ 0.0f, 0.0f, 0.0f };
// 2. 旋转:欧拉角(绕本地X/Y/Z轴)
float m_angle_x{ 0.0f };
float m_angle_y{ 0.0f };
float m_angle_z{ 0.0f };
// 3. 缩放:三轴缩放比例
glm::vec3 m_scale{ 1.0f, 1.0f, 1.0f };
public:
// 后续接口声明...
};
三、旋转逻辑:本地坐标系 + Unity 标准顺序
旋转是 3D 变换中最易出错的环节,核心要解决两个问题:基于什么坐标系旋转?旋转的先后顺序是什么?
3.1 本地坐标系旋转
我们采用本地坐标系(局部坐标系) 旋转规则:
物体的旋转始终围绕自身当前的坐标轴 ,而非世界坐标系坐标轴。
比如物体先绕 X 轴旋转 30°,再绕 Z 轴旋转时,Z 轴是物体旋转后的本地 Z 轴,而非世界 Z 轴 ------ 这符合现实中物体的运动直觉(如飞机、汽车的运动)。
3.2 旋转顺序:严格遵循 Unity 引擎标准
为保证旋转结果可预期,固定旋转顺序 至关重要,我们直接沿用工业级标准(Unity 引擎):
先 Pitch(X 轴)→ 再 Yaw(Y 轴)→ 最后 Roll(Z 轴)
此顺序能最大程度避免万向锁问题,适配绝大多数 3D 场景需求。
四、接口封装:简洁易用的变换 API
Object 类对外提供极简接口,支持设置位置、增量旋转、设置缩放三大操作,区分「赋值式设置」与「增量式修改」,符合引擎开发习惯。
4.1 核心接口声明
cpp
// object.h 公共接口
public:
Object() = default;
~Object() = default;
// 1. 设置位置:赋值式(直接覆盖世界坐标)
void setPosition(const glm::vec3& position);
// 2. 增量旋转:在原有角度基础上累加
void rotateX(float angle);
void rotateY(float angle);
void rotateZ(float angle);
// 3. 设置缩放:赋值式(直接覆盖三轴缩放)
void setScale(const glm::vec3& scale);
// 4. 核心:计算并返回模型矩阵
glm::mat4 getModelMatrix();
4.2 接口实现(object.cpp)
旋转接口采用增量累加 逻辑,每调用一次 rotateX,就在原有角度上叠加新角度,适配帧更新的旋转逻辑;位置与缩放为直接赋值,保证精准控制。
cpp
// object.cpp 接口实现
#include "object.h"
// 设置位置
void Object::setPosition(const glm::vec3& position) {
m_position = position;
}
// 增量旋转 X 轴
void Object::rotateX(float angle) {
m_angle_x += angle;
}
// 增量旋转 Y 轴
void Object::rotateY(float angle) {
m_angle_y += angle;
}
// 增量旋转 Z 轴
void Object::rotateZ(float angle) {
m_angle_z += angle;
}
// 设置缩放
void Object::setScale(const glm::vec3& scale) {
m_scale = scale;
}
五、模型矩阵计算:渲染管线的「核心输出」
getModelMatrix() 是 Object 类的灵魂函数,它将位移、旋转、缩放三组离散数据,组合为渲染管线必需的模型矩阵 ,变换顺序严格遵循 Unity 标准:
先缩放 → 再旋转 → 最后平移
⚠️ 关键规则:平移必须在最后,且基于世界坐标系执行,保证物体最终定位到指定世界坐标。
5.1 矩阵计算完整实现
cpp
// object.cpp 模型矩阵计算
glm::mat4 Object::getModelMatrix() {
// 1. 初始化单位矩阵
glm::mat4 transform = glm::mat4(1.0f);
// 2. 第一步:缩放变换(本地坐标系)
transform = glm::scale(transform, m_scale);
// 3. 第二步:旋转变换(本地坐标系,严格按 X→Y→Z 顺序)
// 旋转需转弧度:GLM 仅支持弧度运算,角度必须转换!
transform = glm::rotate(transform, glm::radians(m_angle_x), glm::vec3(1.0f, 0.0f, 0.0f));
transform = glm::rotate(transform, glm::radians(m_angle_y), glm::vec3(0.0f, 1.0f, 0.0f));
transform = glm::rotate(transform, glm::radians(m_angle_z), glm::vec3(0.0f, 0.0f, 1.0f));
// 4. 第三步:平移变换(世界坐标系,单独计算后相乘)
transform = glm::translate(glm::mat4(1.0f), m_position) * transform;
// 返回最终模型矩阵
return transform;
}
5.2 关键细节勘误(必看)
GLM 数学库的旋转函数仅支持弧度值 ,但我们设计的 m_angle_x/y/z 是角度值,必须用 glm::radians() 转换,否则旋转结果完全错误,这是 3D 开发高频踩坑点❗
六、Object 类的价值与扩展方向
6.1 核心价值
-
统一抽象:将所有 3D 物体的变换逻辑收敛到一个基类,减少重复代码
-
标准输出:输出合规模型矩阵,无缝对接渲染管线
-
易于继承:子类可快速扩展(如 Model 类添加网格数据、Light 类添加光照参数)
6.2 扩展方向
-
新增
resetTransform()重置所有变换 -
添加欧拉角转四元数,解决万向锁问题
-
支持父物体 / 子物体的父子坐标系变换
结语
Object 类是 3D 渲染框架的「最小可用单元」,它没有复杂的逻辑,却承载了物体空间变换的核心能力。从变量权限设计、旋转规则制定,到模型矩阵计算,每一步都遵循工业级引擎标准,代码简洁、可直接落地到 OpenGL/ES 3D 项目中。

掌握 Object 类,你就掌握了 3D 场景中物体运动的底层逻辑,后续再扩展相机、光照、材质等模块,都会变得水到渠成🌊。