介绍
本文主要介绍如何在绘制图片时,对图片进行几何变换,如旋转、平移、缩放、裁剪和镜像。
最终结果
3维物体到2维图片
虽然本文输入的就是 2维的图片,但是将图片放置在空间的哪个位置,也会给他提供第三维的坐标。
从 3 维空间到2维图片的过程中包含了4大变换:世界变换、观察变换、投影变换和视口变换。
前面的3个变换是我们要对 输入顶点做的变换,最后一个是光栅化时,pipeline 做的变换。
世界变换
我们一遍给图片设置顶点坐标时,都是将其设置为 (-1,-1),(-1,1),(1,1),(1,-1) 这些比较好书写的顶点,但是你想将图画放到其他位置,你当然可以手算放到该位置,其顶点要如何设置;也可以默认将图片放置在默认位置,然后对其进行矩阵变换,将其放到对应位置。
世界变换,可以理解为一堆图片刚开始都放置在 (-1,-1),(-1,1),(1,1),(1,-1) 这个区域内,然后你告诉装修师傅,每一张画要放置哪些地方,然后装修师傅安装说明对每张图片进行变换,然后挂到对应位置。
具体如何安装说明挂图片可以参考1参考2
观察变换
观察变换可以理解为,师傅在挂画的时候,你一直是站在 (0,0,0)位置的。现在装修师傅把画挂好了,你开始移动摄像机的位置和角度了,在家里进行拍照。确定摄像机的位姿,同个三个点来确定,一个是摄像机的位置,一个是摄像机的朝向,还有一个是摄像机的上方向(比如摄像头上方向的朝上的,拍到的人就是朝上的,如果摄像头旋转180后,拍到的人就是朝下的)。
为了计算方便,摄像机的移动可以理解为摄像头没动,画动了,但是每一幅画之间的相对位置是没动的。 就是做了一个坐标系的变换。
投影变换
我个人感觉,投影变换可以理解给摄像机的固有参数,就像同一个位置,相同的风景,不同照相机拍到的相片也是不一样的。比如广角相机、底片的宽高,焦距等参数。常用 fov 、宽高比和最近距离和最远距离设置变换矩阵。
常见的投影变换有正交投影和透视投影。正交投影可以理解为画物体三视图那种感觉,透视投影就是我们人眼看到的场景,近大远小。
视口变换
为了前面一套流程的标准化,所有刚开始都把图片缩放到了[-1,1]^2。在正式展示之前需要把图片的大小还原,视口变换做的就是该部分的内容。
其他想法
参考资料说,通过前面三个步骤,将空间缩放到了 [-1,1]^3,但是如何将三维坍塌到二维还是不太理解,有一种说法是去掉z轴。
做完上面三个步骤,可以理解为摄像头是在 (0,0,0)位置,看向z方向(由于directX 是左手坐标系,所有是看向z 方向;有些教程用的是右手坐标系,看向的就是-z方向),摄像头上方向为y方向。然后物体在 fov 、宽高比和最近距离和最远距离内,画布上就可以显示该物体。
如果我们不做上面的三个变化,可以怎么理解呢?我测试下来是世界变换不对物体进行任何仿射变换则其对应矩阵为单位矩阵;将摄像头放置在原点、朝向z,上方向为y,其对应矩阵为单位矩阵;投影变换,做正交投影,宽和高都为2,最近距离为0,最远距离为1,其对应矩阵为单位矩阵。
正交投影时, 观察变换的 z 位置不影响最终画面。
show me the code
获得矩阵
这些矩阵,通过参考教程里面可以手动写出来,但是 DirectXMath 库(参考)把这些矩阵都封装好了,只要设置相应的参数就可以自动生成对应的矩阵。
世界变换
设置旋转
XMMatrixRotationX
XMMatrixRotationY
XMMatrixRotationZ
设置缩放
XMMatrixScaling
设置平移
XMMatrixTranslation
设置镜像
XMMatrixReflect (镜像后,顺时针三角形回变成逆时针三角形,所以镜像后可能看不到图片了,可以对 uv 坐标做镜像,就不会有这个问题了)
观察变换
XMMatrixLookAtLH (倒数第二个L,应该代表的是左手坐标系,我看函数里面还有XMMatrixLookAtRH )
投影变换
XMMatrixOrthographicLH 正交投影
XMMatrixPerspectiveFovLH 透视投影
计算
这些变换可以在 cpu 中执行,也可以在 gpu 执行,理论上 gpu 对矩阵的计算是比 cpu 快的,但是考虑到数据从 cpu 拷贝到 gpu 的耗时,且该矩阵比较简单,具体哪种方式比较快,还没有测试过。本文是在 cpu 中进行计算的。在 cpu 中,如果这边变换矩阵没有改变,则后面的循环这边就不用耗时了,放在 gpu 则需要每一次循环都计算一遍。
代码
XMFLOAT4X4 pos = SetMVP();
SimpleVertex vertices[] =
{
{XMFLOAT3(pos._11 / pos._14, pos._12 / pos._14, pos._13 / pos._14), XMFLOAT2(m_transformation.flipH ? 1.0 : 0.0f, m_transformation.flipV ? 0.0f : 1.0f)},
{XMFLOAT3(pos._21 / pos._24, pos._22 / pos._24, pos._23 / pos._24), XMFLOAT2(m_transformation.flipH ? 1.0 : 0.0f, m_transformation.flipV ? 1.0f : 0.0f)},
{XMFLOAT3(pos._31 / pos._34, pos._32 / pos._34, pos._33 / pos._34), XMFLOAT2(m_transformation.flipH ? 0.0 : 1.0f, m_transformation.flipV ? 1.0f : 0.0f)},
{XMFLOAT3(pos._41 / pos._44, pos._42 / pos._44, pos._43 / pos._44), XMFLOAT2(m_transformation.flipH ? 0.0 : 1.0f, m_transformation.flipV ? 0.0f : 1.0f)},
};
DirectX::XMFLOAT4X4 Graphics::SetMVP()
{
//XMMATRIX rotate = DirectX::XMMatrixRotationX(m_transformation.angle * D3DX_PI / 180);
//XMMATRIX rotate = DirectX::XMMatrixRotationY(m_transformation.angle * D3DX_PI / 180);
XMMATRIX rotate = DirectX::XMMatrixRotationZ(m_transformation.angle * D3DX_PI / 180);
XMMATRIX scale = DirectX::XMMatrixScaling(m_transformation.scale.x * 265.0, m_transformation.scale.y*235, 1.0f);
XMMATRIX translate = DirectX::XMMatrixTranslation(m_transformation.pos.x, m_transformation.pos.y, 0.0f);
//XMMATRIX shear = DirectX::XMMatrix;
m_model = scale * rotate* translate;
// 由于 directX 是左手坐标系,有些东西和课里面不一样
m_view = DirectX::XMMatrixLookAtLH(
DirectX::XMVectorSet(0.0f, 0.0f, -10.0f, 0.0f),
DirectX::XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f),
DirectX::XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f));
FLOAT fov = 90 * D3DX_PI / 180;
FLOAT aspectRatio = 800.0 / 600;
odprintf("m_transformation.scale.x %f ", m_transformation.scale.x);
//FLOAT aspectRatio = 1.0;
//m_projection = DirectX::XMMatrixPerspectiveFovLH(fov, aspectRatio, 1.0f, 100.0f);
m_projection = DirectX::XMMatrixOrthographicLH(800.0, 600.0, 1.0f, 1000.0f);
XMMATRIX matrix = m_projection * m_view * m_model;
XMMATRIX pos = DirectX::XMMatrixSet(
-1.0f, -1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, -1.0f, 1.0f, 1.0f);
pos = pos * m_model;
pos = pos * m_view;
pos = pos * m_projection;
XMFLOAT4X4 pDestination;
DirectX::XMStoreFloat4x4(&pDestination, pos);
return pDestination;
}
其他
_mm_set_ps 是倒叙放置的,参考