投影矩阵问题,1:1:1 立方体看起来沿 z 轴拉伸

Problem with Projection Matrix, a 1:1:1 cube looks stretched along the z axis

我正在处理渲染器,我在使用透视投影矩阵时遇到了一些问题。

下面是我的透视投影矩阵

public static Matrix4 Projection(float _zNear, float _zFar, float _Width, float _Height, float _fov)
{
        float _ar = _Width / _Height;
        float _tanHalffov = (float)Math.Tan(Math_of_Rotation.Radians_of(_fov / 2));
        float _zRange = _zFar - _zNear;

        return new Matrix4(new Vector4(1/(_tanHalffov * _ar), 0                 , 0                             , 0),
                           new Vector4(0                    , 1 / _tanHalffov   , 0                             , 0),
                           new Vector4(0                    , 0                 , -(_zFar + _zNear) / _zRange   , 2*_zNear*_zFar / _zRange),
                           new Vector4(0                    , 0                 , 1                             , 0));
}

然后我将它与相机的变换矩阵和模型的变换矩阵相乘。

可以,但是z方向好像有点拉长了,如果我把zFar拉大,拉长就更明显了,所以我想可能是zRange的问题,但是我把它和zRange分开了在矩阵中,所以它不应该重新缩放吗?

以下是我的程序的结果。 the 1:1:1 cube looks weird after the projection even weirder in the corner

---更新---

这是顶点着色器

#version 330 core

layout (location = 0) in vec3 position;
layout (location = 1) in vec2 texCoord;
layout (location = 2) in vec3 normal;

uniform vec3 cam_pos;
uniform mat4 transform;
uniform mat4 nptransform;

vec4 temp_Pos;

out vec3 normal0;
out vec2 texCoord0;
out vec3 cam_angle;
out vec3 position0;

void main()
{
    temp_Pos = nptransform * vec4(position, 1.0);
    position0 = vec3(temp_Pos.x,temp_Pos.y,temp_Pos.z);
    cam_angle = normalize(cam_pos - position0);
    normal0 = normal;
    texCoord0 = texCoord;
    gl_Position = transform * vec4(position, 1.0);//the bug is about this line
}

下面是我的矩阵的完整代码

public Matrix4 GetTransform(Vector3 _OffSet)
{
    return Matrix4.Translation(Position - _OffSet) * Matrix4.RotateX(Rotation.x) * Matrix4.RotateY(Rotation.y) * Matrix4.RotateZ(Rotation.z) * Matrix4.Scale(Scale.x, Scale.y, Scale.z);
}

public Matrix4 GetProjectdTransform(Vector3 _OffSet)//This is the one I sent to the shader.
{
        Transform CameraTransform = Core.The_Camera.Attaching_GameObject.transform;
        
        return Matrix4.Projection(Core.MainCamera.zNear, Core.MainCamera.zFar, Core.MainCamera.Width, Core.MainCamera.Height, Core.MainCamera.fov) * Matrix4.RotateX(CameraTransform.Rotation.x) * Matrix4.RotateY(CameraTransform.Rotation.y) * Matrix4.RotateZ(CameraTransform.Rotation.z) * Matrix4.CameraTranslation(CameraTransform.Position) * GetTransform(_OffSet);
}

还有矩阵函数的细节,不过应该没有问题,我测试了很多次。

    public static Matrix4 CameraTranslation(Vector3 _CameraPosition)
    {
        return new Matrix4(new Vector4(1, 0, 0, -_CameraPosition.x),
                           new Vector4(0, 1, 0, -_CameraPosition.y),
                           new Vector4(0, 0, 1, -_CameraPosition.z),
                           new Vector4(0, 0, 0, 1));
    }

    public static Matrix4 Translation(Vector3 _Position)
    {
        return new Matrix4(new Vector4(1, 0, 0, _Position.x), 
                           new Vector4(0, 1, 0, _Position.y), 
                           new Vector4(0, 0, 1, _Position.z), 
                           new Vector4(0, 0, 0, 1));
    }

    public static Matrix4 Scale(float _x, float _y, float _z)
    {
        return new Matrix4(new Vector4(_x, 0, 0, 0),
                           new Vector4(0, _y, 0, 0),
                           new Vector4(0, 0, _z, 0),
                           new Vector4(0, 0, 0, 1));
    }

    public static Matrix4 RotateX(float _Angle)
    {
        double _Radians = Math_of_Rotation.Radians_of(_Angle);
        return new Matrix4(new Vector4(1, 0, 0, 0), 
                           new Vector4(0, (float)Math.Cos(_Radians), (float)Math.Sin(_Radians), 0), 
                           new Vector4(0, -(float)Math.Sin(_Radians), (float)Math.Cos(_Radians), 0), 
                           new Vector4(0, 0, 0, 1));
    }

    public static Matrix4 RotateY(float _Angle)
    {
        double _Radians = Math_of_Rotation.Radians_of(_Angle);
        return new Matrix4(new Vector4((float)Math.Cos(_Radians), 0, -(float)Math.Sin(_Radians), 0), 
                           new Vector4(0, 1, 0, 0),
                           new Vector4((float)Math.Sin(_Radians), 0, (float)Math.Cos(_Radians), 0), 
                           new Vector4(0, 0, 0, 1));
    }

    public static Matrix4 RotateZ(float _Angle)
    {
        double _Radians = Math_of_Rotation.Radians_of(_Angle);
        return new Matrix4(new Vector4((float)Math.Cos(_Radians), -(float)Math.Sin(_Radians), 0, 0), 
                           new Vector4((float)Math.Sin(_Radians), (float)Math.Cos(_Radians), 0, 0), 
                           new Vector4(0, 0, 1, 0), 
                           new Vector4(0, 0, 0, 1));
    }
    public static Matrix4 Projection(float _zNear, float _zFar, float _Width, float _Height, float _fov)
    {
        float _ar = _Width / _Height;
        float _tanHalffov = (float)Math.Tan(Math_of_Rotation.Radians_of(_fov / 2));
        float _zRange = _zFar - _zNear;
        
        return new Matrix4(new Vector4((_tanHalffov )       , 0                 , 0                             , 0),
                           new Vector4(0                    , _tanHalffov       , 0                             , 0),
                           new Vector4(0                    , 0                 , -(_zFar + _zNear) / _zRange   , 2*_zNear*_zFar / _zRange),
                           new Vector4(0                    , 0                 , 1                             , 0));
    }

如果你想做与Matrix4.CreatePerspectiveFieldOfView相同的事情,你需要转置矩阵并反转一些组件。

public static Matrix4 Projection(float _zNear, float _zFar, float _Width, float _Height, float _fov)
{
    float _ar = _Width / _Height;
    float _tanHalffov = (float)Math.Tan(Math_of_Rotation.Radians_of(_fov / 2));
    float _zRange = _zFar - _zNear;

    return new Matrix4(new Vector4(1/(_tanHalffov * _ar), 0              , 0                          ,  0),
                       new Vector4(0                    , 1 / _tanHalffov, 0                          ,  0),
                       new Vector4(0                    , 0              , -(_zFar + _zNear) / _zRange, -1),
                       new Vector4(0                    , 0              , -2*_zNear*_zFar / _zRange  ,  0));
}

OpenGL 矩阵以列优先顺序存储。第一列是 x 轴,然后是 y 轴和 z 轴。第四栏是翻译。
这意味着每一行 (Vector4) 代表矩阵的一列。

常用的OpenGL坐标系是右手系。在视图 space 中,z 轴指向视线。归一化设备 Space 是一个左手系统。因此 z 轴被投影矩阵反转。参见 Left- vs. Right-handed coordinate systems

您可以在 3D Geometry

找到一个有效的 C#/OpenTK 示例

@Rabbid76 非常感谢您的帮助。我解决了这个问题。答案是……我从来没有错。我 运行 通过大量的测试,我发现答案确实是正确的,而且结果看起来应该是有线的。因为,如果你考虑一下,投影矩阵使 x 和 y 变小,而 fov 变大(更宽的视野意味着更小的物体),但因为 z 是线性函数的输入

(_zFar + _zNear) / (_zFar - _zNear) * z + -2 * _zNear * _zFar / (_zFar - _zNear)

所以当改变 fov 时,立方体的 z 的长度永远不会改变,但 x 和 y 变小,这就是它看起来很奇怪的原因。

@Rabbid76 好心提醒我,我想是因为我的游戏引擎也是左手系统,所以矩阵不一样。

证明:Cube in unity also looks wired when the fov is 90

并且:So as mine