I'm currently trying to write a C 3D software rendering engine from scratch just for fun and to have an insight on what OpenGL does behind the scene and what 90's programmers had to do on DOS.

I have written my own matrix library and tested it without noticing any issues, but when I tried projecting the vertices of a simple 2x2 cube at 0,0 as seen by a basic camera at 0,0,10, the cube seems to appear way bigger than the application's window. If I scale the vertices' coordinates down by 8 times I can see a proper cube centered on the screen. This cube doesn't seem to be in perspective: wheen seen from the front, the back vertices pe rfectly overlap with the front ones, so I'm quite sure it's not correct.

this is how I create the view and projection matrices (vec4_initd initializes the vectors with w=0, vec4_initw initializes the vectors with w=1):

void mat4_lookatlh(mat4 *m, const vec4 *pos, const vec4 *target, const vec4 *updirection) {
    vec4 fwd, right, up;

    // fwd = norm(pos - target)
    fwd = *target;
    vec4_sub(&fwd, pos);

    // right = norm(cross(updirection, fwd))
    vec4_cross(updirection, &fwd, &right);

    // up = cross(right, forward)
    vec4_cross(&fwd, &right, &up);

    // orientation and translation matrices combined
    vec4_initd(&m->a, right.x, up.x, fwd.x);
    vec4_initd(&m->b, right.y, up.y, fwd.y);
    vec4_initd(&m->c, right.z, up.z, fwd.z);
    vec4_initw(&m->d, -vec4_dot(&right, pos), -vec4_dot(&up, pos), -vec4_dot(&fwd, pos));

void mat4_perspectivefovrh(mat4 *m, float fovdegrees, float aspectratio, float near, float far) {
    float h = 1.f / tanf(ftoradians(fovdegrees / 2.f));
    float w = h / aspectratio;

    vec4_initd(&m->a, w, 0.f, 0.f);
    vec4_initd(&m->b, 0.f, h, 0.f);
    vec4_initw(&m->c, 0.f, 0.f, -far / (near - far));
    vec4_initd(&m->d, 0.f, 0.f, (near * far) / (near - far));

this is how I project my vertices:

void device_project(device *d, const vec4 *coord, const mat4 *transform, int *projx, int *projy) {
    vec4 result;

    mat4_mul(transform, coord, &result);
    *projx = result.x * d->w + d->w / 2;
    *projy = result.y * d->h + d->h / 2;

void device_rendervertices(device *d, const camera *camera, const mesh meshes[], int nmeshes, const rgba *color) {
    int i, j;
    mat4 view, projection, world, transform, projview;
    mat4 translation, rotx, roty, rotz, transrotz, transrotzy;
    int projx, projy;

    // vec4_unity = (0.f, 1.f, 0.f, 0.f)
    mat4_lookatlh(&view, &camera->pos, &camera->target, &vec4_unity);
    mat4_perspectivefovrh(&projection, 45.f, (float)d->w / (float)d->h, 0.1f, 1.f);

    for (i = 0; i < nmeshes; i++) {
        // world matrix = translation * rotz * roty * rotx
        mat4_translatev(&translation, meshes[i].pos);
        mat4_rotatex(&rotx, ftoradians(meshes[i].rotx));
        mat4_rotatey(&roty, ftoradians(meshes[i].roty));
        mat4_rotatez(&rotz, ftoradians(meshes[i].rotz));
        mat4_mulm(&translation, &rotz, &transrotz); // transrotz = translation * rotz
        mat4_mulm(&transrotz, &roty, &transrotzy); // transrotzy = transrotz * roty = translation * rotz * roty
        mat4_mulm(&transrotzy, &rotx, &world); // world = transrotzy * rotx = translation * rotz * roty * rotx

        // transform matrix
        mat4_mulm(&projection, &view, &projview); // projview = projection * view
        mat4_mulm(&projview, &world, &transform); // transform = projview * world = projection * view * world

        for (j = 0; j < meshes[i].nvertices; j++) {
            device_project(d, &meshes[i].vertices[j], &transform, &projx, &projy);
            device_putpixel(d, projx, projy, color);

this is how the cube and camera are initialized:

// test mesh
cube = &meshlist[0];
mesh_init(cube, "Cube", 8);
cube->rotx = 0.f;
cube->roty = 0.f;
cube->rotz = 0.f;
vec4_initw(&cube->pos, 0.f, 0.f, 0.f);
vec4_initw(&cube->vertices[0], -1.f, 1.f, 1.f);
vec4_initw(&cube->vertices[1], 1.f, 1.f, 1.f);
vec4_initw(&cube->vertices[2], -1.f, -1.f, 1.f);
vec4_initw(&cube->vertices[3], -1.f, -1.f, -1.f);
vec4_initw(&cube->vertices[4], -1.f, 1.f, -1.f);
vec4_initw(&cube->vertices[5], 1.f, 1.f, -1.f);
vec4_initw(&cube->vertices[6], 1.f, -1.f, 1.f);
vec4_initw(&cube->vertices[7], 1.f, -1.f, -1.f);

// main camera
vec4_initw(&maincamera.pos, 0.f, 0.f, 10.f);
maincamera.target = vec4_zerow;

and, just to be sure, this is how I compute matrix multiplications:

void mat4_mul(const mat4 *m, const vec4 *va, vec4 *vb) {
    vb->x = m->a.x * va->x + m->b.x * va->y + m->c.x * va->z + m->d.x * va->w;
    vb->y = m->a.y * va->x + m->b.y * va->y + m->c.y * va->z + m->d.y * va->w;
    vb->z = m->a.z * va->x + m->b.z * va->y + m->c.z * va->z + m->d.z * va->w;
    vb->w = m->a.w * va->x + m->b.w * va->y + m->c.w * va->z + m->d.w * va->w;

void mat4_mulm(const mat4 *ma, const mat4 *mb, mat4 *mc) {
     mat4_mul(ma, &mb->a, &mc->a);
     mat4_mul(ma, &mb->b, &mc->b);
     mat4_mul(ma, &mb->c, &mc->c);
     mat4_mul(ma, &mb->d, &mc->d);
  • 1
    \$\begingroup\$ I haven't read thoroughly through your code, but make sure all your matrix operations use the same row-column conventions. You might accidentally be using a transposed matrix. Also, I would suggest using a library such as GLM for math. \$\endgroup\$ – d3dave Jun 5 '14 at 10:00

It is not in perspective because device_project (...) does not divide by w.

Given a typical perspective projection matrix, the z component of the last row/column is usually +/- 1.0 and everything else in the row/column is 0.0.

OpenGL Perspective Projection Matrix (right-handed eye-space --> left-handed clip-space):

(source: songho.ca)

This produces w = +/-z in the transformed vertex (depending on whether the projection matrix is setup to flip the Z-axis - GL flips the Z-axis, D3D does not). Thus the homogeneous divide will actually scale geometry based on distance down the Z-axis and this is why it is sometimes called the perspective divide.

| improve this answer | |

Not the answer you're looking for? Browse other questions tagged or ask your own question.