视椎体定义的是在相机坐标系下以坐标原点为光心的一块可视区域的椎体,如下图所示,椎体区域可以由近截面,远截面,水平视场角,垂直视场角定义:
在使用Opengl,Direct3D进行渲染时会将视椎体中的三维点映射到2维的平面上,整个映射过程会使用到投影矩阵,投影矩阵是一个4X4的矩阵M,例如有一个三维点P=[X,Y,Z,1]',通过M乘以P得到映射平面的点坐标[Xp,Yp,Zp,Wp],然后除以齐次坐标Wp,保证在视椎体内的三维点通过映射后Xp/Wp在[-1,1],Yp/Wp在[-1,1],Zp/Wp在[0,1](Direct3D中Zp/Wp在[0,1],OpenGL中Zp/Wp在[-1,1])。下面我们分别推导Direct3D和OpenGL中的透视矩阵:
一. Direct3D中采用的是左手坐标系,视线方向沿Z轴的正方向,如下图所示:
根据入参的不同分为两种求解方式:
1. 近截面n,远截面f,垂直视场角a,宽高比r
因为在Camera坐标系中,近截面和远截面平行于XY平面,所以n与f的数值等同于Z值,宽高比r=w/h,w和h分别表示输出图像的宽和高,由r和a可以得出水平视场角b=ar,为了方便分析我们取投影平面的高为2,则投影平面的宽w=rh=2r,如下图所示:
为投影平面到相机坐标系原点的距离,根据正切可得:
根据相似三角形定理:
如下图所示,
是三维点在投影平面上的投影:
根据定义只有点(x,y,z)在视椎体内才会被投影到投影平面,并且满足条件:
最后我们需要将,
以及深度信息,转换到NDC(归一化设备坐标系)中,即让
,
转化到
空间内:
修改我们上面得到的投影计算:
最后我们将上面左右的计算整理到矩阵M中:
通过将齐次坐标系除以w=z:
至此,x,y分量都已经转换到了空间内,最后需要将深度信息转化到
空间内:
解方程组得:
将A,B整理进矩阵M,得到:
二. OpenGL中采用的是右手坐标系,视线方向沿Z轴的负方向,如下图所示:
1. 近截面n,远截面f,垂直视场角a,宽高比r
因为在Camera坐标系中,近截面和远截面平行于XY平面,所以n与f的数值等同于Z的负值
static Matrix4 PerspectiveRH(T yfov, T aspect, T znear, T zfar)
{
Matrix4 m;
T tanHalfFov = tan(yfov * T(0.5f));
m.M[0][0] = T(1) / (aspect * tanHalfFov);
m.M[1][1] = T(1) / tanHalfFov;
m.M[2][2] = zfar / (znear - zfar);
// m.M[2][2] = zfar / (zfar - znear);
m.M[3][2] = T(-1);
m.M[2][3] = (zfar * znear) / (znear - zfar);
m.M[3][3] = T(0);
// Note: Post-projection matrix result assumes Left-Handed coordinate system,
// with Y up, X right and Z forward. This supports positive z-buffer values.
// This is the case even for RHS cooridnate input.
return m;
}
2. 近截面n,远截面f,水平左,水平右,垂直上,垂直下
Matrix4f Matrix4f_CreateProjection( const float minX, const float maxX,
float const minY, const float maxY, const float nearZ, const float farZ )
{
const float width = maxX - minX;
const float height = maxY - minY;
const float offsetZ = nearZ; // set to zero for a [0,1] clip space
Matrix4f out;
if ( farZ <= nearZ )
{
// place the far plane at infinity
out.M[0][0] = 2 * nearZ / width;
out.M[0][1] = 0;
out.M[0][2] = ( maxX + minX ) / width;
out.M[0][3] = 0;
out.M[1][0] = 0;
out.M[1][1] = 2 * nearZ / height;
out.M[1][2] = ( maxY + minY ) / height;
out.M[1][3] = 0;
out.M[2][0] = 0;
out.M[2][1] = 0;
out.M[2][2] = -1;
out.M[2][3] = -( nearZ + offsetZ );
out.M[3][0] = 0;
out.M[3][1] = 0;
out.M[3][2] = -1;
out.M[3][3] = 0;
}
else
{
// normal projection
out.M[0][0] = 2 * nearZ / width;
out.M[0][1] = 0;
out.M[0][2] = ( maxX + minX ) / width;
out.M[0][3] = 0;
out.M[1][0] = 0;
out.M[1][1] = 2 * nearZ / height;
out.M[1][2] = ( maxY + minY ) / height;
out.M[1][3] = 0;
out.M[2][0] = 0;
out.M[2][1] = 0;
out.M[2][2] = -( farZ + offsetZ ) / ( farZ - nearZ );
out.M[2][3] = -( farZ * ( nearZ + offsetZ ) ) / ( farZ - nearZ );
out.M[3][0] = 0;
out.M[3][1] = 0;
out.M[3][2] = -1;
out.M[3][3] = 0;
}
return out;
}
近截面n,远截面f,垂直视场角a,宽高比r ---------> 右手系透视矩阵,Z[-1,1]
近截面n,远截面f,垂直视场角a,宽高比r ---------> 右手系透视矩阵,Z[0,1]
近截面n,远截面f,垂直视场角a,宽高比r ---------> 左手系透视矩阵,Z[-1,1]
近截面n,远截面f,垂直视场角a,宽高比r ---------> 左手系透视矩阵,Z[0,1]
近截面n,远截面f,水平左l(以1作为近截面表示),水平右r(以1作为近截面表示),垂直上t(以1作为近截面表示),垂直下b(以1作为近截面表示) ---------> 右手系透视矩阵,Z[-1,1]
近截面n,远截面f,水平左l(以近截面n为基准),水平右r(以近截面n为基准),垂直上t(以近截面n为基准),垂直下b(以近截面n为基准) ---------> 右手系透视矩阵,Z[-1,1]
近截面n,远截面f,水平左l(以1作为近截面表示),水平右r(以1作为近截面表示),垂直上t(以1作为近截面表示),垂直下b(以1作为近截面表示) ---------> 右手系透视矩阵,Z[0,1]
近截面n,远截面f,水平左l(以近截面n为基准),水平右r(以近截面n为基准),垂直上t(以近截面n为基准),垂直下b(以近截面n为基准) ---------> 右手系透视矩阵,Z[0,1]
近截面n,远截面f(无穷远),水平左l(以1作为近截面表示),水平右r(以1作为近截面表示),垂直上t(以1作为近截面表示),垂直下b(以1作为近截面表示) ---------> 右手系透视矩阵,Z[-1,1]
近截面n,远截面f(无穷远),水平左l(以近截面n为基准),水平右r(以近截面n为基准),垂直上t(以近截面n为基准),垂直下b(以近截面n为基准) ---------> 右手系透视矩阵,Z[-1,1]
近截面n,远截面f(无穷远),水平左l(以1作为近截面表示),水平右r(以1作为近截面表示),垂直上t(以1作为近截面表示),垂直下b(以1作为近截面表示) ---------> 右手系透视矩阵,Z[0,1]
近截面n,远截面f(无穷远),水平左l(以近截面n为基准),水平右r(以近截面n为基准),垂直上t(以近截面n为基准),垂直下b(以近截面n为基准) ---------> 右手系透视矩阵,Z[0,1]
近截面n,远截面f,水平左l(以1作为近截面表示),水平右r(以1作为近截面表示),垂直上t(以1作为近截面表示),垂直下b(以1作为近截面表示) ---------> 左手系透视矩阵,Z[-1,1]
近截面n,远截面f,水平左l(以近截面n为基准),水平右r(以近截面n为基准),垂直上t(以近截面n为基准),垂直下b(以近截面n为基准) ---------> 左手系透视矩阵,Z[-1,1]
近截面n,远截面f,水平左l(以1作为近截面表示),水平右r(以1作为近截面表示),垂直上t(以1作为近截面表示),垂直下b(以1作为近截面表示) ---------> 左手系透视矩阵,Z[0,1]
近截面n,远截面f,水平左l(以近截面n为基准),水平右r(以近截面n为基准),垂直上t(以近截面n为基准),垂直下b(以近截面n为基准) ---------> 左手系透视矩阵,Z[0,1]