其实各大矩阵具体的推导过程我就不给出了,我直接给出矩阵具体的形式和实现代码,以及那些大牛推导矩阵详细的文章:
一,世界矩阵(WorldMatrix)
我一般称世界矩阵为SRT矩阵,SRT分别是"Scale","rotate","translate"三个单词的缩写,也就是世界矩阵由缩放矩阵,旋转矩阵,平移矩阵构成的
(1)缩放矩阵(ScaleMatrix)
假设在X轴缩放Sx倍,在Y轴缩放Sy倍,在Z轴缩放Sz倍,缩放如下所示:
点乘以矩阵的公式如下:
\
实现代码:
//构造一个缩放矩阵
Matrix BuildScaleMatrix(float x, float y, float z)
{
Matrix ScaleMa;
//清除为0
ZeroMemory(&ScaleMa, sizeof(ScaleMa));
ScaleMa.ma[0][0] = x;
ScaleMa.ma[1][1] = y;
ScaleMa.ma[2][2] = z;
ScaleMa.ma[3][3] = 1.0f;
return ScaleMa;
}
(2)旋转矩阵(RotateMatrix)
在说明旋转矩阵之前,我得说明一下,在左手坐标系如何判断哪个方向为顺时针方向旋转。
先来看下面的图(本人画工有点差,请见谅),我们先用左手的拇指朝向Y的正方向,则四指所绕的方向也就是饶Y轴渲染的顺时针方向了,其它轴的顺时针方向同理也就是这样判断。(其他轴的顺时针旋转方向以及右手坐标系的顺时针也是用这样的方法判断,只不过右手坐标系下改为了使用右手)
一,绕X轴顺时针旋转Θ度数
实现代码:
//构造一个绕X轴的旋转矩阵,形参为角度,顺时针
Matrix BuildRotateXMatrix(float x)
{
Matrix RotateXMa;
//清除为0
ZeroMemory(&RotateXMa, sizeof(RotateXMa));
//度数转化为弧度,math库的函数的参数都是弧度
float angle = (x *MATH_PI) / (180.0f);
RotateXMa.ma[0][0] = 1.0f;
RotateXMa.ma[3][3] = 1.0f;
float cs = (float)cos(angle);
float ss = (float)sin(angle);
RotateXMa.ma[1][1] = cs;
RotateXMa.ma[2][2] = cs;
RotateXMa.ma[1][2] = ss;
RotateXMa.ma[2][1] = -ss;
return RotateXMa;
}
二,绕Y轴顺时针旋转Θ度数
实现代码:
//构造一个绕Y轴的旋转矩阵,形参为角度,顺时针
Matrix BuildRotateYMatrix(float y)
{
Matrix RotateYMa;
//清除为0
ZeroMemory(&RotateYMa, sizeof(RotateYMa));
//度数转化为弧度,math库的函数的参数都是弧度
float angle = (y *MATH_PI) / (180.0f);
RotateYMa.ma[1][1] = 1.0f;
RotateYMa.ma[3][3] = 1.0f;
float cs = (float)cos(angle);
float ss = (float)sin(angle);
RotateYMa.ma[0][0] = cs;
RotateYMa.ma[2][2] = cs;
RotateYMa.ma[0][2] = -ss;
RotateYMa.ma[2][0] = ss;
return RotateYMa;
}
三,绕Z轴顺时针旋转Θ度数
实现代码:
//构造一个绕Z轴的旋转矩阵,形参为角度,顺时针
Matrix BuildRotateZMatrix(float z)
{
Matrix RotateZMa;
//清除为0
ZeroMemory(&RotateZMa, sizeof(RotateZMa));
RotateZMa.ma[2][2] = 1.0f;
RotateZMa.ma[3][3] = 1.0f;
//度数转化为弧度,math库的函数的参数都是弧度
float angle = (z *MATH_PI) / (180.0f);
float cs = (float)cos(angle);
float ss = (float)sin(angle);
RotateZMa.ma[0][0] = cs;
RotateZMa.ma[1][1] = cs;
RotateZMa.ma[0][1] = ss;
RotateZMa.ma[1][0] = -ss;
return RotateZMa;
}
(3)移动矩阵(TranslateMatrix)
假设某个顶点在X轴方向移动dx个单位,Y轴移动dy个单位,在Z轴移动dz个单位
实现代码:
//构造一个移动矩阵
Matrix BuildTranslateMatrix(float x,float y,float z)
{
Matrix TranslateMa;
//清除为0
ZeroMemory(&TranslateMa, sizeof(TranslateMa));
TranslateMa.ma[0][0] = 1.0f;
TranslateMa.ma[1][1] = 1.0f;
TranslateMa.ma[2][2] = 1.0f;
TranslateMa.ma[3][3] = 1.0f;
TranslateMa.ma[3][0] = x;
TranslateMa.ma[3][1] = y;
TranslateMa.ma[3][2] = z;
return TranslateMa;
}
关于缩放矩阵,旋转矩阵,移动矩阵的详细推导见《Introduction+to+3D+Game+Programming+with+DirectX+11》第三章
二,相机变换矩阵(ViewMatrix)
在此之前看看UVN相机模型,如图所示:
注视向量N的方向跟相机空间(ViewSpace)的Z轴方向是一致的,而竖直向量V的方向与相机空间(ViewSpace)的Y轴方向是一致的,右向量U的方向与相机空间(ViewSpace)的X轴方向是一致的。
计算UVN向量的公式如下:
这里的“X”为叉乘的意思,并且一样可以通过左手规则(因为本格软件光栅器用的是模拟的为Directx11,为左手坐标系)得到叉乘向量的方向,也就是用左手从叉乘符号左边那个向量绕向叉乘符号右边的那个向量,拇指所指方向即为叉乘得到向量的方向。
相机矩阵如下所示:
实现代码:
//建立一个左手坐标系的相机变换矩阵
Matrix MatrixLookAtLH(Point* Eye, Point* LookAt, Vector* Up)
{
Vector N;
Vector U;
Vector V;
//求出注视向量N
N.x = LookAt->x - Eye->x;
N.y = LookAt->y - Eye->y;
N.z = LookAt->z - Eye->z;
N.w = 0.0f;
//用叉乘求出右向量U,U=UpxN;
U = VectorCrossProduct(Up, &N);
//用叉乘求出V,V=NxU
V = VectorCrossProduct(&N, &U);
//规格化UVN三个向量
VectorNormalize(&U);
VectorNormalize(&V);
VectorNormalize(&N);
//求出-EyeU -EyeV -EyeN
float x1 = -VectorDotProduct(Eye, &U);
float y1 = -VectorDotProduct(Eye, &V);
float z1 = -VectorDotProduct(Eye, &N);
//求出相机变换矩阵
Matrix mViewMatrix;
ZeroMemory(&mViewMatrix, sizeof(mViewMatrix));
mViewMatrix.ma[0][0] = U.x;
mViewMatrix.ma[1][0] = U.y;
mViewMatrix.ma[2][0] = U.z;
mViewMatrix.ma[3][0] = x1;
mViewMatrix.ma[0][1] = V.x;
mViewMatrix.ma[1][1] = V.y;
mViewMatrix.ma[2][1] = V.z;
mViewMatrix.ma[3][1] = y1;
mViewMatrix.ma[0][2] = N.x;
mViewMatrix.ma[1][2] = N.y;
mViewMatrix.ma[2][2] = N.z;
mViewMatrix.ma[3][2] = z1;
mViewMatrix.ma[3][3] = 1.0f;
return mViewMatrix;
}
具体推导见文章:推导相机变换矩阵
三,透视投影矩阵(PerspectiveMatrix)
在推导透视投影矩阵前先看看视截体(Frustum)是怎么样的:
我的渲染器,近截面和投影平面是同一个平面,视截体在YZ平面的投影如下面图所示,
n为原点到近截面的距离,f为原点到远截面的距离,α为视截体在YZ平面投影的FOV视角,r为投影平面的宽高比,则透视投影矩阵为:
实现代码:
//建立一个左手坐标系的透视投影矩阵,注意为左手坐标系下的,视平面和近截面为同一个平面,FOV视角为YZ屏幕的
Matrix MatrixPerspectiveFovLH(float FovYZ,float ScreenAspect,float ScreenNear,float ScreenFar)
{
Matrix mProjMatrix;
//清除为0
ZeroMemory(&mProjMatrix, sizeof(mProjMatrix));
//度数转化为弧度,math库的函数的参数都是弧度
float angle = (FovYZ *MATH_PI)/ (180.0f);
//半角
angle /= 2.0f;
//求出各类参数
float s1 = 1 / (ScreenAspect*(float)tan(angle));
float s2 = 1 / tan(angle);
float a = ScreenFar / (ScreenFar - ScreenNear);
float b = -(ScreenNear*ScreenFar) / (ScreenFar - ScreenNear);
mProjMatrix.ma[0][0] = s1;
mProjMatrix.ma[1][1] = s2;
mProjMatrix.ma[2][2] = a;
mProjMatrix.ma[3][2] = b;
mProjMatrix.ma[2][3] = 1.0f;
return mProjMatrix;
}
具体推导参见文章:
四,透视除法:
透视除法的本质:
假设有一个向量为(x,y,z,w),进行透视除法也就是把向量的每个参数除以向量的第四个参数,得到(x/w,y/w,z/w,1)
参见 深入探索透视投影变换 和 深入探索透视投影变换(续)
实现代码:
//对点集进行透视除法,会后变到NDC空间
void PerspectiveDivede(vector<Vertex>& pList)
{
for (vector<Vertex>::iterator it = pList.begin(); it != pList.end(); ++it)
{
(*it).x = (*it).x / (*it).w;
(*it).y = (*it).y / (*it).w;
(*it).z = (*it).z / (*it).w;
(*it).w = (*it).w / (*it).w;
}
}
五,视口变换矩阵(ViewPortMatrix)
实际上在D3D11中,不需要我们手动进行视口变换的,D3D11给我们提供了一个接口,我们在这个接口填充参数,然后D3D11在内部自动帮我们进行视口变换了,那个接口如下图所示:
生成相应的视口矩阵:
解释一下上面的参数,TopLeftX和TopLeftY为视平面在左上角的坐标点,一般来说都为0。Width为视平面的宽度,Height为视平面的高度,MinDepth为顶点的最小深度(Z缓存),
MaxDepth为顶点的最大深度(Z缓存),一般来说MinDepth为0,MaxDepth为1.
所以一般视口变换矩阵为下面所示:
实现代码:
}
//一般而言,视口变换矩阵的TopLeftX=0,TopLeftY=0,MaxDepth=1.0,MinDepth=0.0
Matrix MatrixViewPort(float ScreenWidth, float ScreenHeight, float MaxDepth, float MinDepth, float TopLeftX, float TopLeftY)
{
Matrix MatrixViewPort;
//清除为0
ZeroMemory(&MatrixViewPort, sizeof(MatrixViewPort));
MatrixViewPort.ma[0][0] = ScreenWidth / 2.0f;
MatrixViewPort.ma[1][1] = -ScreenHeight/ 2.0f;
MatrixViewPort.ma[2][2] = MaxDepth - MinDepth;
MatrixViewPort.ma[3][0] = TopLeftX + ScreenWidth / 2.0f;
MatrixViewPort.ma[3][1] = TopLeftY+ ScreenHeight / 2.0f;
MatrixViewPort.ma[3][2] = MinDepth;
MatrixViewPort.ma[3][3] = 1.0f;
return MatrixViewPort;
}
具体推导参见《Introduction+to+3D+Game+Programming+with+DirectX+11》的第十六章的16.1小节
实现代码: