我开始使用DirectX(和SharpDX,因此仅在C#/ hlsl中编程)并且我正在尝试构建自己的相机类。它应该是可旋转的,允许向前和向后移动以及“侧向”运动(经典的第一人称运动通常映射到A和D,在我的情况下加上和向下)。为了更容易错误修正模型和世界空间在我的情况下是相同的,透视投影尚未实现,旋转相机,我的相机应该在正Z轴(进入屏幕)。我的错误修正模型是一个简单的四边形,宽度和高度为1.f,z = 0并且在屏幕上居中。
为了便于使用,我找到了DirectX的Matrix.LookAtLH()
并使用它来构建我的矩阵,用于从世界到视图坐标的转换,基于我的相机在世界坐标中的位置,向上矢量(现在 - 没有旋转 - 总是正Y轴)和世界坐标中的目标点
我的(顶点)着色器使用此矩阵的简单乘法:
output.position = mul(position, worldToView);
LookAt-Matrix计算如下:
Matrix.LookAtLH(vec3(0, 0, -1), vec3(0, 0, 0.5f), vec3(0, 1, 0))
导致这张图片:
现在我想将相机移到右边,在这种情况下,将1.f添加到它的X坐标。 我的预期结果与之前相同的四边形,向左移动了一点。左侧构造了一个新的LootAt矩阵,通过相同的向量移动眼睛坐标和目标坐标: />
Matrix.LookAtLH(vec3(1, 0, -1), vec3(1, 0, 0.5f), vec3(0, 1, 0))
结果如下:
我移动相机越多越好,但屏幕中心仍然保持四边形的中心。这与我对Matrix.LookAtLH
答案 0 :(得分:5)
当您使用D3DX函数时,您必须在将矩阵发送到着色器之前转置矩阵。
来自here的更深入的解释:
在线性代数中,使用标准矩阵乘法算法将向量和矩阵相乘。因此,有一些关于操作顺序和所涉及的矩阵“形状”的规则。数学家通常将向量视为包含单列元素的矩阵,翻译乘法看起来像这样:
[ 0, 0, 0, tx] [ x]
[ 0, 0, 0, ty] *[ y]
[ 0, 0, 0, tz] [ z]
[ 0, 0, 0, 1] [ 1]
首先请注意,矩阵乘法会根据此简单规则生成特定行/列配置的结果:
AxB * BxC = AxC。
换句话说,大小为A行和B列的矩阵乘以B行和C列的矩阵将产生A行和C列的矩阵。而且,为了正确地相乘,B必须对两者都相等。在这种情况下,我们有4x4 * 4x1,它产生4x1或另一个列向量。如果我们改变了乘法的顺序,它将是4x1 * 4x4,这将是非法的。
然而,计算机科学家经常将向量视为具有单行的矩阵。这有几个原因,但通常是因为单行代表单个线性内存块或单维数组,因为数组通常被称为数组[row] [column]。为了避免在代码中使用二维数组,人们简单地使用“行向量”。因此,为了使用矩阵乘法获得所需的结果,我们将顺序交换为1x4 * 4x4 = 1x4,或向量*矩阵:
[ x, y, z, 1] * [ 0, 0, 0, 0]
[ 0, 0, 0, 0]
[ 0, 0, 0, 0]
[ x, y, z, 1]
注意如何移动平移矩阵的x,y,z元素以保留乘法的正确结果(在这种情况下,它被转置)。
使用列向量时,典型的操作转换顺序为P * V * W * v,因为列向量必须最后才能产生正确的结果。请记住,矩阵乘法是相关的,而不是可交换的,所以为了实现由世界变换的矢量的适当结果,转换成视图空间,转换成同质的屏幕空间,我们必须按顺序相乘。这给了我们(使用关联性)P *(V *(W * v)),因此从内部parens到外部parens,世界变换首先,下一个视图,下一个投影。
如果我们使用行向量,则乘法如下:v * W * V * P.使用关联性,我们意识到它只是相同的操作顺序:((v * W)* V)* P.或者世界第一,然后观察,然后投射。
两种形式的乘法同样有效,DX库选择使用后者,因为它匹配内存布局模式,并允许您从左到右读取转换顺序。
HLSL支持两种操作订单。 “*”运算符通过元素缩放执行简单的元素,它不执行矩阵乘法。这是使用“mul()”内在操作执行的。如果将4元素向量作为第一个参数传递给mul()内部函数,则假定您希望将其视为“行向量”。因此,您必须提供已按正确顺序相乘的矩阵,并使用正确的行/列格式提供。这是使用DX效果参数从DX库传递矩阵时的默认行为。如果将4元素向量作为第二个参数提供给mul()内在函数,它会将其视为列向量,并且必须为列向量提供正确形成和相乘的矩阵。