引言
Hi,大家好,我是一牛。在上一篇博客中,我们学习了如何在Metal 中使用MVP变换实现正交投影。正交投影常用于工程制图,它可以保持物体的比例不变,但是如果我们想要模拟人眼中的现实世界(近大远小),我们需要使用透视投影。今天,让我们一起学习下透视投影是如何实现的。
视椎体

透视投影矩阵
和正交投影使用长方体不同的是,在透视投影中我们使用了视椎体,经过模型变换、视图变换后,为了得到透视投影矩阵,我们只需要将是椎体压缩成长方体,之后再进行一次正交投影,我们就能够得到透视矩阵。
那么我们是如何通过挤压视椎体得到正交投影需要要的长方体?
假设我们从+x 方向观察,看向-x

根据相似三角形我们可以得到
y′=zny
同理,我们可以得到
x′=znx
对于空间中的任何一点 xyz1
经过挤压得到 znxznyunknown1
由于是齐次坐标,我们可以将乘z 得到 nxnyunknownz
我们可以初步得到这个矩阵
n0A00nB000C100D0
接下来我们只需要求出待定系数 A,B,C,D
我们知道,近平面上的点经过挤压保持不变。也就是说对于近平面任意一点 xyn1
由于它是齐次坐标,我们可以乘上n,得到nxnyn2n
我们观察待求矩阵的第三方,可得 A=0,B=0,即
[00AB] xyn1 = n2
-> A∗n+B=n2 (1)
又因为远平面的中心经过挤压保持不变,所以对应远平面中心点 00f1
由于它是齐次坐标,我们可以乘上f,得到00f2f
-> [00AB] 00f1 = f2
-> A∗f+B=f2 (2)
联立(1)(2)解一元二次方程组
得 A=n+f 、B=−n∗f
带人A,B,C,D 系数,得到挤压矩阵
n0000n0000n+f100−n∗f0
将挤压矩阵左乘正交投影矩阵
r−l20000t−b20000f−n11−r−lr+l−t−bt+b−f−nn0
最终透视投影矩阵是
r−l2n0000t−b2n00−r−lr+l−t−bt+bf−nf100−f−nn∗f0
在图形学中我们一般用视场角fovY,和近平面宽高比来定义透视投影矩阵。假定视椎体是对称物体,b=−t 且 l=−r

tanfovY/2=t/n 且 aspectRatio=r/t
我们可以得到透视投影矩阵
func createPerspectiveMatrix(fov: Float, aspectRatio: Float, nearPlane: Float, farPlane: Float) -> simd_float4x4 {
let tanHalfFov = tan(fov / 2.0);
var matrix = simd_float4x4(0.0);
matrix[0][0] = 1.0 / (aspectRatio * tanHalfFov);
matrix[1][1] = 1.0 / (tanHalfFov);
matrix[2][2] = farPlane / (farPlane - nearPlane);
matrix[2][3] = 1.0;
matrix[3][2] = -(farPlane * nearPlane) / (farPlane - nearPlane);
return matrix;
}
效果

仔细观察,与正交投影变换不同的是,图片的比例在转动的过程中会发生改变,这是因为转动过程中图片的深度值在发生改变,对应不同的深度值,透视投影会产生近大远小的效果!
结语
掌握好透视投影是我们打开3D世界的钥匙,而掌握好透视投影的关键是了解透视投影的矩阵推导过程。
谢谢大家,欢迎大家点赞、收藏!
本项目已开源