Search code examples
c#matrixopenglgame-enginerenderer

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


I am working on a renderer, and I am having some troubles with the perspective projection matrix.

Following is my perspective projection matrix.

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));
}

I then multiplied it with the camera's transform matrix and the model's transform matrix.

it works but the z direction seems stretched a bit, and if I make zFar larger, the stretching is even more obvious, so I figured it may be something to do with the zRange, but I have divided it with zRange in the matrix, so isn't it supposed to be rescaled?

Following is the result of my program. the 1:1:1 cube looks weird after the projection even weirder in the corner

---Update---

This is the vertex shader

#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
}

The following is the complete code of my matrix

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);
}

And there is the detail of the matrix functions, but there shouldn't be any problem, I tested them a lot of times.

    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));
    }

Solution

  • @Rabbid76 Thanks very very much for your helps. I solved the question. The answer is...... I was never wrong. I ran through tons of test, and I found that the answer were in deed correct, and the outcome is suppose to look that wired. Cause, if you think about it the projection matrix makes x and y smaller while fov is larger(wider view means smaller object), but cause z is the input of a linear function

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

    so when changing fov the length of a cube's z is never change but the x and y is smaller, that's why it looks weird.

    As the hints @Rabbid76 so kindly to remind me, I think cause my game engine is also left handed system, so the matrix is different.

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

    And: So as mine