ssloy/tinyrenderer

Lesson 04 : Buggy Perspective Projection

aditya-c2512 opened this issue · 1 comments

My current code is similar to the one explained in the commit linked in Lesson 04, but I am not getting perfect results. For reference,
here is my main() :

int main(int argc, char** argv) 
{
    if (3==argc) 
    {
        model = new Model(argv[1],argv[2]);
    } else 
    {
        model = new Model("assets/obj/test.obj","assets/textures/test/diffuse.tga");
    }

    float *zbuffer = new float[width*height];
    for (int i=width*height; i--; zbuffer[i] = -depth);

    Vec3f camera(0,0,3);

    Matrix Projection = Matrix::identity();
    Matrix ViewPort = viewport(width/8, height/8, width*3/4, height*3/4);
    Projection[3][2] = -1.f/camera.z;

    TGAImage image(width, height, TGAImage::RGB);
    Vec3f light_dir(0,0,-1);
    TGAImage diffuse = model->diffuse();
    for (int i=0; i<model->nfaces(); i++) 
    {
        std::vector<int> face = model->face(i);
        Vec3f world_coords[3];
        Vec3f pts[3];
        for (int j=0; j<3; j++) 
        {
            Vec3f v = model->vert(face[j]);
            pts[j] = m2v(ViewPort * Projection * v2m(v));
            // pts[j] = world2screen(v);
            world_coords[j]  = v;
        }
        Vec3f n = cross((world_coords[2]-world_coords[0]),(world_coords[1]-world_coords[0]));
        n.normalize(); 
        float intensity = n*light_dir;
        if(intensity > 0)
        {
            Vec2f uvs[3];
            for (int k=0; k<3; k++) {
                uvs[k] = model->uv(i, k) * diffuse.get_width();
            }
            std::cout << pts[0] << "\t||\t" << pts[1] << "\t||\t" << pts[2] << std::endl;
            triangle(pts, zbuffer, image, diffuse, uvs, intensity);
        }
    }

    image.flip_vertically(); // i want to have the origin at the left bottom corner of the image
    image.write_tga_file("renders/lesson_04.tga");
    delete model;
    return 0;
}

Here are my helper functions
triangle(...):

void triangle(Vec3f *pts, float *zbuffer, TGAImage &image, TGAImage& diffuse, Vec2f* uvs, float intensity) 
{
    Vec2f bboxmin( std::numeric_limits<float>::max(),  std::numeric_limits<float>::max());
    Vec2f bboxmax(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max());
    Vec2f clamp(image.get_width()-1, image.get_height()-1);
    for (int i=0; i<3; i++) 
    {
        for (int j=0; j<2; j++) 
        {
            bboxmin[j] = std::max(0.f,      std::min(bboxmin[j], pts[i][j]));
            bboxmax[j] = std::min(clamp[j], std::max(bboxmax[j], pts[i][j]));
        }
    }
    Vec3f P;
    for (P.x=bboxmin.x; P.x<=bboxmax.x; P.x++) 
    {
        for (P.y=bboxmin.y; P.y<=bboxmax.y; P.y++) 
        {
            Vec3f bc_screen  = barycentric(pts[0], pts[1], pts[2], P);
            if (bc_screen.x<0 || bc_screen.y<0 || bc_screen.z<0) continue;
            P.z = 0;
            Vec2f uv;
            for(int i=0; i<3; i++) 
            {
                P.z += pts[i][2]*bc_screen[i];
                uv.x += uvs[i].x*bc_screen[i];
                uv.y += uvs[i].y*bc_screen[i];
            }
            TGAColor color = diffuse.get(uv.x, uv.y);
            color = TGAColor(color.r * intensity, color.g * intensity, color.b * intensity, 255);
            if (zbuffer[int(P.x+P.y*width)]<P.z) 
            {
                zbuffer[int(P.x+P.y*width)] = P.z;
                image.set(P.x, P.y, color);
            }
        }
    }
}
Vec3f m2v(Matrix41 m) 
{
    return Vec3f(m[0][0]/m[3][0], m[1][0]/m[3][0], m[2][0]/m[3][0]);
}

Matrix41 v2m(Vec3f v) {
    Matrix41 m;
    m[0][0] = v.x;
    m[1][0] = v.y;
    m[2][0] = v.z;
    m[3][0] = 1.f;
    return m;
}

Matrix viewport(int x, int y, int w, int h) {
    Matrix m = Matrix::identity();
    m[0][3] = x+w/2.f;
    m[1][3] = y+h/2.f;
    m[2][3] = depth/2.f;

    m[0][0] = w/2.f;
    m[1][1] = h/2.f;
    m[2][2] = depth/2.f;
    return m;
}

Result :
lesson_04

I am guessing there is something wrong with the Z-Buffer probably but after seeing the contents of z_buffer, I dont see any problems.

NOTE :
For debugging purposes, I replaced the projection steps with a similar step :

for (int j=0; j<3; j++) 
        {
            Vec3f v = model->vert(face[j]);
            pts[j] = world2screen(m2v(Projection * v2m(v)));
            world_coords[j]  = v;
        }

and it now gives the expected result. Only when I use pts[j] = m2v(ViewPort * Projection * v2m(v)); I seem to get the above buggy behavior. What seems to be the issue in my ViewPort matrix?