函数的输入和输出都是3D向量,我们称为线性变换
矩阵表示法:
⭐所以已知一个线性变换,只要将i,j,z也就是标准基向量代入线性变换,就能构造一个变换矩阵
A:线性变换的矩阵表示法 -- 线性变换才能满足这种提取式
⭐若为线性变换,当且仅当此函数具有下列性质:
缩放变换是相对于当前坐标系中的原点,令向量在x、y、z轴上分别以系数进行缩放
-- Sx Sy Sz就是xyz轴的缩放系数
左手坐标系的变换矩阵与坐标表示与右手有很大不同,变换矩阵乘在坐标右侧, 坐标以行向量形式存在
旋转矩阵证明(罗德里格旋转公式):假设这种情况,令向量v绕轴n以角θ进行旋转,在沿n轴从上至下俯瞰时,我们按顺时针方向来测量角θ,并假设,v和n之间的夹角为α
旋转时我们考虑将向量v分解成平行于n轴的分量和垂直于n轴的分量,平行于n轴的分量在旋转时是保持不变的
最终,旋转向量:
:v·n是大小,n是平行方向
:在垂直于n轴的平面内旋转θ角度
确定大小:
确定方向:
的方向就是旋转的"切方向"
因此推导出了旋转公式:
:因为v垂直=v-v平行
要证明是线性变换,只要证明其满足上述两个性质⭐即可
①分配律:叉乘和点乘都满足分配律,所以第一条性质满足
②标量乘法提取:叉乘和点乘都满足能提取标量乘积,所以第二条性质满足
所以是线性变换 -- > 能写成矩阵表达式的方式
将各个标准基向量代入线性变换函数,再把得到的向量分别作为矩阵的行向量,最终结果:
其中:
旋转矩阵有一个特殊的性质,每个行向量都为单位长度且两两正交,也就是说这些行向量都是规范正交的,因此旋转矩阵是正交矩阵 --- 正交矩阵的特性:逆矩阵等于转置矩阵
特别的:如果选择绕x轴、y轴或z轴旋转(n取(1,0,0)...)
注意:①这是在不考虑齐次坐标的情况下的旋转矩阵
②记忆方法:如若旋转x轴,则第一行第一列扣除,剩下的”余子阵“左上角为cosθ,右上角为sinθ以此类推
注意:旋转部分,是以原点为中心进行旋转! -- 如果想对一个对称图案进行旋转,请先确定其旋转中心 。
将坐标扩充为四元组,第四个坐标的取值w根据坐标描述对象的不同而不同:
① 表示向量
② 表示点
好处:①向量不考虑平移操作,设置w=0可以防止收到平移操作的影响
②点-点=向量 点+向量=点 向量+向量=向量
仿射变换=线性变换+平移
扩充为齐次坐标后:
①如果输入的w=0,那么0*b_x=0,所以没有执行平移操作
②输入w不管是多少,仿射变换矩阵最后一列都是[0,0,0,1]^T,得到的新坐标w仍然为原值,所以进行仿射变换后不会改变w的值
注意,不管是缩放还是平移等变换矩阵,如果再乘以变换矩阵的逆,那么物体就没有移动,所以从理论上我们就能联想到平移矩阵和缩放矩阵的逆是如何书写的
变换矩阵的组合:SRT -- S:缩放矩阵 R:旋转矩阵 T:平移矩阵
我们往往会先把各种变换矩阵相乘,在对每一个顶点操作,免得每个顶点都挨着乘上不同的变换矩阵,影响性能 -- 但是注意,矩阵乘法不满足交换律,所以矩阵乘法的顺序很重要
①向量的坐标变换:
对于框架A:,对于框架B:
其中x,y是P点在框架A中的坐标,是框架B的xy坐标轴相对于框架A的坐标
已知P点在A框架中的坐标,以及A框架中坐标轴相对于B框架的坐标,即可求得
现将向量推广到3D空间,如果,那么
②点的坐标变换:
点的坐标变换,相比于向量的坐标变换,要额外考虑坐标系的平移,坐标系的原点不在同一处
所以加上平移向量:
:框架A中原点在框架B中的位置坐标
③(总结)坐标变换的矩阵表示:
根据线性变换和齐次坐标(对应w坐标)的概念,我们可以把拆成矩阵相乘形式:
坐标变换矩阵: 坐标转换矩阵的逆:
-- 本质上是一个旋转加平移矩阵[旋转矩阵的逆就是其转置矩阵、平移矩阵的逆就是取负数] -- 能把坐标变换,理解成与几何变换相同的数学形式,可谓是殊途同归
在后续DX12管线处理中,我们需要得到观察矩阵(view matrix),观察矩阵可以从世界空间到观察空间的转换,我们可以通过求W的逆,将坐标从世界空间变换到观察空间,DirectXMath提供专门的函数XMMatrixLookAtLH()得到观察矩阵,也可以自行构造(龙书第15章书写摄像机类时自行构造了观察矩阵)
具体代码:
// 代码在龙书P499~500页 float x = -XMVectorGetX(XMVector3Dot(P, R)); float y = -XMVectorGetX(XMVector3Dot(P, U)); float z = -XMVectorGetX(XMVector3Dot(P, L)); // mRight:摄像机右侧 -- 观察坐标系+x轴 mView(0, 0) = mRight.x; mView(1, 0) = mRight.y; mView(2, 0) = mRight.z; mView(3, 0) = x; // mUp:摄像机正上方 -- 观察坐标系+y轴 mView(0, 1) = mUp.x; mView(1, 1) = mUp.y; mView(2, 1) = mUp.z; mView(3, 1) = y; // mLook:摄像机正前方 -- 观察坐标系+z轴 mView(0, 2) = mLook.x; mView(1, 2) = mLook.y; mView(2, 2) = mLook.z; mView(3, 2) = z; mView(0, 3) = 0.0f; mView(1, 3) = 0.0f; mView(2, 3) = 0.0f; mView(3, 3) = 1.0f;
mRight是观察坐标系+x轴的向量在世界坐标系中的坐标表现 -> 观察坐标系是框架A,世界坐标系是框架B,所以得到的是从观察坐标系转换到世界坐标系的坐标转换矩阵 -- 所以需要求逆
// 1.这些函数的作用是构造(返回)一个变换矩阵
// 构造缩放矩阵
XMMATRIX XM_CALLCONV XMMatrixScaling(float ScaleX,float ScaleY,float ScaleZ);
// 用3D向量的分量来构造缩放矩阵
XMMATRIX XM_CALLCONV XMMatrixScalingFromVector(FXMVECTOR Scale);
// 弧度 以顺时针方向旋转
XMMATRIX XM_CALLCONV XMMatrixRotationX(float Angle); // float Angle是以弧度为单位
// 绕旋转轴Axis顺时针旋转
XMMATRIX XM_CALLCONV XMMatrixRotationAxis(FXMVECTOR Axis,float Angle);
// 平移
XMMATRIX XM_CALLCONV XMMatrixTranslation(float OffsetX,float OffsetY,float OffsetZ);
// 平移 用向量构造
XMMATRIX XM_CALLCONV XMMatrixTranslationFromVector(FXMVECTOR Offset);
// 2.计算:
// 计算向量与矩阵的乘积vM 针对点的变换 Vw默认为1
XMVECTOR XM_CALLCONV XMVector3TransformCoord(FXMVECTOR V,CXMMATRIX M);
// 针对向量的变换 Vw默认为0
XMVECTOR XM_CALLCONV XMVector3TransformNormal(FXMVECTOR V,CXMMATRIX M);
注意:参数float Angle是以弧度为单位(虽然参数名字是angle而不是radian)
其实弧度值根本没必要转换为角度值,弧度制为我们常见的表示形式(等等),且三角函数的参数都是以弧度制作为单位
弧度与角度的转换公式:
代码示例:
#include
#include
#include
#include
using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;
ostream& XM_CALLCONV operator<<(ostream& os, FXMMATRIX m)
{
XMFLOAT4X4 dest;
XMStoreFloat4x4(&dest, m);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
os << dest.m[i][j] << " ";
}
os << "\n";
}
return os;
}
int main()
{
XMMATRIX m1 = XMMatrixSet(
1.f, 2.f, 3.f, 0.f,
0.f, 1.f, 0.f, 0.f,
0.f, 0.f, 1.f, 0.f,
2.f, 2.f, 2.f, 1.f
);
cout << m1 << endl;
XMMATRIX m2 = XMMatrixScaling(1.f, 2.f, 3.f);
cout << m2 << endl;
XMVECTOR v1 = XMVectorSet(1.f, 2.f, 3.f, 0.f);
XMMATRIX m3 = XMMatrixScalingFromVector(v1);
cout << m3 << endl;
XMMATRIX m4 = XMMatrixRotationX(30 * XM_PI / 180);
cout << m4 << endl;
XMVECTOR v2 = XMVectorSet(1.f, 2.f, 3.f, 0);
v2 = XMVector4Normalize(v2);
XMMATRIX m5 = XMMatrixRotationAxis(v2, 60 * XM_PI / 180);
cout << m5 << endl;
}