第1关:纹理映射
main.cpp
#include <vector>
#include <cmath>
#include <algorithm>
#include <iostream>
#include "model.h"
#include "geometry.h"
#include "pngimage.h"
using namespace std;
const double PI = acos(-1.0);
Vec3f barycentric(Vec3i *pts, Vec3f P)
{
Vec3f u = Vec3f(pts[2].x - pts[0].x, pts[1].x - pts[0].x, pts[0].x - P.x) ^
Vec3f(pts[2].y - pts[0].y, pts[1].y - pts[0].y, pts[0].y - P.y);
/* `pts` and `P` has integer value as coordinates
so `abs(u[2])` < 1 means `u[2]` is 0, that means
triangle is degenerate, in this case return something with negative coordinates */
if (std::abs(u.z) < 1)
return Vec3f(-1, 1, 1);
return Vec3f(1.f - (u.x + u.y) / u.z, u.y / u.z, u.x / u.z);
}
void triangle(Model *model, Vec3i *pts, Vec2i *uv, PNGImage &image, float intensity, float *zbuffer)
{
int minX = min({ pts[0].x, pts[1].x, pts[2].x });
int maxX = max({ pts[0].x, pts[1].x, pts[2].x });
int minY = min({ pts[0].y, pts[1].y, pts[2].y });
int maxY = max({ pts[0].y, pts[1].y, pts[2].y });
int width = image.get_width();
int height = image.get_height();
if (maxX >= width)
maxX = width - 1;
if (maxY >= height)
maxY = height - 1;
Vec3f P;
for (P.x = minX; P.x <= maxX; P.x++) {
for (P.y = minY; P.y <= maxY; P.y++) {
Vec3f bc_screen = barycentric(pts, P);
if (bc_screen.x < 0 || bc_screen.y < 0 || bc_screen.z < 0) continue;
P.z = 0;
Vec2i uvP;
for (int i = 0; i < 3; i++)
{
P.z += pts[i].z * bc_screen[i];
//4.由重心坐标和三角形顶点uv坐标计算三角形内部任意一点对应的uv坐标
uvP.x += uv[i].x * bc_screen[i];
uvP.y += uv[i].y * bc_screen[i];
/********** Begin ********/
/********** End *********/
}
if (zbuffer[int(P.x + P.y*width)] < P.z) {
zbuffer[int(P.x + P.y*width)] = P.z;
//5.由uv坐标获得三角形内部任意一点对应的纹理颜色值
PNGColor color = model->diffuse(uvP);
/********** Begin ********/
/********** End *********/
image.set(P.x, P.y, PNGColor(color.r*intensity, color.g*intensity, color.b*intensity, 255));
}
}
}
}
Matrix projection(Vec3f eye, Vec3f center)
{
Matrix m = Matrix::identity(4);
m[3][2] = -1.f / (eye - center).norm();
return m;
}
Matrix viewport(int x, int y, int w, int h, int depth) {
Matrix m = Matrix::identity(4);
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;
}
Matrix lookat(Vec3f eye, Vec3f center, Vec3f up) {
Vec3f z = (eye - center).normalize();
Vec3f x = (up^z).normalize();
Vec3f y = (z^x).normalize();
Matrix res = Matrix::identity(4);
for (int i = 0; i < 3; i++) {
res[0][i] = x[i];
res[1][i] = y[i];
res[2][i] = z[i];
res[i][3] = -center[i];
}
return res;
}
Matrix translation(Vec3f v) {
Matrix Tr = Matrix::identity(4);
Tr[0][3] = v.x;
Tr[1][3] = v.y;
Tr[2][3] = v.z;
return Tr;
}
Matrix scale(float factorX, float factorY, float factorZ)
{
Matrix Z = Matrix::identity(4);
Z[0][0] = factorX;
Z[1][1] = factorY;
Z[2][2] = factorZ;
return Z;
}
Matrix rotation_x(float angle)
{
angle = angle * PI / 180;
float sinangle = sin(angle);
float cosangle = cos(angle);
Matrix R = Matrix::identity(4);
R[1][1] = R[2][2] = cosangle;
R[1][2] = -sinangle;
R[2][1] = sinangle;
return R;
}
Matrix rotation_y(float angle)
{
angle = angle * PI / 180;
float sinangle = sin(angle);
float cosangle = cos(angle);
Matrix R = Matrix::identity(4);
R[0][0] = R[2][2] = cosangle;
R[0][2] = sinangle;
R[2][0] = -sinangle;
return R;
}
Matrix rotation_z(float angle) {
angle = angle * PI / 180;
float sinangle = sin(angle);
float cosangle = cos(angle);
Matrix R = Matrix::identity(4);
R[0][0] = R[1][1] = cosangle;
R[0][1] = -sinangle;
R[1][0] = sinangle;
return R;
}
int main(int argc, char** argv)
{
const int width = 800;
const int height = 800;
const int depth = 255;
//generate some image
PNGImage image(width, height, PNGImage::RGBA); //Error when RGB because lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size() in encode
image.init(PNGColor(0, 0, 0, 255));
Model *model = new Model("african_head.obj");
float *zbuffer = new float[width*height];
for (int i = 0; i < width * height; i++)
zbuffer[i] = -numeric_limits<float>::max();
Vec3f eye(0, 0, 3),center(0, 0, 0), up(0, 1, 0);
Vec3f light_dir(0, 0, -1);
Matrix ModelView = Matrix::identity(4);;
Matrix Projection = projection(eye, center);
Matrix ViewPort = viewport(width / 8, height / 8, width * 3 / 4, height * 3 / 4, depth);
Matrix VP = ViewPort * Projection;
for (int i = 0; i < model->nfaces(); i++)
{
vector<int> face = model->face(i);
{
Vec3f world_coords[3];
Vec3i screen_coords[3];
for (int j = 0; j < 3; j++)
{
world_coords[j] = model->vert(face[j]);
screen_coords[j] = VP * world_coords[j];
}
Vec3f n = (world_coords[2] - world_coords[0]) ^ (world_coords[1] - world_coords[0]);
n.normalize();
float intensity = n * light_dir;
if (intensity > 0) {
// 2.获得三角形顶点的uv坐标
Vec2i uv[3];
for (int k = 0; k < 3; k++) {
uv[k] = model->uv(i, k);
}
/********** Begin ********/
/********** End *********/
triangle(model, screen_coords, uv, image, intensity, zbuffer);
}
}
}
image.flip_vertically(); // i want to have the origin at the left bottom corner of the image
image.write_png_file("../img_step1/test.png");
delete model;
delete zbuffer;
return 0;
}
model.cpp
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
#include "model.h"
Model::Model(const char *filename) : verts_(), faces_(), norms_(), uv_() {
std::ifstream in;
in.open (filename, 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, "vn ")) {
iss >> trash >> trash;
Vec3f n;
for (int i=0;i<3;i++) iss >> n[i];
norms_.push_back(n);
} else if (!line.compare(0, 3, "vt ")) {
iss >> trash >> trash;
Vec2f uv;
for (int i=0;i<2;i++) iss >> uv[i];
uv_.push_back(uv);
} else if (!line.compare(0, 2, "f ")) {
std::vector<Vec3i> f;
Vec3i tmp;
iss >> trash;
while (iss >> tmp[0] >> trash >> tmp[1] >> trash >> tmp[2]) {
for (int i=0; i<3; i++) tmp[i]--; // in wavefront obj all indices start at 1, not zero
f.push_back(tmp);
}
faces_.push_back(f);
}
}
// 1.加载纹理文件
load_texture(filename, "_diffuse.png", diffusemap_);
/********** Begin ********/
/********** End *********/
}
Model::~Model() {
}
int Model::nverts() {
return (int)verts_.size();
}
int Model::nfaces() {
return (int)faces_.size();
}
std::vector<int> Model::face(int idx) {
std::vector<int> face;
for (int i = 0; i < (int)faces_[idx].size(); i++) face.push_back(faces_[idx][i][0]);
return face;
}
Vec3f Model::vert(int i) {
return verts_[i];
}
void Model::load_texture(std::string filename, const char *suffix, PNGImage &img) {
std::string texfile(filename);
size_t dot = texfile.find_last_of(".");
if (dot != std::string::npos) {
texfile = texfile.substr(0, dot) + std::string(suffix);
std::cerr << "texture file " << texfile << " loading " << (img.read_png_file(texfile.c_str()) ? "ok" : "failed") << std::endl;
img.flip_vertically();
}
}
PNGColor Model::diffuse(Vec2i uv) {
// 6.由uv坐标获得纹理颜色值
return diffusemap_.get(uv.x, uv.y);
/********** Begin ********/
/********** End *********/
}
Vec2i Model::uv(int iface, int nvert) {
// 3.由三角形面的索引和顶点编号获得三角形顶点的uv坐标
int idx = faces_[iface][nvert][1];
return Vec2i(uv_[idx].x*diffusemap_.get_width(), uv_[idx].y*diffusemap_.get_height());
/********** Begin ********/
/********** End *********/
}