DirectX透视投影矩阵推导
定义一个相机的位置和方向。知道了相机的位置和方向,我们是通过定义一个平截头体(frustum)来描述相机的范围,裁剪阶段也是依据这个范围空间进行裁剪。平截头体如下:
表示垂直视角, 表示水平视角,near plane 表示近平面,far plane 表示远平面。
通常,我们在观察空间中使用4个参数定义,以原点为中心,观察方向为 z 轴正方向的 frustum。四个参数分别为:
- , 近平面位置
- , 远平面位置
- , 垂直视角
- , 横纵比 (aspect ratio)
在观察空间中,近平面和远平面都平行于 xy 平面,所以只需要指定他们沿 z 轴方向的距离即可表示这两个平面。横纵比由 表示,其中 w 表示投影窗口的宽度,h 表示投影窗口的高度(单位由观察空间决定)。投影窗口本质是场景在观察空间中的2D图像,而且该图像最终会被映射到后台缓冲区,所以投影窗口的比例应和后台缓冲区的尺寸比例一致。当后台缓冲 区的尺寸为 800×600 时,横纵比 r = 800/600 ≈ 1.333 。不一致会导致变形,比如一个圆被拉伸成椭圆。
计算水平视角 #
首先,假设一个投影平面,为了方便计算,设高 ,有, 。投影面尺寸大小不重要,重要的是比例 。同时,设投影面距离视点 为 。投影面在哪也无所谓,可以放在近平面,也可以放在 之间,但是最后发现的是,投影矩阵以及最后得到的坐标都与这个距离 无关。
由 平面,有
由 平面,有
所以,可以求得水平视角 。
计算投影窗口的投影坐标#
设视觉空间中一个点的坐标为 ,这个点投影到投影窗口上的坐标为 ,如图所示。
所以,根据三角形相似,有
因为在 frustum 内的点都会投影到投影面,所以我们可以反推,点 在 frustum 中,当且仅当,如下条件被满足时。
规范化设备坐标 (NDC, Normalized Device Coordinates)#
为什么要使用NDC?
- 设备坐标系(Device),也叫屏幕坐标系,由每一个显示设备(如显示器)的像素点定义,每一个显示设备都有自己单独的坐标系统。还可以进一步在屏幕坐标系统定义一块视图区(view port)
- 上述计算的投影坐标,依赖投影窗口的横纵比,这也就意味着我们需要为硬件指定横纵比,硬件才可以执行那些与投影窗口尺寸相关的操作(比如,将投影窗口映射到后台缓冲区)
- 去除横纵比的依赖,可以简化后面的运算。NDC空间的坐标可以通过视口变换映射为设备坐标系下的屏幕坐标。
在NDC空间下,x,y 会规范到 [-1, 1] 之间,z规范到 [0, 1] 之间(d3d中,而在opengl中,z 规范到 [-1, 1])
所以NDC空间下,点 在 frustum 中,当且仅当,
所以,修改上面的投影公式,得到在NDC空间下的投影坐标:
在 NDC 空间中,投影窗口的高度和宽度都为 2。也就是说,现在的尺寸是固定的,硬件不需要知道横纵比,而且我们必须自己来完成投影坐标从观察空间到 NDC 空间的转换工作(图形硬件假定我们会完成这一工作,这个工作也就是视口变换)。
矩阵描述以及透视除法#
现在我们想要把投影变换用矩阵来表示。但是上式中,由于都出现了除以z,而非线性的式子我们无法使用矩阵描述,但是都除以z提醒了我们可以分为两部分处理:先一个线性操作,然后一个除以z的非线性操作,但是我们经过线性变化后,原始的 z 已经丢失,我们需要想办法保存下来原始的深度 z 。解决办法是我们使用齐次坐标的 w 保存 z。所以投影矩阵大致如下:
矩阵中的 A、B 是两个对 z 线性变换的常量参数,后面会求。
任意一个frustum中的坐标 (x, y, z, 1) 与 投影矩阵乘,可以得到:
然后,每个坐标除以 ,得到最终结果:
除以w这一步骤,又被称为透视除法,同时除以w的时候,我们不必担心 w=0,因为 w = z >= n > 0
规范化深度值#
在d3d中,z会被规范到 [0, 1],令 为映射函数,所以有,
将 和 带入,有,
解得:
有,
函数图像:
由于图像是非线性的,当 z 接近 近平面 和 远平面 时,都会出现精度问题。通常的建议是让近平面和远平面尽可能接近,把深度的精度性问题减小到最低程度。
最后,投影矩阵:
Direct3D 11 中,投影矩阵可通过 XNA 数学库获得。
static XMMATRIX XMMatrixPerspectiveFovLH( // returns projection matrix, LH表示左手坐标系
FLOAT FovAngleY, // vertical field of view angle in radians
FLOAT AspectRatio, // aspect ratio = width / height
FLOAT NearZ, // distance to near plane
FLOAT FarZ); // distance to far plane
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现