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?
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.
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);
}
}
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.