使用偏航角和俯仰角创建旋转矩阵并模拟FPS相机

6
首先,我必须说我想在这里做的一切都与点有关,而不是对象或其他任何东西。这里我使用OpenGL更多地是为了计算。
所以这是我的问题。我想在我的2D上下文中(例如winform或图像)显示一些3d点;为此,我编写了这个函数:(它是用C#编写的,并且使用OpenTK(OpenGL包装器),但我相信如果您了解OpenGL,您可以理解它,无论您对C#的了解如何)
    private Vector3 GetProjectedLocation(
        Vector3 pointPosition,
        Vector3 cameraPosition,
        Matrix4 cameraRotationMatrix)
    {
        float horizentalFov = MathHelper.DegreesToRadians(60.0f);
        float viewRatio = (float)this.RenderSize.Width / this.RenderSize.Height;
        Vector3 cameraReference = new Vector3(0, 0, -1);
        Vector3 transformedReference = Vector3.Transform(cameraReference, Matrix4.Invert(cameraRotationMatrix));
        Vector3 cameraLookatTarget = cameraPosition + transformedReference;
        Matrix4 modelMatrix = Matrix4.Identity;
        Matrix4 viewMatrix = Matrix4.LookAt(cameraPosition, cameraLookatTarget, new Vector3(0, 1, 0));
        Matrix4 projectionMatrix = Matrix4.CreatePerspectiveFieldOfView(horizentalFov, viewRatio, 0.1f, 100.0f);
        Matrix4 viewModelMatrix = viewMatrix * modelMatrix;
        return Helper.Project(
            pointPosition,
            viewModelMatrix,
            projectionMatrix,
            new Rectangle(new Point(), this.RenderSize));
    }

我还写了另一个名为Project的函数,因为我在OpenTK中没有找到它:

    public static Vector3 Project(Vector3 point, Matrix4 viewmodel, Matrix4 projection, Rectangle viewport)
    {
        Vector4 point4 = new Vector4(point, 1.0f);
        Vector4 pointModel = Vector4.Transform(point4, viewmodel);
        Vector4 pointProjection = Vector4.Transform(pointModel, projection);
        pointProjection.W = (float)(1.0 / pointProjection.W);
        pointProjection.X *= pointProjection.W;
        pointProjection.Y *= pointProjection.W;
        pointProjection.Z *= pointProjection.W;
        return new Vector3
                   {
                       X = (float)((pointProjection.X * 0.5 + 0.5) * viewport.Width + viewport.X),
                       Y = (float)((pointProjection.Y * 0.5 + 0.5) * viewport.Height + viewport.Y),
                       Z = (float)((1.0 + pointProjection.Z) * 0.5)
                   };
    }

当yaw、pitch和roll这三个值都为零时,似乎它的工作是正常的。我可以通过改变yaw水平移动点,使用pitch值垂直移动点。Roll也使它运动到应该去的方向。
问题出现在当我将pitch设置为90度时。在这种情况下,yaw不是水平移动它,而是垂直移动它,更糟的是,只能向下移动它。无论我向正侧还是负侧(-20度或+20度),它总是向下移动。有趣的是,当pitch为90度时,Roll也具有相同的影响,它只会垂直向下移动,就像yaw一样。将pitch设置为-90度具有相同的效果,但是Yaw和roll这次只会将其向上移动。
看起来像万向节锁问题,其中我的两个轴平行,我丢失了一个方向,这个方向在这种情况下是roll。尽管如此,我仍然不明白为什么yaw只在一个方向上工作。
无论如何,问题在于当竖直查看时发生了这种锁定,这也是我的程序大部分时间查看的地方。我已经阅读过如果改变旋转顺序,可以将万向节锁移到顶部(不那么频繁的查看位置)。我尝试了很多次序,但都没有找到一个好的方法。所以这是我的代码,也许我做错了:
    private Matrix4 CreateRotationMatrix(char axis, float radians)
    {
        float c = (float)Math.Cos(radians);
        float s = (float)Math.Sin(radians);

        if (axis == 'X')
        {
            return new Matrix4(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, c, -s, 0.0f, 0.0f, s, c, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
        }
        if (axis == 'Y')
        {
            return new Matrix4(c, 0.0f, s, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, -s, 0.0f, c, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
        }
        return new Matrix4(c, -s, 0.0f, 0.0f, s, c, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
    }

    private Matrix4 MatrixFromEulerAngles(Vector3 euler, string order)
    {
        return CreateRotationMatrix(order[2], GetEulerAngle(order[2], euler))
               * CreateRotationMatrix(order[1], GetEulerAngle(order[1], euler))
               * CreateRotationMatrix(order[0], GetEulerAngle(order[0], euler));
    }

    private float GetEulerAngle(char angle, Vector3 euler)
    {
        if (angle == 'X') return euler.X;
        if (angle == 'Y') return euler.Y;
        return angle == 'Z' ? euler.Z : 0f;
    }

你有任何想法吗?

这是我知道的顺序: "XYZ", "XZY", "YXZ", "YZX", "ZXY", "ZYX"

我可以提供一个示例程序,让你自己看到问题。


当俯仰角为90度,其他角度为0时,点位于最高位置,对吧?所以它只能往下走。在某些位置上,您可能会遇到万向锁定(意味着两个不同角度的变化会导致相同的运动)。这取决于您的定义。我还没有检查过它们,但它们似乎没问题。 - Nico Schertler
我理解你的观点。我已经阅读过相关内容。我不确定这是否是问题所在。但如果是的话,我的真正问题是我想从其他来源读取这些信息。在这个例子中,它将从iPhone的传感器中读取,因此我无法获得我想要的结果。我知道在某些位置只能向下移动。但为什么滚动会使其向下移动?当角度达到80度时,为什么我失去了水平运动(偏航)?这就是我不明白的地方。我尝试使用iPhone直接提供的旋转矩阵和旋转四元数,但也没有成功。所以我想也许问题在于我的代码。 - Soroush Falahati
这也可能是因为iPhone使用与您的代码不同的旋转顺序。您应该检查一下。 - Nico Schertler
@NicoSchertler 不,不是那个问题。我已经用简单的偏航、俯仰和滚动滑块进行了测试,问题仍然存在。 - Soroush Falahati
你为什么不使用顶点着色器? - CSharpie
1个回答

1
我认为问题的一部分来自于您在第一个代码块中使用的LookAt函数。这些函数的工作方式是通过查看与您正在查看的方向垂直的全局“上”向量的部分来计算本地“上”向量。在您向上查看时,全局“上”向量(在上面的代码中为(0,1,0))没有垂直于查看方向的分量,结果可能是未定义或不稳定的。当您向下查看时也是如此。

相反,尝试将LookAt函数的“up”向量计算为:

Vector3 localUpVec = Vector3.Transform(new Vector3(0, 1, 0), Matrix4.Invert(cameraRotationMatrix));
Matrix4 viewMatrix = Matrix4.LookAt(cameraPosition, cameraLookatTarget, localUpVec);

甚至更好的是,您可以完全绕过LookAt函数并自己计算:

Matrix4 viewMatrix = Matrix4.Subtract( Matrix4.Transpose(cameraRotationMatrix), Matrix4.Translation(cameraPosition) );

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接