摘要
投影变换是将三维世界转换为二维图像的过程,类似于将三维物体“拍成照片”显示在屏幕上。它分为正交投影和透视投影两种类型。正交投影将物体“压扁”到平面上,适合2D游戏和建筑设计;透视投影则模拟人眼视角,近大远小,适合3D游戏。在游戏中,投影变换用于渲染画面、生成角色影子、碰撞检测和UI显示。通过投影矩阵,游戏引擎将三维场景转换为二维图像,使玩家能在屏幕上看到立体的游戏世界。投影变换的核心在于将三维坐标通过数学公式转换为屏幕上的像素坐标,最终实现游戏画面的呈现。
一、什么是投影变换?(形象比喻)
1. 生活中的投影
想象你在阳光下站着,地上出现了你的影子。
- 你的影子就是你被“投影”到地面上的结果。
- 你本来是三维的,但影子只有二维。
2. 游戏里的投影
在游戏中,投影变换就像是把三维世界“拍成照片”,让它能显示在二维的屏幕上。
- 你看到的游戏画面,其实是三维场景经过投影变换后,变成二维图像。
二、投影变换的原理(生动解释)
1. 正交投影(Orthographic Projection)
比喻:
就像用尺子量物体,把它“压扁”到一张纸上。
- 不管远近,物体的大小都不会变形。
- 适合2D游戏、策略游戏、建筑设计等。
游戏应用:
- 俯视角策略游戏(如《红色警戒》)
- 2D横版游戏(如《马里奥》)
- 游戏地图编辑器
2. 透视投影(Perspective Projection)
比喻:
就像用相机拍照,近的大,远的小。
- 物体离你越远,看起来越小,产生“透视感”。
- 这是我们日常看到世界的方式。
游戏应用:
- 绝大多数3D游戏(如《我的世界》、《GTA》、《英雄联盟》3D视角)
- 让玩家有“身临其境”的感觉
三、投影变换在游戏中的具体应用
1. 渲染画面
- 游戏引擎会把三维场景里的每个物体,通过投影变换,计算出它在屏幕上的二维位置。
- 这样,玩家才能在显示器上看到三维世界的“照片”。
2. 角色影子
- 角色的影子其实就是把角色的形状“投影”到地面上。
- 这也是一种投影变换(通常是正交投影)。
3. 碰撞检测
- 有时需要把物体的位置投影到某个平面上,判断它们的相对位置,比如角色跳到平台上时,判断落点。
4. UI与2D元素
- 游戏中的血条、地图等2D元素,通常通过正交投影叠加在3D画面上。
四、简单公式(不用怕,形象理解)
-
正交投影:
只保留x、y坐标,z坐标直接丢掉(或者用来排序谁在前谁在后)。 -
透视投影:
物体的x、y坐标会除以z(深度),z越大,x、y越小,看起来就远了。
五、总结一句话
投影变换就像是把三维世界“拍成照片”,让你能在二维屏幕上看到立体的游戏世界。
- 正交投影像尺子量出来的平面图,适合2D和俯视角。
- 透视投影像相机拍出来的照片,适合3D和第一/第三人称视角。
我们继续深入,用具体的投影矩阵、代码实现,并结合游戏场景举例说明。
一、投影矩阵的原理
1. 透视投影矩阵(Perspective Projection)
作用:
让远处的物体看起来更小,近处的物体更大,产生真实的空间感。
常用参数:
- 视野角度(fov,field of view)
- 屏幕宽高比(aspect ratio)
- 近平面(near)
- 远平面(far)
二、代码实现(以Python+numpy为例)
1. 透视投影矩阵
import numpy as np
def perspective(fov, aspect, near, far):
f = 1.0 / np.tan(fov / 2)
m = np.zeros((4, 4))
m[0, 0] = f / aspect
m[1, 1] = f
m[2, 2] = (far + near) / (near - far)
m[2, 3] = (2 * far * near) / (near - far)
m[3, 2] = -1
return m
# 示例参数
fov = np.radians(60) # 60度视野
aspect = 16/9 # 宽高比
near = 0.1
far = 100.0
proj_matrix = perspective(fov, aspect, near, far)
print(proj_matrix)
2. 正交投影矩阵
def orthographic(left, right, bottom, top, near, far):
m = np.zeros((4, 4))
m[0, 0] = 2 / (right - left)
m[1, 1] = 2 / (top - bottom)
m[2, 2] = -2 / (far - near)
m[0, 3] = -(right + left) / (right - left)
m[1, 3] = -(top + bottom) / (top - bottom)
m[2, 3] = -(far + near) / (far - near)
m[3, 3] = 1
return m
# 示例参数
left, right, bottom, top, near, far = -10, 10, -10, 10, 0.1, 100
ortho_matrix = orthographic(left, right, bottom, top, near, far)
print(ortho_matrix)
三、游戏场景中的应用举例
1. 3D射击游戏(透视投影)
- 玩家视角就是用透视投影矩阵渲染的。
- 你看到远处的敌人变小,近处的枪很大,就是因为用了透视投影。
2. 2D横版游戏(正交投影)
- 画面不会因为角色远近而变大变小。
- 地图编辑器、迷宫游戏、塔防游戏等都用正交投影。
3. 小地图/雷达(正交投影)
- 3D场景中的小地图,通常用正交投影把场景“压扁”到一个平面上,显示在屏幕角落。
4. 角色影子(投影变换)
- 角色的影子可以通过把角色的顶点“投影”到地面平面上实现(通常是正交投影)。
四、流程总结
- 建模空间:物体在三维世界有自己的坐标。
- 视图变换:把世界坐标变换到摄像机坐标系。
- 投影变换:用投影矩阵把三维坐标变成二维屏幕坐标。
- 屏幕映射:最终显示在屏幕上。
五、形象一句话
投影矩阵就像一台“魔法相机”,决定你在游戏里看到的世界是“真实立体”还是“平面压扁”,而且一切都能用代码和数学公式来实现!
我们以一个具体的3D游戏场景为例,详细讲解顶点变换的全过程,让你清楚每一步数据是怎么“走”到屏幕上的。
一、场景设定
假设你在一个3D射击游戏里,场景中有一个箱子。我们要把箱子的一个顶点(比如左下角)从模型空间一路变换到屏幕上的像素坐标。
二、顶点变换的四大步骤
-
模型空间(Model Space)
—— 顶点在物体自身坐标系下的位置。 -
世界空间(World Space)
—— 顶点在整个游戏世界中的位置。 -
视图空间(View/Camera Space)
—— 顶点在摄像机(玩家视角)坐标系下的位置。 -
裁剪空间/标准化设备坐标(Clip/NDC Space)
—— 顶点经过投影矩阵变换,准备映射到屏幕。 -
屏幕空间(Screen Space)
—— 顶点最终在屏幕上的像素坐标。
三、每一步的数学变换
1. 模型空间 → 世界空间(模型变换)
- 用模型矩阵(Model Matrix)变换。
- 作用:把物体从自身坐标系放到世界坐标系。
P world =ModelMatrix×P model
例子:
箱子左下角顶点在模型空间是 (1, 0, 0, 1)。
箱子在世界中的位置是 (10, 0, 5),旋转了30度,缩放2倍。
这些信息组成了模型矩阵。
2. 世界空间 → 视图空间(视图变换)
- 用视图矩阵(View Matrix)变换。
- 作用:把世界坐标系变换到摄像机坐标系(玩家视角)。
P view =ViewMatrix×P world
例子:
玩家(摄像机)在 (0, 2, 10) 位置,朝向世界原点。
视图矩阵根据摄像机位置和朝向生成。
3. 视图空间 → 裁剪空间(投影变换)
- 用投影矩阵(Projection Matrix)变换。
- 作用:把三维坐标投影到二维平面,形成透视或正交效果。
P clip =ProjectionMatrix×P view
4. 裁剪空间 → NDC(标准化设备坐标)
- 这一步是自动完成的:
用齐次除法,把坐标除以w分量。
(x,y,z,w)→(x/w,y/w,z/w)
- 结果范围在 [-1, 1] 之间。
5. NDC → 屏幕空间
四、代码伪例(Python+numpy)
import numpy as np
# 1. 顶点在模型空间
P_model = np.array([1, 0, 0, 1])
# 2. 模型矩阵(假设已知)
ModelMatrix = ... # 4x4矩阵
# 3. 视图矩阵(摄像机位置和朝向生成)
ViewMatrix = ... # 4x4矩阵
# 4. 投影矩阵(透视或正交)
ProjectionMatrix = ... # 4x4矩阵
# 5. 依次变换
P_world = ModelMatrix @ P_model
P_view = ViewMatrix @ P_world
P_clip = ProjectionMatrix @ P_view
# 6. 齐次除法
P_ndc = P_clip[:3] / P_clip[3]
# 7. 映射到屏幕
screen_width, screen_height = 1920, 1080
screen_x = int((P_ndc[0] + 1) / 2 * screen_width)
screen_y = int((1 - P_ndc[1]) / 2 * screen_height)
print(f"顶点最终在屏幕上的像素坐标: ({screen_x}, {screen_y})")
五、游戏场景举例
场景:你在《我的世界》里看一个箱子
- 箱子的顶点在模型空间是 (1, 0, 0, 1)
- 箱子在世界坐标 (10, 0, 5)
- 你(摄像机)在 (0, 2, 10),朝向原点
- 游戏用透视投影
- 屏幕分辨率 1920x1080
每一帧渲染时,所有顶点都要经过上述变换,才能正确显示在屏幕上。
六、形象总结
顶点变换就像是:
- 先把物体“放到世界里”(模型变换),
- 再用摄像机“观察世界”(视图变换),
- 用“魔法相机”把三维世界拍成二维照片(投影变换),
- 最后把照片贴到你的屏幕上(屏幕映射)。
我们用一个具体的例子,给出每一步的矩阵数值,并带你一步步算出顶点最终在屏幕上的像素坐标。
一、场景设定
- 箱子顶点(模型空间):P_model = (1, 0, 0, 1)
- 箱子在世界中的位置:平移到 (10, 0, 5),不旋转不缩放
- 摄像机位置:在 (0, 2, 10),朝向世界原点 (0, 0, 0)
- 投影方式:透视投影,fov=60°,宽高比=16/9,near=0.1,far=100
- 屏幕分辨率:1920x1080
二、具体矩阵数值
1. 模型矩阵(Model Matrix)
2. 视图矩阵(View Matrix)
摄像机在 (0, 2, 10),看向 (0, 0, 0),up向量 (0, 1, 0)。
我们用右手坐标系,OpenGL风格。
计算过程:
3. 投影矩阵(Projection Matrix)
三、顶点变换全过程
四、结果
- 最终屏幕坐标: (3017, 914)
- 由于x超出了1920,说明这个顶点在屏幕右侧之外,不会被渲染出来。
五、总结
- 模型矩阵:把顶点从物体坐标变到世界坐标
- 视图矩阵:把世界坐标变到摄像机坐标
- 投影矩阵:把摄像机坐标变到裁剪空间
- 齐次除法:得到NDC
- 屏幕映射:得到像素坐标
每一步都可以用具体的矩阵和数值来计算,整个过程就是3D渲染流水线的核心!