Search code examples
cmatrix3dprojection

3D perspective projection coordinates in clip space


i am trying to 3D project a few points. from what i have read, after the projection matrix is applied to a vertex, the vertex ends up in clip space. at this point if -w < x,y,z < w the vertex is visible, else it is outside the visible area and it needs to be clipped. the problem i have is that i cant get a vertex to be -w < x,y,z < w. i must be doing something wrong but i cant figure out what it is. i try to stick to openGL conventions where possible (coordinate system etc.). please have a look at this SSCCE.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define DEG2RAD 0.01745329f

// multiplies two matrices
void m3d_matrixMul(const float matrix1[], const float matrix2[], float destination[])
{
    int i, row, col;

    for (i = 15; i >= 0; i--)
    {
        row = i % 4;
        col = i / 4 * 4;
        destination[i] = matrix1[row] * matrix2[col] + matrix1[row + 4] * matrix2[col + 1] + matrix1[row + 8] * matrix2[col + 2] + matrix1[row + 12] * matrix2[col + 3];
    }
}

// multiplies matrix with vector/vertex
void m3d_matVecMul(const float matrix[], const float vector[], float destination[])
{
    float x = vector[0], y = vector[1], z = vector[2], w = vector[3];

    destination[0] = x * matrix[0] + y * matrix[4] + z * matrix[8] + w * matrix[12];
    destination[1] = x * matrix[1] + y * matrix[5] + z * matrix[9] + w * matrix[13];
    destination[2] = x * matrix[2] + y * matrix[6] + z * matrix[10] + w * matrix[14];
    destination[3] = x * matrix[3] + y * matrix[7] + z * matrix[11] + w * matrix[15];
}

// creates the projection matrix (column major)
void m3d_getFrustum(float left, float right, float bottom, float top, float near, float far, float matrix[])
{
    matrix[0] = 2.0f * near / (right - left);
    matrix[1] = 0.0f;
    matrix[2] = 0.0f;
    matrix[3] = 0.0f;
    matrix[4] = 0.0f;
    matrix[5] = 2.0f * near / (top - bottom);
    matrix[6] = 0.0f;
    matrix[7] = 0.0f;
    matrix[8] = (right + left) / (right - left);
    matrix[9] = (top + bottom) / (top - bottom);
    matrix[10] = -(far + near) / (far - near);
    matrix[11] = -1.0f;
    matrix[12] = 0.0f;
    matrix[13] = 0.0f;
    matrix[14] = -2.0f * far * near / (far - near);
    matrix[15] = 0.0f;
}

void m3d_getPerspective(float vfov, float aspect, float near, float far, float matrix[])
{
    float height = tanf(vfov * 0.5f * DEG2RAD) * near;
    float width = aspect * height;
    m3d_getFrustum(-width, width, -height, height, near, far, matrix);
}

void m3d_getIdentity(float matrix[])
{
    matrix[0] = 1.0f;
    matrix[1] = 0.0f;
    matrix[2] = 0.0f;
    matrix[3] = 0.0f;
    matrix[4] = 0.0f;
    matrix[5] = 1.0f;
    matrix[6] = 0.0f;
    matrix[7] = 0.0f;
    matrix[8] = 0.0f;
    matrix[9] = 0.0f;
    matrix[10] = 1.0f;
    matrix[11] = 0.0f;
    matrix[12] = 0.0f;
    matrix[13] = 0.0f;
    matrix[14] = 0.0f;
    matrix[15] = 1.0f;
}

void m3d_translate(float x, float y, float z, float matrix[])
{
    matrix[12] += x;
    matrix[13] += y;
    matrix[14] += z;
}

int main(void)
{
    float vertex[4] = {0.5f, 0.5f, 0.5f, 1.0f};
    float modelView[16];
    float projection[16];
    float mvp[16];
    char buffer[4];

    // initialize model-view matrix with identity matrix
    m3d_getIdentity(modelView);
    // add z-translation to model-view matrix
    m3d_translate(0.0f, 0.0f, -2.0f, modelView);
    // get perspective projection matrix
    m3d_getPerspective(45.0f, 800.0f / 600.0f, -0.5f, -1000.0f, projection);
    // fuse projection and model-view matrix into one matrix
    m3d_matrixMul(projection, modelView, mvp);
    // apply model-view-projection matrix to vertex
    m3d_matVecMul(mvp, vertex, vertex);

    printf("Projected vertex: %f, %f, %f, %f\n", vertex[0], vertex[1], vertex[2], vertex[3]);
    printf("Press ENTER to quit.");
    fgets(buffer, 4, stdin);
    return EXIT_SUCCESS;

    // Projected vertex: 0.905330, 1.207107, 2.502001, 1.500000
}

thx in advance.


Solution

  • Okay, i have looked at your code again and i think there is something wrong with your projection matrix calculation. I have replaced that m3d_getPerspective method with the definition from gluPerspective and added a printMatrix (pm) method. For some example points the results seem reasonable now (code below).

    I have not specifically found your bug as i am not sure why exactly you do this frustum - workaround. A hint may be that you seemed to use a tanf where i now used a cotangens, but this wasn't the complete problem i think. Also, setting near and far to negative values is not intuitive as they should represent distances. Your multiplications seem to be okay, i did not recognize you were copying the vertex to x,y,z,w at first.

    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    
    #define DEG2RAD 0.01745329f
    
    void m3d_matrixMul(const float matrix1[], const float matrix2[], float destination[])
    {
        int i, row, col;
    
        for (i = 15; i >= 0; i--)
        {
            row = i % 4;
            col = i / 4 * 4;
            destination[i] = matrix1[row] * matrix2[col] + matrix1[row + 4] * matrix2[col + 1] + matrix1[row + 8] * matrix2[col + 2] + matrix1[row + 12] * matrix2[col + 3];
        }
    }
    
    void m3d_matVecMul(const float matrix[], const float vector[], float destination[])
    {
        float x = vector[0], y = vector[1], z = vector[2], w = vector[3];
    
        destination[0] = x * matrix[0] + y * matrix[4] + z * matrix[8] + w * matrix[12];
        destination[1] = x * matrix[1] + y * matrix[5] + z * matrix[9] + w * matrix[13];
        destination[2] = x * matrix[2] + y * matrix[6] + z * matrix[10] + w * matrix[14];
        destination[3] = x * matrix[3] + y * matrix[7] + z * matrix[11] + w * matrix[15];
    }
    
    //not used anymore
    void m3d_getFrustum(float left, float right, float bottom, float top, float near, float far, float matrix[])
    {
        matrix[0] = 2.0f * near / (right - left);
        matrix[1] = 0.0f;
        matrix[2] = 0.0f;
        matrix[3] = 0.0f;
        matrix[4] = 0.0f;
        matrix[5] = 2.0f * near / (top - bottom);
        matrix[6] = 0.0f;
        matrix[7] = 0.0f;
        matrix[8] = (right + left) / (right - left);
        matrix[9] = (top + bottom) / (top - bottom);
        matrix[10] = -(far + near) / (far - near);
        matrix[11] = -1.0f;
        matrix[12] = 0.0f;
        matrix[13] = 0.0f;
        matrix[14] = -2.0f * far * near / (far - near);
        matrix[15] = 0.0f;
    }
    
    void m3d_getPerspective(float vfov, float aspect, float near, float far, float matrix[])
    {
        float f = 1.0f/tanf(vfov * 0.5f * DEG2RAD);
    
        matrix[0] = f/aspect;
        matrix[1] = 0.0f;
        matrix[2] = 0.0f;
        matrix[3] = 0.0f;
    
        matrix[4] = 0.0f;
        matrix[5] = f;
        matrix[6] = 0.0f;
        matrix[7] = 0.0f;
    
        matrix[8] = 0;
        matrix[9] = 0;
        matrix[10] = (near+far)/(near-far);
        matrix[11] = -1;
    
        matrix[12] = 0.0f;
        matrix[13] = 0.0f;
        matrix[14] = 2.0f * far * near / (near-far);
        matrix[15] = 0.0f;
    
    
    
    }
    
    void m3d_getIdentity(float matrix[])
    {
        matrix[0] = 1.0f;
        matrix[1] = 0.0f;
        matrix[2] = 0.0f;
        matrix[3] = 0.0f;
        matrix[4] = 0.0f;
        matrix[5] = 1.0f;
        matrix[6] = 0.0f;
        matrix[7] = 0.0f;
        matrix[8] = 0.0f;
        matrix[9] = 0.0f;
        matrix[10] = 1.0f;
        matrix[11] = 0.0f;
        matrix[12] = 0.0f;
        matrix[13] = 0.0f;
        matrix[14] = 0.0f;
        matrix[15] = 1.0f;
    }
    
    void m3d_translate(float x, float y, float z, float matrix[])
    {
        matrix[12] += x;
        matrix[13] += y;
        matrix[14] += z;
    }
    
    void pm(float* m) {
        printf("%f, %f, %f, %f\n", m[0], m[4], m[8], m[12]);
        printf("%f, %f, %f, %f\n", m[1], m[5], m[9], m[13]);
        printf("%f, %f, %f, %f\n", m[2], m[6], m[10], m[14]);
        printf("%f, %f, %f, %f\n", m[3], m[7], m[11], m[15]);
        printf("\n");
    }
    
    int main(void)
    {
        //float vertex[4] = {0.0f, 0.0f, -1000.0f, 1.0f}; //point at center & far
        //float vertex[4] = {0.0f, 0.0f, -0.5f, 1.0f}; //point at center & near
        float vertex[4] = {(800.0f/600.0f)*1.0f, 1.0f, -2.0f, 1.0f}; //point at one quater of the x and y range
        float modelView[16];
        float projection[16];
        float mvp[16];
        char buffer[4];
    
        m3d_getIdentity(modelView);
        pm(modelView);
        m3d_translate(0.0f, 0.0f, 0.0f, modelView); 
        pm(modelView);
        m3d_getPerspective(90.0f, 800.0f / 600.0f, 0.5f, 1000.0f, projection);
        pm(projection);
        m3d_matrixMul(projection, modelView, mvp);
        pm(mvp);
        m3d_matVecMul(mvp, vertex, vertex);
    
        printf("Projected vertex: %f, %f, %f, %f\n", vertex[0], vertex[1], vertex[2], vertex[3]);
        printf("Press ENTER to quit.");
        fgets(buffer, 4, stdin);
        return EXIT_SUCCESS;
    }