21

I develop a simple 3D engine (Without any use of API), successfully transformed my scene into world and view space but have trouble projecting my scene (from view space) using the perspective projection matrix (OpenGL style). I'm not sure about the fov, near and far values and the scene I get is distorted. I hope if someone can direct me how to build and use the perspective projection matrix properly with example codes. Thanks in advance for any help.

The matrix build:

double f = 1 / Math.Tan(fovy / 2);
return new double[,] { 

    { f / Aspect, 0, 0, 0 },
    { 0, f, 0, 0 },
    { 0, 0, (Far + Near) / (Near - Far),  (2 * Far * Near) / (Near - Far) }, 
    { 0, 0, -1, 0 } 
};

The matrix use:

foreach (Point P in T.Points)
{     
    .
    .     // Transforming the point to homogen point matrix, to world space, and to view space (works fine)
    .     

    // projecting the point with getProjectionMatrix() specified in the previous code :      

    double[,] matrix = MatrixMultiply( GetProjectionMatrix(Fovy, Width/Height, Near, Far) , viewSpacePointMatrix );

    // translating to Cartesian coordinates (from homogen):

    matrix [0, 0] /= matrix [3, 0];
    matrix [1, 0] /= matrix [3, 0];
    matrix [2, 0] /= matrix [3, 0];
    matrix [3, 0] = 1;
    P = MatrixToPoint(matrix);

    // adjusting to the screen Y axis:

    P.y = this.Height - P.y;

    // Printing...
}
reznic
  • 281
  • 1
  • 3
  • 5
  • http://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographic-projection-matrix I also recommend the previous lessons (projecting points and 3D viewing). – user18490 Dec 26 '14 at 11:22

2 Answers2

33

Following is a typical implemenation of perspective projection matrix. And here is a good link to explain everything OpenGL Projection Matrix

void ComputeFOVProjection( Matrix& result, float fov, float aspect, float nearDist, float farDist, bool leftHanded /* = true */ )
{
    //
    // General form of the Projection Matrix
    //
    // uh = Cot( fov/2 ) == 1/Tan(fov/2)
    // uw / uh = 1/aspect
    // 
    //   uw         0       0       0
    //    0        uh       0       0
    //    0         0      f/(f-n)  1
    //    0         0    -fn/(f-n)  0
    //
    // Make result to be identity first

    // check for bad parameters to avoid divide by zero:
    // if found, assert and return an identity matrix.
    if ( fov <= 0 || aspect == 0 )
    {
        Assert( fov > 0 && aspect != 0 );
        return;
    }

    float frustumDepth = farDist - nearDist;
    float oneOverDepth = 1 / frustumDepth;

    result[1][1] = 1 / tan(0.5f * fov);
    result[0][0] = (leftHanded ? 1 : -1 ) * result[1][1] / aspect;
    result[2][2] = farDist * oneOverDepth;
    result[3][2] = (-farDist * nearDist) * oneOverDepth;
    result[2][3] = 1;
    result[3][3] = 0;
}
Wayne Wang
  • 1,287
  • 12
  • 21
  • Sorry but what is uh and uw here? User width and user height? – ReX357 Feb 14 '14 at 00:56
  • 2
    @ReX357 uw = near/right, and uh = near/top, where right is the coordinates of right clip plan and top is the coordinates of top clip plane. As the above perspective projection is symmetric, so right = half of horizon width and top = half of vertical height, then uw/uh = top/right = height/width = 1/aspect – Wayne Wang Feb 15 '14 at 09:38
  • isn't the z usually multiplied by -1 in opengl? –  Aug 23 '15 at 03:27
  • @racarate I think opengl use right hand coordinate system, but directx use left hand. If you turn opengl to left hand coordinate system for consistent with directx, then you multiple z by -1. The above calculation already take care of this. (the leftHand parameter you passed to the function) – Wayne Wang Sep 20 '15 at 16:41
  • @WayneWang, I was reading your link, but I got stuck at "Next, we map xp and yp to xn and yn of NDC with linear relationship; [l, r] ⇒ [-1, 1] and [b, t] ⇒ [-1, 1]."[here is the equation](http://www.songho.ca/opengl/files/gl_projectionmatrix_eq04.png) can you explain to me why are we adding **beta** and why are we substituting xp = r and xn = 1? – watashiSHUN Oct 13 '15 at 17:26
  • @watashiSHUN, It is a multiple step to illustrate how perspective projection get calculated. xp, yp is the point projected on near plane which range from (l, r), the NDC space range from (-1, 1). So that is why the linear mapping get applied. The beta is added because the view frustum may be asymmetric. Usually, the view frustum is symmetric so beta equal to 0. You can get this info by continuing reading that article. – Wayne Wang Oct 24 '15 at 03:17
3

Another function that may be useful.

This one is based on left/right/top/bottom/near/far parameters (used in OpenGL):

static void test(){
    float projectionMatrix[16];

    // width and height of viewport to display on (screen dimensions in case of fullscreen rendering)
    float ratio = (float)width/height;
    float left = -ratio;
    float right = ratio;
    float bottom = -1.0f;
    float top = 1.0f;
    float near = -1.0f;
    float far = 100.0f;

    frustum(projectionMatrix, 0, left, right, bottom, top, near, far);

}

static void frustum(float *m, int offset,
                     float left, float right, float bottom, float top,
                     float near, float far) {

    float r_width  = 1.0f / (right - left);
    float r_height = 1.0f / (top - bottom);
    float r_depth  = 1.0f / (far - near);
    float x =  2.0f * (r_width);
    float y =  2.0f * (r_height);
    float z =  2.0f * (r_depth);
    float A = (right + left) * r_width;
    float B = (top + bottom) * r_height;
    float C = (far + near) * r_depth;
    m[offset + 0] = x;
    m[offset + 3] = -A;
    m[offset + 5] = y;
    m[offset + 7] = -B;
    m[offset + 10] = -z;
    m[offset + 11] = -C;
    m[offset +  1] = 0.0f;
    m[offset +  2] = 0.0f;
    m[offset +  4] = 0.0f;
    m[offset +  6] = 0.0f;
    m[offset +  8] = 0.0f;
    m[offset +  9] = 0.0f;
    m[offset + 12] = 0.0f;
    m[offset + 13] = 0.0f;
    m[offset + 14] = 0.0f;
    m[offset + 15] = 1.0f;

}
vir us
  • 9,920
  • 6
  • 57
  • 66