第 21 集通常是 Cherno 教程中从“固定画面”转向“动态交互”的关键点,也是将 M、V、P 三者真正融合进代码的时刻。

按照程序运行的时间线,我们可以把这套复杂的逻辑分为以下阶段:

1. 初始化阶段:建立“尺子” (P)#

在程序启动时,你先定义了投影矩阵 (Projection Matrix)

  • 动作glm::ortho(0.0f, 960.0f, 0.0f, 540.0f, -1.0f, 1.0f)
  • 目的:建立一套“像素坐标系”。它就像一把尺子,告诉 GPU:“以后我给你 这个数,你就把它当成屏幕的最右边。”
  • 状态:此时你还没决定看哪,也没决定物体在哪。

2. 空间布局阶段:摆放相机与物体 (V & M)#

在每一帧渲染开始前,你需要在 CPU 中计算当前的位置。

  • 视图矩阵 (View Matrix)
    • 动作glm::translate(glm::mat4(1.0f), glm::vec3(x, y, z))(通常是反向平移)。
    • 目的:模拟摄像机的移动。如果你想让相机往右移,代码里其实是把整个世界往左拽。
  • 模型矩阵 (Model Matrix)
    • 动作glm::translateglm::rotate
    • 目的:决定这个特定的物体(比如那个 logo)在世界里的具体坐标、旋转角度和缩放比例。

分工#

旋转、缩放、平移,这“三兄弟”通常全部都在 M(Model 矩阵)里完成。

虽然在数学上,你可以把它们放进任何矩阵,但在图形学的标准逻辑(即第 21 集所讲的逻辑)中,它们分工非常明确:

1. M (Model 矩阵) —— 物体自己的“属性”#

适用范围:旋转、缩放、平移(全占了)。

  • 平移 (Translation):你想把方块放到屏幕左边还是右边?
  • 旋转 (Rotation):你想让方块站着还是躺着?
  • 缩放 (Scaling):你想让方块变大还是变小?

核心逻辑:Model 矩阵负责定义物体在世界里的样子。一个 logo 无论它怎么转、怎么挪,那都是它作为一个“物体”的行为。

2. V (View 矩阵) —— 相机的“视角”#

适用范围:平移、旋转(极少用缩放)。

  • 平移:相机往前走,还是往后退。
  • 旋转:相机低头看,还是转过头去看。
  • 为什么不用缩放?:在现实生活中,相机本身是不会“变大变小”的。如果你想让画面里的东西变大,你要么挪近相机(平移),要么换个长焦镜头(修改 P 矩阵)。

核心逻辑:View 矩阵负责定义观察者的位置

3. P (Projection 矩阵) —— 镜头的“参数”#

适用范围:视野(FOV)、宽高比、远近裁剪。

  • 它不处理物体的旋转或平移,它只负责把 3D 空间里的物体投影到 2D 屏幕上。

3. 组合阶段:矩阵连乘 (MVP)#

这是第 21 集最核心的操作,在传给 Shader 之前,你在 C++ 里把它们乘在了一起。

  • 计算顺序proj * view * model(注意:必须是从左往右写的这个顺序,因为矩阵运算是从右向左生效的)。
  • 意义:这三者合并成了一个单一的 glm::mat4 矩阵,它包含了“物体在哪”、“相机在哪”和“投影范围”的所有信息。

例子#

		//宽高比:960:540即16:9
		glm::mat4 proj = glm::ortho(0.0f, 960.0f, 0.0f, 540.0f, -1.0f, 1.0f);
		
		//这里在原来的基础上,左移了100
		//即向右移动相机
		glm::mat4 view = glm::translate(glm::mat4(1.0f),
			glm::vec3(-100, 0, 0));

		glm::mat4 mvp = proj * view;
		//.....
		
		shader.SetUniformMat4f("u_MVP", mvp);

移动后:


		//宽高比:960:540即16:9
		glm::mat4 proj = glm::ortho(0.0f, 960.0f, 0.0f, 540.0f, -1.0f, 1.0f);
		//即向右移动相机
		glm::mat4 view = glm::translate(glm::mat4(1.0f),
			glm::vec3(-100, 0, 0));
		//向右向上移动200
		glm::mat4 model = glm::translate(glm::mat4(1.0f),
			glm::vec3(200, 200, 0));

		glm::mat4 mvp = proj * view * model;

		glm::vec4 vp(100.0f, 100.0f, 0.0f, 1.0f);
		glm::vec4 result = proj * vp * model;

4. 传输阶段:Uniform 传递#

  • 动作glUniformMatrix4fv(location, 1, GL_FALSE, &mvp[0][0])
  • 目的:把你在 CPU 里算好的这块“综合翻译表”送进显卡的内存。

5. GPU 执行阶段:顶点变换#

这是在 顶点着色器 (Vertex Shader) 内部发生的:

  • 代码gl_Position = u_MVP * position;

  • 时间轴位置

    1. 第一步 (Model):顶点从本地坐标变成世界坐标。
    2. 第二步 (View)世界坐标变成相对于你的观察坐标 这句很重要,解释了learnopengl中为何出现欧拉角等等概念
    3. 第三步 (Projection):观察坐标被“压缩”到 。
  • 结果:GPU 得到了最终的 NDC 坐标。

6. 光栅化阶段:视口变换 (Viewport)#

  • 时间点:顶点着色器执行完后,像素着色器执行前。
  • 动作:GPU 自动调用你设置的 glViewport
  • 目的:把你之前在 proj 里定义的比例映射回真正的 个像素格子里。

💡 核心总结:21 集教会了我们什么?#

步骤矩阵对应操作你的认知
第一步Model改变物体的位置“我想让方块出现在屏幕中间”
第二步View改变观察者的位置“我想让相机离远一点”
第三步Projection改变显示的范围“我想让这块屏幕代表 像素”

在这一集中,你可能已经通过 ImGui 做了滑动条来实时改变 Model 矩阵的平移值。当你拖动滑动条时,你其实是在实时更新时间线上的“第 2 阶段”,从而影响了最终生成的那个 u_MVP