ssloy/tinyrenderer

Lesson 03 : Texture Reading Homework

aditya-c2512 opened this issue · 7 comments

I have implemented a few of my own functions in model.cpp and used them to get back the texture coordinates. However, I am not getting the required output from the whole operation.
model.cpp:

#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
#include "model.h"

Model::Model(const char *model_file, const char *diffuse_file) : verts_(), faces_() {
    std::ifstream in;
    in.open (model_file, std::ifstream::in);
    if (in.fail()) return;
    std::string line;
    while (!in.eof()) {
        std::getline(in, line);
        std::istringstream iss(line.c_str());
        char trash;
        if (!line.compare(0, 2, "v ")) {
            iss >> trash;
            Vec3f v;
            for (int i=0;i<3;i++) iss >> v[i];
            verts_.push_back(v);
        } 
        else if(!line.compare(0, 3, "vt "))
        {
            iss >> trash >> trash;
            Vec2f uv;
            for(int i = 0; i < 2; i++) iss >> uv[i];
            tex_coords_.push_back({uv.x, 1.0f-uv.y});
        }
        else if (!line.compare(0, 2, "f ")) {
            std::vector<int> f;
            int itrash, idx;
            iss >> trash;
            while (iss >> idx >> trash >> itrash >> trash >> itrash) {
                idx--; // in wavefront obj all indices start at 1, not zero
                f.push_back(idx);
            }
            faces_.push_back(f);
        }
    }
    std::cerr << "# v# " << verts_.size() << " f# "  << faces_.size() << std::endl;

    load_texture(model_file, diffuse_file, diffuse_map);
}

Model::~Model() {
}

int Model::nverts() {
    return (int)verts_.size();
}

int Model::nfaces() {
    return (int)faces_.size();
}

std::vector<int> Model::face(const int idx) {
    return faces_[idx];
}

Vec3f Model::vert(int i) {
    return verts_[i];
}

void Model::load_texture(std::string filename, const std::string suffix, TGAImage &img) {
    size_t dot = filename.find_last_of(".");
    if (dot==std::string::npos) return;
    std::string texfile = suffix;
    std::cerr << "texture file " << texfile << " loading " << (img.read_tga_file(texfile.c_str()) ? "ok" : "failed") << std::endl;
}

Vec2f Model::uv(const int iface, const int nthvert)
{
    std::vector<int> f = face(iface);
    return tex_coords_[f[nthvert]];
}

main.cpp:

#include <vector>
#include <cmath>
#include <cstdlib>
#include <limits>
#include "tgaimage.h"
#include "model.h"
#include "geometry.h"

const TGAColor white = TGAColor(255, 255, 255, 255);
const TGAColor red   = TGAColor(255, 0,   0,   255);
Model *model = NULL;

#define width 800
#define height 800

void line(int x0, int y0, int x1, int y1, TGAImage &image, TGAColor color) 
{
    bool steep = false;
    if (std::abs(x0-x1)<std::abs(y0-y1)) 
    {
        std::swap(x0, y0);
        std::swap(x1, y1);
        steep = true;
    }
    if (x0>x1) 
    {
        std::swap(x0, x1);
        std::swap(y0, y1);
    }

    for (int x=x0; x<=x1; x++) 
    {
        float t = (x-x0)/(float)(x1-x0);
        int y = y0*(1.-t) + y1*t;
        if (steep) 
        {
            image.set(y, x, color);
        } else 
        {
            image.set(x, y, color);
        }
    }
}

Vec3f barycentric(Vec3f A, Vec3f B, Vec3f C, Vec3f P) 
{
    Vec3f s[2];
    for (int i=2; i--; ) 
    {
        s[i][0] = C[i]-A[i];
        s[i][1] = B[i]-A[i];
        s[i][2] = A[i]-P[i];
    }
    Vec3f u = cross(s[0], s[1]);
    if (std::abs(u[2])>1e-2) // dont forget that u[2] is integer. If it is zero then triangle ABC is degenerate
        return Vec3f(1.f-(u.x+u.y)/u.z, u.y/u.z, u.x/u.z);
    return Vec3f(-1,1,1); // in this case generate negative coordinates, it will be thrown away by the rasterizator
}

void triangle(Vec3f *pts, float *zbuffer, TGAImage &image, TGAColor color) 
{
    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;
            for (int i=0; i<3; i++) P.z += pts[i][2]*bc_screen[i];
            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 world2screen(Vec3f v) 
{
    return Vec3f(int((v.x+1.)*width/2.+.5), int((v.y+1.)*height/2.+.5), v.z);
}

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] = -std::numeric_limits<float>::max());

    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];
        Vec2f uvs[3];
        TGAColor colors[3];
        for (int j=0; j<3; j++) 
        {
            pts[j] = world2screen(model->vert(face[j]));
            world_coords[j]  = model->vert(face[j]); 
            uvs[j] = model->uv(i,j);
            colors[j] = diffuse.get(uvs[j].x, uvs[j].y);
        }
        TGAColor final_color = TGAColor((colors[0].r+colors[1].r+colors[2].r)/3.0f,
                                        (colors[0].g+colors[1].g+colors[2].g)/3.0f,
                                        (colors[0].b+colors[1].b+colors[2].b)/3.0f,
                                        255);
        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) triangle(pts, zbuffer, image, TGAColor(intensity*final_color.r, intensity*final_color.g, intensity*final_color.g, 255));
    }

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

final_color is the average of the color[] array, which are the diffuse colors for each vertex of face i.

When printing the color array to the standard output for each pixel, I get that the array is constantly [{c,0,F},{c,0,F},{c,0,F}] so I am pretty confused as to where I am going wrong. Can anyone help me debug this particular error?

FINAL OUTPUT :
lesson_03

NOTE : I am getting different UV values from the uvs[j] = model->uv(i,j); statement so that does not seem to be the problem. I am guessing the problem is in the diffuse.get(...) function after I get the uv values.

Update : I found some of my mistakes, which I have fixed now. I was giving the get(...) function incorrect values, it should rather be

int uv_x = uvs[j].x*1024;
int uv_y = 1uvs[j].y*1024;
colors[j] = diffuse.get(uv_x, uv_y);

However, I am still getting incorrect diffuse colors, as shown below :
lesson_03(1)

You have to use the barycentric to get uvs for any points in the triangle

This is the triangle(...) I am using right now :

void triangle(Vec3f *pts, float *zbuffer, TGAImage &image, TGAColor color) 
{
    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;
            for (int i=0; i<3; i++) P.z += pts[i][2]*bc_screen[i];
            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);
            }
        }
    }
}

If I pass the UV coordinates of the 3 points contained in pts to triangle(...) and use the barycentric(...), passing the 3 UV coordinates and P(screen coordinates), will I get back the UV coordinates for P ?

Here is my current code that I have for function(...) and main(...) :

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;

    TGAColor colors[3];
    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;
            for(int i=0; i<3; i++) P.z += pts[i][2]*bc_screen[i];
            for(int i=0; i<3; i++)
            {
                colors[i] = diffuse.get(uvs[i].x, uvs[i].y);
                colors[i].r *= bc_screen[i];
                colors[i].g *= bc_screen[i];
                colors[i].b *= bc_screen[i];
            }
            TGAColor final_color = TGAColor((colors[0].r+colors[1].r+colors[2].r)*intensity,
                                        (colors[0].g+colors[1].g+colors[2].g)*intensity,
                                        (colors[0].b+colors[1].b+colors[2].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, final_color);
            }
        }
    }
}

main():

for (int i=0; i<model->nfaces(); i++) 
    {
        std::vector<int> face = model->face(i);
        Vec3f world_coords[3];
        Vec3f pts[3];
        Vec2f uvs[3];
        for (int j=0; j<3; j++) 
        {
            pts[j] = world2screen(model->vert(face[j]));
            world_coords[j]  = model->vert(face[j]); 
            uvs[j] = model->uv(i,j);
            int uv_x = uvs[j].x*diffuse.get_width();
            int uv_y = diffuse.get_height() - uvs[j].y*diffuse.get_height();
            uvs[j].x = uv_x;
            uvs[j].y = uv_y;
        }
        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)
        {
            triangle(pts, zbuffer, image, diffuse, uvs, intensity);
        }
    }

lesson_03(2)

You have to get uvs for any point P in the triangle first. It's the same way as you get P.z using the barycentric.
and then you can use the uv to get the color in the texture.
Using "colors[i] = diffuse.get(uvs[i].x, uvs[i].y);", you will get the same color for all the points in the triangle

try something like this:

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];//get uv for point P in the triangle
	uv.y += uvs[i].y * bc_screen[i];
}

Thanks! I will try it out.