TinyRenderer学习笔记

一、从画线开始

TinyRenderer项目用tga文件来查看渲染出的图片,引入tgaimage.h与.cpp

#ifndef __IMAGE_H__
#define __IMAGE_H__

#include 

#pragma pack(push,1)
struct TGA_Header {
	char idlength;
	char colormaptype;
	char datatypecode;
	short colormaporigin;
	short colormaplength;
	char colormapdepth;
	short x_origin;
	short y_origin;
	short width;
	short height;
	char  bitsperpixel;
	char  imagedescriptor;
};
#pragma pack(pop)



struct TGAColor {
	union {
		struct {
			unsigned char b, g, r, a;
		};
		unsigned char raw[4];
		unsigned int val;
	};
	int bytespp;

	TGAColor() : val(0), bytespp(1) {
	}

	TGAColor(unsigned char R, unsigned char G, unsigned char B, unsigned char A) : b(B), g(G), r(R), a(A), bytespp(4) {
	}

	TGAColor(int v, int bpp) : val(v), bytespp(bpp) {
	}

	TGAColor(const TGAColor &c) : val(c.val), bytespp(c.bytespp) {
	}

	TGAColor(const unsigned char *p, int bpp) : val(0), bytespp(bpp) {
		for (int i=0; i

#include 
#include 
#include 
#include 
#include 
#include "tgaimage.h"

TGAImage::TGAImage() : data(NULL), width(0), height(0), bytespp(0) {
}

TGAImage::TGAImage(int w, int h, int bpp) : data(NULL), width(w), height(h), bytespp(bpp) {
	unsigned long nbytes = width*height*bytespp;
	data = new unsigned char[nbytes];
	memset(data, 0, nbytes);
}

TGAImage::TGAImage(const TGAImage &img) {
	width = img.width;
	height = img.height;
	bytespp = img.bytespp;
	unsigned long nbytes = width*height*bytespp;
	data = new unsigned char[nbytes];
	memcpy(data, img.data, nbytes);
}

TGAImage::~TGAImage() {
	if (data) delete [] data;
}

TGAImage & TGAImage::operator =(const TGAImage &img) {
	if (this != &img) {
		if (data) delete [] data;
		width  = img.width;
		height = img.height;
		bytespp = img.bytespp;
		unsigned long nbytes = width*height*bytespp;
		data = new unsigned char[nbytes];
		memcpy(data, img.data, nbytes);
	}
	return *this;
}

bool TGAImage::read_tga_file(const char *filename) {
	if (data) delete [] data;
	data = NULL;
	std::ifstream in;
	in.open (filename, std::ios::binary);
	if (!in.is_open()) {
		std::cerr << "can't open file " << filename << "\n";
		in.close();
		return false;
	}
	TGA_Header header;
	in.read((char *)&header, sizeof(header));
	if (!in.good()) {
		in.close();
		std::cerr << "an error occured while reading the header\n";
		return false;
	}
	width   = header.width;
	height  = header.height;
	bytespp = header.bitsperpixel>>3;
	if (width<=0 || height<=0 || (bytespp!=GRAYSCALE && bytespp!=RGB && bytespp!=RGBA)) {
		in.close();
		std::cerr << "bad bpp (or width/height) value\n";
		return false;
	}
	unsigned long nbytes = bytespp*width*height;
	data = new unsigned char[nbytes];
	if (3==header.datatypecode || 2==header.datatypecode) {
		in.read((char *)data, nbytes);
		if (!in.good()) {
			in.close();
			std::cerr << "an error occured while reading the data\n";
			return false;
		}
	} else if (10==header.datatypecode||11==header.datatypecode) {
		if (!load_rle_data(in)) {
			in.close();
			std::cerr << "an error occured while reading the data\n";
			return false;
		}
	} else {
		in.close();
		std::cerr << "unknown file format " << (int)header.datatypecode << "\n";
		return false;
	}
	if (!(header.imagedescriptor & 0x20)) {
		flip_vertically();
	}
	if (header.imagedescriptor & 0x10) {
		flip_horizontally();
	}
	std::cerr << width << "x" << height << "/" << bytespp*8 << "\n";
	in.close();
	return true;
}

bool TGAImage::load_rle_data(std::ifstream &in) {
	unsigned long pixelcount = width*height;
	unsigned long currentpixel = 0;
	unsigned long currentbyte  = 0;
	TGAColor colorbuffer;
	do {
		unsigned char chunkheader = 0;
		chunkheader = in.get();
		if (!in.good()) {
			std::cerr << "an error occured while reading the data\n";
			return false;
		}
		if (chunkheader<128) {
			chunkheader++;
			for (int i=0; ipixelcount) {
					std::cerr << "Too many pixels read\n";
					return false;
				}
			}
		} else {
			chunkheader -= 127;
			in.read((char *)colorbuffer.raw, bytespp);
			if (!in.good()) {
				std::cerr << "an error occured while reading the header\n";
				return false;
			}
			for (int i=0; ipixelcount) {
					std::cerr << "Too many pixels read\n";
					return false;
				}
			}
		}
	} while (currentpixel < pixelcount);
	return true;
}

bool TGAImage::write_tga_file(const char *filename, bool rle) {
	unsigned char developer_area_ref[4] = {0, 0, 0, 0};
	unsigned char extension_area_ref[4] = {0, 0, 0, 0};
	unsigned char footer[18] = {'T','R','U','E','V','I','S','I','O','N','-','X','F','I','L','E','.','\0'};
	std::ofstream out;
	out.open (filename, std::ios::binary);
	if (!out.is_open()) {
		std::cerr << "can't open file " << filename << "\n";
		out.close();
		return false;
	}
	TGA_Header header;
	memset((void *)&header, 0, sizeof(header));
	header.bitsperpixel = bytespp<<3;
	header.width  = width;
	header.height = height;
	header.datatypecode = (bytespp==GRAYSCALE?(rle?11:3):(rle?10:2));
	header.imagedescriptor = 0x20; // top-left origin
	out.write((char *)&header, sizeof(header));
	if (!out.good()) {
		out.close();
		std::cerr << "can't dump the tga file\n";
		return false;
	}
	if (!rle) {
		out.write((char *)data, width*height*bytespp);
		if (!out.good()) {
			std::cerr << "can't unload raw data\n";
			out.close();
			return false;
		}
	} else {
		if (!unload_rle_data(out)) {
			out.close();
			std::cerr << "can't unload rle data\n";
			return false;
		}
	}
	out.write((char *)developer_area_ref, sizeof(developer_area_ref));
	if (!out.good()) {
		std::cerr << "can't dump the tga file\n";
		out.close();
		return false;
	}
	out.write((char *)extension_area_ref, sizeof(extension_area_ref));
	if (!out.good()) {
		std::cerr << "can't dump the tga file\n";
		out.close();
		return false;
	}
	out.write((char *)footer, sizeof(footer));
	if (!out.good()) {
		std::cerr << "can't dump the tga file\n";
		out.close();
		return false;
	}
	out.close();
	return true;
}

// TODO: it is not necessary to break a raw chunk for two equal pixels (for the matter of the resulting size)
bool TGAImage::unload_rle_data(std::ofstream &out) {
	const unsigned char max_chunk_length = 128;
	unsigned long npixels = width*height;
	unsigned long curpix = 0;
	while (curpix=width || y>=height) {
		return TGAColor();
	}
	return TGAColor(data+(x+y*width)*bytespp, bytespp);
}

bool TGAImage::set(int x, int y, TGAColor c) {
	if (!data || x<0 || y<0 || x>=width || y>=height) {
		return false;
	}
	memcpy(data+(x+y*width)*bytespp, c.raw, bytespp);
	return true;
}

int TGAImage::get_bytespp() {
	return bytespp;
}

int TGAImage::get_width() {
	return width;
}

int TGAImage::get_height() {
	return height;
}

bool TGAImage::flip_horizontally() {
	if (!data) return false;
	int half = width>>1;
	for (int i=0; i>1;
	for (int j=0; j=(int)width) {
				errx -= width;
				nx += bytespp;
				memcpy(tdata+nscanline+nx, data+oscanline+ox, bytespp);
			}
		}
		erry += h;
		oscanline += olinebytes;
		while (erry>=(int)height) {
			if (erry>=(int)height<<1) // it means we jump over a scanline
				memcpy(tdata+nscanline+nlinebytes, tdata+nscanline, nlinebytes);
			erry -= height;
			nscanline += nlinebytes;
		}
	}
	delete [] data;
	data = tdata;
	width = w;
	height = h;
	return true;
}

有了以上的头文件支持,就可以输出到tga图片了!

#include
#include"tgaimage.h"
using namespace std;

const TGAColor white = TGAColor(255, 255, 255, 255);
const TGAColor red = TGAColor(255, 0, 0, 255);

void Line(int x0, int y0, int x1, int y1, TGAImage& image, TGAColor color) {
	for (int x = x0; x <= x1; x++) {
		float t =(float)(x - x0) / (x1 - x0); //t是一个比例
		int y = y0 + t * (y1 - y0);
		image.set(x, y, color);				  //画点
	}
}

int main() {
	TGAImage image(100, 100, TGAImage::RGB); //创建一个tga图片
	//Line(13, 20, 80, 40, image, white); //线段A
	//Line(20, 13, 40, 80, image, red); //线段B
	Line(80, 40, 13, 20, image, red);//线段C

	//输出到图片
	image.flip_vertically(); //左下角做原点
	image.write_tga_file("output.tga"); 
}

运行。此时,发现线段C并没有显示在图片里,为什么呢——其实它和A是一条线,但是现在的画线代码,只有x0,y0比x1,y1小时才管用,否则t算出来是<0的

添加代码:

if (x0 > x1)swap(x0, x1);
if (y0 > y1)swap(y0, y1);

现在,C能正确绘制了,AC也是正确的覆盖关系(C后画,红线理应盖住白线)

问题是B画出来不是连续的线

TinyRenderer学习笔记_第1张图片

 为什么?因为它的k>1~~~

解决方式是,把这条线转换成根据y=x对称的线,这样,这条线的k就<0,实现了连续

在画点时,去画(y,x)点,不就刚好画出了原来要画的线吗

bool steep = false; //k>1?
	if (abs(x0 - x1) < abs(y0 - y1)) {
		swap(x0, y0);
		swap(x1, y1);
		steep = true;
	}



if (steep)
			image.set(y, x, color);				  //若k>1画根据y=x对称的点

TinyRenderer学习笔记_第2张图片

因此获得了画线算法的正确代码 

#include
#include"tgaimage.h"
using namespace std;

const TGAColor white = TGAColor(255, 255, 255, 255);
const TGAColor red = TGAColor(255, 0, 0, 255);

void Line(int x0, int y0, int x1, int y1, TGAImage& image, TGAColor color) {
	if (x0 > x1)swap(x0, x1);
	if (y0 > y1)swap(y0, y1);
	bool steep = false; //k>1?
	if (abs(x0 - x1) < abs(y0 - y1)) {
		swap(x0, y0);
		swap(x1, y1);
		steep = true;
	}

	for (int x = x0; x <= x1; x++) {
		float t =(float)(x - x0) / (x1 - x0); //t是一个比例
		int y = y0 + t * (y1 - y0);
		if (steep)
			image.set(y, x, color);				  //若k>1画根据y=x对称的点
		else
			image.set(x, y, color);				  //画点
	}

}

int main() {
	TGAImage image(100, 100, TGAImage::RGB); //创建一个tga图片
	Line(13, 20, 80, 40, image, white); //线段A
	Line(20, 13, 40, 80, image, red); //线段B
	Line(80, 40, 13, 20, image, red);//线段C

	//输出到图片
	image.flip_vertically(); //左下角做原点
	image.write_tga_file("output.tga"); 
}

二、渲染模型线框

再引入关于向量等的geometry头文件

#ifndef __GEOMETRY_H__
#define __GEOMETRY_H__

#include 

///

template  struct Vec2 {
	union {
		struct { t u, v; };
		struct { t x, y; };
		t raw[2];
	};
	Vec2() : u(0), v(0) {}
	Vec2(t _u, t _v) : u(_u), v(_v) {}
	inline Vec2 operator +(const Vec2& V) const { return Vec2(u + V.u, v + V.v); }
	inline Vec2 operator -(const Vec2& V) const { return Vec2(u - V.u, v - V.v); }
	inline Vec2 operator *(float f)          const { return Vec2(u * f, v * f); }
	template  friend std::ostream& operator<<(std::ostream& s, Vec2& v);
};

template  struct Vec3 {
	union {
		struct { t x, y, z; };
		struct { t ivert, iuv, inorm; };
		t raw[3];
	};
	Vec3() : x(0), y(0), z(0) {}
	Vec3(t _x, t _y, t _z) : x(_x), y(_y), z(_z) {}
	inline Vec3 operator ^(const Vec3& v) const { return Vec3(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x); }
	inline Vec3 operator +(const Vec3& v) const { return Vec3(x + v.x, y + v.y, z + v.z); }
	inline Vec3 operator -(const Vec3& v) const { return Vec3(x - v.x, y - v.y, z - v.z); }
	inline Vec3 operator *(float f)          const { return Vec3(x * f, y * f, z * f); }
	inline t       operator *(const Vec3& v) const { return x * v.x + y * v.y + z * v.z; }
	float norm() const { return std::sqrt(x * x + y * y + z * z); }
	Vec3& normalize(t l = 1) { *this = (*this) * (l / norm()); return *this; }
	template  friend std::ostream& operator<<(std::ostream& s, Vec3& v);
};

typedef Vec2 Vec2f;
typedef Vec2   Vec2i;
typedef Vec3 Vec3f;
typedef Vec3   Vec3i;

template  std::ostream& operator<<(std::ostream& s, Vec2& v) {
	s << "(" << v.x << ", " << v.y << ")\n";
	return s;
}

template  std::ostream& operator<<(std::ostream& s, Vec3& v) {
	s << "(" << v.x << ", " << v.y << ", " << v.z << ")\n";
	return s;
}

#endif //__GEOMETRY_H__

以及处理模型的model文件

#ifndef __MODEL_H__
#define __MODEL_H__

#include 
#include "geometry.h"

class Model {
private:
	std::vector verts_;
	std::vector > faces_;
public:
	Model(const char *filename);
	~Model();
	int nverts();
	int nfaces();
	Vec3f vert(int i);
	std::vector face(int idx);
};

#endif //__MODEL_H__
#include 
#include 
#include 
#include 
#include 
#include "model.h"

Model::Model(const char *filename) : verts_(), faces_() {
    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.raw[i];
            verts_.push_back(v);
        } else if (!line.compare(0, 2, "f ")) {
            std::vector 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;
}

Model::~Model() {
}

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

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

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

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

如何渲染线框?——连接模型中顶点组成的面的边就好了

遍历所有面,对于每个面,用刚刚的画线算法连三条线

(连的线需要在屏幕空间画,所以要进行一下坐标的转换)

(这里应该是TinyRenderer项目进行了一些简化,直接认为已经是一个标准立方体)


	//模型的面作为循环控制变量
	for (int i = 0; i < model->nfaces(); i++) {
		//face数组:存储一个面的三个顶点坐标
		std::vectorface = model->face(i);
		for (int j = 0; j < 3; j++) {
			Vec3f v0 = model->vert(face[j]);
			Vec3f v1 = model->vert(face[(j + 1) % 3]);
			//分别找每个点和它的下一个点(可能越界,所以对3取模)
			//对这两个点进行画线算法(不过首先要从世界坐标映射到屏幕坐标)
			//这里直接默认把[-1,1]标准立方体的x,y映射到屏幕了
			int x0 = (v0.x + 1.) * width / 2.;
			int y0 = (v0.y + 1.) * height / 2.;
			int x1 = (v1.x + 1.) * width / 2.;
			int y1 = (v1.y + 1.) * height / 2.;
			//画线
			Line(x0, y0, x1, y1, image, white);
		}
	}

同时发现刚刚的画线算法有点错误

先处理斜率,再处理点哪个大哪个小

完整的线框渲染代码:

#include 
#include 
#include "tgaimage.h"   //tga画图库
#include "model.h"      //模型类,主要实现模型的读取
#include "geometry.h"   //几何库,主要定义了Vec2和Vec3类型
#include 

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

//定义宽度高度
const int width = 800;
const int height = 800;

//画线算法
void Line(int x0, int y0, int x1, int y1, TGAImage& image, TGAColor color) {
	bool steep = false; //k>1?
	if (abs(x0 - x1) < 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); //t是一个比例
		int y = y0 + t * (y1 - y0);
		if (steep)
			image.set(y, x, color);				  //若k>1画根据y=x对称的点
		else
			image.set(x, y, color);				  //画点
	}

}

int main(int argc, char** argv) {

	//命令行控制方式和代码方式构造model
	//构造模型(obj文件路径)
	if (2 == argc) {
		model = new Model(argv[1]);
	}
	else {
		model = new Model("obj/african_head.obj");
	}
	//构造tga(宽,高,指定颜色空间)
	TGAImage image(width, height, TGAImage::RGB);

	//模型的面作为循环控制变量
	for (int i = 0; i < model->nfaces(); i++) {
		//face数组:存储一个面的三个顶点坐标
		std::vectorface = model->face(i);
		for (int j = 0; j < 3; j++) {
			Vec3f v0 = model->vert(face[j]);
			Vec3f v1 = model->vert(face[(j + 1) % 3]);
			//分别找每个点和它的下一个点(可能越界,所以对3取模)
			//对这两个点进行画线算法(不过首先要从世界坐标映射到屏幕坐标)
			//这里直接默认把[-1,1]标准立方体的x,y映射到屏幕了
			int x0 = (v0.x + 1.) * width / 2.;
			int y0 = (v0.y + 1.) * height / 2.;
			int x1 = (v1.x + 1.) * width / 2.;
			int y1 = (v1.y + 1.) * height / 2.;
			//画线
			Line(x0, y0, x1, y1, image, white);
		}
	}

	//输出到图片
	image.flip_vertically(); //左下角做原点
	image.write_tga_file("output.tga"); 
	delete model;
}

三、对三角形着色

利用二的知识,已经可以画出三角形线框,下一步,我们对三角形进行着色

TinyRenderer学习笔记_第3张图片

//对三角形着色
//参数:3个顶点+tga指针+颜色
void triangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAImage& image, TGAColor color) {
	//三角形面积为0
	if (t0.y == t1.y && t0.y == t2.y)return;
	//把该三角形做成从下到上t0,t1,t2的三角形
	if (t0.y > t1.y)std::swap(t0, t1);
	if (t0.y > t2.y)std::swap(t0, t2);
	if (t1.y > t2.y)std::swap(t1, t2);
	int total_height = t2.y - t0.y; //总高度差
	//以i在y方向移动,横着画线,交点用相似三角形求
	for (int i = 0; i < total_height; i++) {
		//根据t1将三角形分割成上下两部分
		bool second_half = i > t1.y - t0.y || t1.y == t0.y; //要么是过了t1,要么是平底三角形

		//所在这一半三角形的高度
		int segment_height = second_half ? t2.y - t1.y : t1.y - t0.y;

		//相似三角形的比例
		float alpha = (float)i / total_height;     //左半边三角形比例
		float beta = (float)(i - (second_half ? t1.y - t0.y : 0))/segment_height;  //右半边三角形比例

		//计算AB的坐标
		Vec2i A = t0 + (t2 - t0) * alpha;
		Vec2i B = second_half ? t1 + (t2 - t1) * beta : t0 + (t1 - t0) * beta;
		//还要保证此算法A在B的左边
		if (A.x > B.x)std::swap(A, B);

		//根据当前i和它与三角形交出的边界点AB一条一条划横线着色
		for (int j = A.x; j <= B.x; j++)
			image.set(j, t0.y + i, color);
	}
}

对模型线框着色的完整代码:

#include 
#include 
#include "tgaimage.h"   //tga画图库
#include "model.h"      //模型类,主要实现模型的读取
#include "geometry.h"   //几何库,主要定义了Vec2和Vec3类型
#include 

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

//定义宽度高度
const int width = 800;
const int height = 800;

画线算法1.0
//void Line(int x0, int y0, int x1, int y1, TGAImage& image, TGAColor color) {
//	bool steep = false; //k>1?
//	if (abs(x0 - x1) < 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); //t是一个比例
//		int y = y0 + t * (y1 - y0);
//		if (steep)
//			image.set(y, x, color);				  //若k>1画根据y=x对称的点
//		else
//			image.set(x, y, color);				  //画点
//	}
//
//}

//画线算法2.0(事实上只是把原来四个参数xy换成了两个参数p,p有xy成员)
void Line(Vec2i p0, Vec2i p1, TGAImage& image, TGAColor color) {
	bool steep=false;
	if (abs(p0.x - p1.x) < abs(p0.y - p1.y)) {
		std::swap(p0.x, p0.y);
		std::swap(p1.x, p1.y);
		steep = true;
	}

	if (p0.x > p1.x) {
		std::swap(p0.x, p1.x);
		std::swap(p0.y, p1.y);
	}

	for (int x = p0.x; x <= p1.x; x++) {
		float t = (x - p0.x) / (float)(p1.x - p0.x);
		int y = p0.y + t * (p1.y - p0.y);
		if (steep)
			image.set(y, x, color);
		else
			image.set(x, y, color);
	}

}

//对三角形着色
//参数:3个顶点+tga指针+颜色
void triangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAImage& image, TGAColor color) {
	//三角形面积为0
	if (t0.y == t1.y && t0.y == t2.y)return;
	//把该三角形做成从下到上t0,t1,t2的三角形
	if (t0.y > t1.y)std::swap(t0, t1);
	if (t0.y > t2.y)std::swap(t0, t2);
	if (t1.y > t2.y)std::swap(t1, t2);
	int total_height = t2.y - t0.y; //总高度差
	//以i在y方向移动,横着画线,交点用相似三角形求
	for (int i = 0; i < total_height; i++) {
		//根据t1将三角形分割成上下两部分
		bool second_half = i > t1.y - t0.y || t1.y == t0.y; //要么是过了t1,要么是平底三角形

		//所在这一半三角形的高度
		int segment_height = second_half ? t2.y - t1.y : t1.y - t0.y;

		//相似三角形的比例
		float alpha = (float)i / total_height;     //左半边三角形比例
		float beta = (float)(i - (second_half ? t1.y - t0.y : 0))/segment_height;  //右半边三角形比例

		//计算AB的坐标
		Vec2i A = t0 + (t2 - t0) * alpha;
		Vec2i B = second_half ? t1 + (t2 - t1) * beta : t0 + (t1 - t0) * beta;
		//还要保证此算法A在B的左边
		if (A.x > B.x)std::swap(A, B);

		//根据当前i和它与三角形交出的边界点AB一条一条划横线着色
		for (int j = A.x; j <= B.x; j++)
			image.set(j, t0.y + i, color);
	}
}

//本人很闲的绘制三角形测试代码
void DrawYuanShi(TGAImage&image) {
	Vec2i V0[3] = { Vec2i(100,400),Vec2i(400,400),Vec2i(250,500) };
	Vec2i V1[3] = { Vec2i(250,500),Vec2i(400,400),Vec2i(400,700) };
	Vec2i V2[3] = { Vec2i(550,500),Vec2i(400,400),Vec2i(400,700) };
	Vec2i V3[3] = { Vec2i(550,500),Vec2i(400,400),Vec2i(700,400) };
	Vec2i V4[3] = { Vec2i(550,300),Vec2i(400,400),Vec2i(700,400) };
	Vec2i V5[3] = { Vec2i(550,300),Vec2i(400,400),Vec2i(400,100) };
	Vec2i V6[3] = { Vec2i(250,300),Vec2i(400,400),Vec2i(400,100) };
	Vec2i V7[3] = { Vec2i(100,400),Vec2i(400,400),Vec2i(250,300) };


	triangle(V0[0], V0[1], V0[2], image, TGAColor(255, 182, 193, 0));
	triangle(V1[0], V1[1], V1[2], image, TGAColor(255, 192, 203, 0));
	triangle(V2[0], V2[1], V2[2], image, TGAColor(255, 182, 193, 0));
	triangle(V3[0], V3[1], V3[2], image, TGAColor(135, 206, 250, 0));
	triangle(V4[0], V4[1], V4[2], image, TGAColor(0, 191, 255, 0));
	triangle(V5[0], V5[1], V5[2], image, TGAColor(30, 144, 255, 0));
	triangle(V6[0], V6[1], V6[2], image, TGAColor(135, 206, 250, 0));
	triangle(V7[0], V7[1], V7[2], image, TGAColor(0, 191, 255, 0));
}

int main(int argc, char** argv) {

	//命令行控制方式和代码方式构造model
	//构造模型(obj文件路径)
	if (2 == argc) {
		model = new Model(argv[1]);
	}
	else {
		model = new Model("obj/african_head.obj");
	}
	//构造tga(宽,高,指定颜色空间)
	TGAImage image(width, height, TGAImage::RGB);

	//画画三角形试试:
	//DrawYuanShi(image);

	//(添加的模型变换、光照模型部分并不严谨!)

	//指定光照方向
	Vec3f light_dir(0, 0, -1);
	//模型面作为循环控制变量
	for (int i = 0; i < model->nfaces(); i++) {
		std::vectorface = model->face(i);
		Vec2i screen_coords[3]; //屏幕坐标
		Vec3f world_coords[3]; //世界坐标
		for (int j = 0; j < 3; j++) {
			Vec3f v = model->vert(face[j]);
			screen_coords[j] = Vec2i((v.x + 1.) * width / 2., (v.y + 1.) * height / 2.);
			world_coords[j] = v;
		}
		//用世界坐标计算这个面的法向量
		Vec3f n = (world_coords[2] - world_coords[0]) ^ (world_coords[1] - world_coords[0]);
		n.normalize();
		float intensity = n * light_dir;
		if (intensity > 0)
			//注意这里的颜色乘上了光照强度
			triangle(screen_coords[0], screen_coords[1], screen_coords[2], image, TGAColor(intensity * 255, intensity * 255, intensity * 255, 255));
	}

	//输出到图片
	image.flip_vertically(); //左下角做原点
	image.write_tga_file("output.tga"); 
	delete model;
}

并不严谨,这里的法向量是直接用顶点的世界坐标叉乘得出每个面的法向量,再用法向量和光照相乘。

(结合后边zbuffer,应该是右手坐标系,因为把光线方向负方向改成(0,0,1)后就会变成下图这样) 

(是没有添加zbuffer导致的,这种情况应该是后边被照亮了)

(光线方向和zbuffer似乎还是很别扭。。。)

TinyRenderer学习笔记_第4张图片

TinyRenderer学习笔记_第5张图片

至此达到的效果:

TinyRenderer学习笔记_第6张图片

四、添加zBuffer

this part要实现深度缓存,我们要离摄像机近的画面

知道顶点深度,怎么求每个像素的深度?——引入了重心坐标进行插值(不只是深度,顶点的许多属性都是通过重心坐标插值去计算的)

TinyRenderer学习笔记_第7张图片

重心坐标的计算:

TinyRenderer学习笔记_第8张图片

 推导过程大概是这样

TinyRenderer学习笔记_第9张图片

 加入zbuffer后的完整代码:

(不过这既不是games101中的phong光照模型,也没有mvp变换,很不标准。只需理解这个zbuffer就好)

#include 
#include 
#include 
#include 
#include "tgaimage.h"
#include "model.h"
#include "geometry.h"
#include 

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

//定义宽度高度
const int width = 800;
const int height = 800;

画线算法1.0
//void Line(int x0, int y0, int x1, int y1, TGAImage& image, TGAColor color) {
//	bool steep = false; //k>1?
//	if (abs(x0 - x1) < 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); //t是一个比例
//		int y = y0 + t * (y1 - y0);
//		if (steep)
//			image.set(y, x, color);				  //若k>1画根据y=x对称的点
//		else
//			image.set(x, y, color);				  //画点
//	}
//
//}

//画线算法2.0(事实上只是把原来四个参数xy换成了两个参数p,p有xy成员)
void Line(Vec2i p0, Vec2i p1, TGAImage& image, TGAColor color) {
	bool steep=false;
	if (abs(p0.x - p1.x) < abs(p0.y - p1.y)) {
		std::swap(p0.x, p0.y);
		std::swap(p1.x, p1.y);
		steep = true;
	}

	if (p0.x > p1.x) {
		std::swap(p0.x, p1.x);
		std::swap(p0.y, p1.y);
	}

	for (int x = p0.x; x <= p1.x; x++) {
		float t = (x - p0.x) / (float)(p1.x - p0.x);
		int y = p0.y + t * (p1.y - p0.y);
		if (steep)
			image.set(y, x, color);
		else
			image.set(x, y, color);
	}

}

//计算重心坐标
Vec3f barycentric(Vec3f v1, Vec3f v2, Vec3f v3, Vec3f p)
{
	//别丢了分母等于0的情况
	if ((-(v1.x - v2.x) * (v3.y - v2.y) + (v1.y - v2.y) * (v3.x - v2.x)) == 0)
		return Vec3f(1, 0, 0);
	if (-(v2.x - v3.x) * (v1.y - v3.y) + (v2.y - v3.y) * (v1.x - v3.x) == 0)
		return Vec3f(1, 0, 0);
	float alpha = (-(p.x - v2.x) * (v3.y - v2.y) + (p.y - v2.y) * (v3.x - v2.x)) / (-(v1.x - v2.x) * (v3.y - v2.y) + (v1.y - v2.y) * (v3.x - v2.x));
	float beta = (-(p.x - v3.x) * (v1.y - v3.y) + (p.y - v3.y) * (v1.x - v3.x)) / (-(v2.x - v3.x) * (v1.y - v3.y) + (v2.y - v3.y) * (v1.x - v3.x));
	float gamma = 1 - alpha - beta;

	return Vec3f(alpha, beta, gamma);
}
//对三角形着色
//参数:3个顶点+tga指针+颜色
void triangle(Vec3f*pts, float *zbuffer,TGAImage& image, TGAColor color) {
	//如何判断像素在三角形内--先看是否在包围盒内,如果在,看是否在三角形内
	Vec2f bboxmin(std::numeric_limits::max(), std::numeric_limits::max());
	Vec2f bboxmax(-std::numeric_limits::max(), -std::numeric_limits::max());
	Vec2f clamp(image.get_width() - 1, image.get_height() - 1);  //不能超过图像的大小

	//确定boundingBox
	for (int i = 0; i < 3; i++) { //per Vertex
		for (int j = 0; j < 2; j++) { //per x,y
			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; //用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]; //对三个顶点的深度进行插值
			//更新zbuffer(这个zbuffer是个一维数组)
			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.), int((v.y + 1.) * height / 2. ), v.z);
}



int main(int argc, char** argv) {

	//命令行控制方式和代码方式构造model
	//构造模型(obj文件路径)
	if (2 == argc) {
		model = new Model(argv[1]);
	}
	else {
		model = new Model("obj/african_head/african_head.obj");
	}
	//构造tga(宽,高,指定颜色空间)
	TGAImage image(width, height, TGAImage::RGB);
	//创建zbuffer,大小为画布大小
	float* zbuffer = new float[width * height];
	//初始化zbuffer,设定一个很小的值
	for (int i = width * height; i--; zbuffer[i] = -std::numeric_limits::max());
	//(添加的模型变换、光照模型部分并不严谨!)

	//指定光照方向
	Vec3f light_dir(0, 0, -1);
	//模型面作为循环控制变量
	for (int i = 0; i < model->nfaces(); i++) {
		std::vectorface = model->face(i);
		Vec3f screen_coords[3]; //屏幕坐标
		Vec3f world_coords[3]; //世界坐标
		for (int j = 0; j < 3; j++) {
			Vec3f v = model->vert(face[j]);
			screen_coords[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)
			//注意这里的颜色乘上了光照强度
			triangle(screen_coords, zbuffer, image, TGAColor(intensity * 255, intensity * 255, intensity * 255, 255));
	}

	//输出到图片
	image.flip_vertically(); //左下角做原点
	image.write_tga_file("output.tga"); 
	delete model;
}

TinyRenderer学习笔记_第10张图片

五、添加透视投影和uv贴图

这里既没有用bbox和重心插值去算uv贴图,透视矩阵和101也完全不同,视角矩阵之类的都只是针对特定数据,存疑...

#include 
#include 
#include 
#include 
#include "tgaimage.h"
#include "model.h"
#include "geometry.h"
#include 

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



//定义宽度高度
const int width = 800;
const int height = 800;
const int depth = 255;

Vec3f light_dir(0.2, 0.15, -1);
Vec3f camera(0, 0, 3);


//4d-->3d
//除以最后一个分量。(当最后一个分量为0,表示向量)
//不为0,表示坐标
Vec3f m2v(Matrix m) {
	return Vec3f(m[0][0] / m[3][0], m[1][0] / m[3][0], m[2][0] / m[3][0]);
}

//3d-->4d
//添加一个1表示坐标
Matrix v2m(Vec3f v) {
	Matrix m(4, 1);
	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(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;
}

画线算法1.0
//void Line(int x0, int y0, int x1, int y1, TGAImage& image, TGAColor color) {
//	bool steep = false; //k>1?
//	if (abs(x0 - x1) < 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); //t是一个比例
//		int y = y0 + t * (y1 - y0);
//		if (steep)
//			image.set(y, x, color);				  //若k>1画根据y=x对称的点
//		else
//			image.set(x, y, color);				  //画点
//	}
//
//}

//画线算法2.0(事实上只是把原来四个参数xy换成了两个参数p,p有xy成员)
void Line(Vec2i p0, Vec2i p1, TGAImage& image, TGAColor color) {
	bool steep=false;
	if (abs(p0.x - p1.x) < abs(p0.y - p1.y)) {
		std::swap(p0.x, p0.y);
		std::swap(p1.x, p1.y);
		steep = true;
	}

	if (p0.x > p1.x) {
		std::swap(p0.x, p1.x);
		std::swap(p0.y, p1.y);
	}

	for (int x = p0.x; x <= p1.x; x++) {
		float t = (x - p0.x) / (float)(p1.x - p0.x);
		int y = p0.y + t * (p1.y - p0.y);
		if (steep)
			image.set(y, x, color);
		else
			image.set(x, y, color);
	}

}

//计算重心坐标
Vec3f barycentric(Vec3f v1, Vec3f v2, Vec3f v3, Vec3f p)
{
	//别丢了分母等于0的情况
	if ((-(v1.x - v2.x) * (v3.y - v2.y) + (v1.y - v2.y) * (v3.x - v2.x)) == 0)
		return Vec3f(1, 0, 0);
	if (-(v2.x - v3.x) * (v1.y - v3.y) + (v2.y - v3.y) * (v1.x - v3.x) == 0)
		return Vec3f(1, 0, 0);
	float alpha = (-(p.x - v2.x) * (v3.y - v2.y) + (p.y - v2.y) * (v3.x - v2.x)) / (-(v1.x - v2.x) * (v3.y - v2.y) + (v1.y - v2.y) * (v3.x - v2.x));
	float beta = (-(p.x - v3.x) * (v1.y - v3.y) + (p.y - v3.y) * (v1.x - v3.x)) / (-(v2.x - v3.x) * (v1.y - v3.y) + (v2.y - v3.y) * (v1.x - v3.x));
	float gamma = 1 - alpha - beta;

	return Vec3f(alpha, beta, gamma);
}

//绘制三角形(顶点坐标,uv坐标,tga指针,亮度,zbuffer)
void triangle(Vec3i t0, Vec3i t1, Vec3i t2, Vec2i uv0, Vec2i uv1, Vec2i uv2, TGAImage& image, float intensity, int* zbuffer) {
	if (t0.y == t1.y && t0.y == t2.y) return;
	//分割成两个三角形
	if (t0.y > t1.y) { std::swap(t0, t1); std::swap(uv0, uv1); }
	if (t0.y > t2.y) { std::swap(t0, t2); std::swap(uv0, uv2); }
	if (t1.y > t2.y) { std::swap(t1, t2); std::swap(uv1, uv2); }
	//用高度做循环控制
	int total_height = t2.y - t0.y;
	for (int i = 0; i < total_height; i++) {
		//判断属于哪一部分以确定高度
		bool second_half = i > t1.y - t0.y || t1.y == t0.y;
		int segment_height = second_half ? t2.y - t1.y : t1.y - t0.y;
		//计算当前的比例
		float alpha = (float)i / total_height;
		float beta = (float)(i - (second_half ? t1.y - t0.y : 0)) / segment_height; // be careful: with above conditions no division by zero here
		//A表示t0与t2之间的点
		//B表示t0与t1之间的点
		Vec3i A = t0 + Vec3f(t2 - t0) * alpha;
		Vec3i B = second_half ? t1 + Vec3f(t2 - t1) * beta : t0 + Vec3f(t1 - t0) * beta;
		//计算UV
		Vec2i uvA = uv0 + (uv2 - uv0) * alpha;
		Vec2i uvB = second_half ? uv1 + (uv2 - uv1) * beta : uv0 + (uv1 - uv0) * beta;
		//保证B在A的右边
		if (A.x > B.x) { std::swap(A, B); }// std::swap(uvA, uvB);}
		//用横坐标作为循环控制,对这一行进行着色
		for (int j = A.x; j <= B.x; j++) {
			//计算当前点在AB之间的比例
			float phi = B.x == A.x ? 1. : (float)(j - A.x) / (float)(B.x - A.x);
			//计算出当前点的坐标和uv坐标,A,B保存了z轴信息
			Vec3i   P = Vec3f(A) + Vec3f(B - A) * phi;
			Vec2i uvP = uvA + (uvB - uvA) * phi;
			if (P.x < width && P.y < height)
			{
				//计算当前zbuffer下标=P.x+P.y*width
				int idx = P.x + P.y * width;
				//当前点的z大于zbuffer信息,覆盖掉,并更新zbuffer
				if (zbuffer[idx] < P.z) {
					zbuffer[idx] = P.z;
					TGAColor color = model->diffuse(uvP);
					image.set(P.x, P.y, TGAColor(color.r * intensity, color.g * intensity, color.b * intensity));
				}
			}
		}
	}
}


Vec3f world2screen(Vec3f v) {
	return Vec3f(int((v.x + 1.) * width / 2.), int((v.y + 1.) * height / 2. ), v.z);
}



int main(int argc, char** argv) {

	//命令行控制方式和代码方式构造model
	//构造模型(obj文件路径)
	if (2 == argc) {
		model = new Model(argv[1]);
	}
	else {
		model = new Model("obj/african_head/african_head.obj");
	}
	//构造tga(宽,高,指定颜色空间)
	TGAImage image(width, height, TGAImage::RGB);

	int * zbuffer = NULL;
	//初始化zbuffer,设定一个很小的值
	zbuffer = new int[width * height];
	for (int i = 0; i < width * height; i++) {
		//初始化zbuffer
		zbuffer[i] = std::numeric_limits::min();
	}
	//(添加的模型变换、光照模型部分并不严谨!)

	//初始化投影矩阵
	Matrix Projection = Matrix::identity(4);
	//初始化视角矩阵
	Matrix ViewPort = viewport(width / 8, height / 8, width * 3 / 4, height * 3 / 4);
	//投影矩阵[3][2]=-1/c,c为相机z坐标
	Projection[3][2] = -1.f / camera.z;

	//模型面作为循环控制变量
	for (int i = 0; i < model->nfaces(); i++) {
		std::vectorface = model->face(i);
		Vec3i screen_coords[3]; //屏幕坐标
		Vec3f world_coords[3]; //世界坐标
		for (int j = 0; j < 3; j++) {
			Vec3f v = model->vert(face[j]);
			//视角矩阵*投影矩阵*坐标
			screen_coords[j] = m2v(ViewPort * Projection * v2m(v));
			world_coords[j] = v;
		}
		//用世界坐标计算这个面的法向量
		Vec3f n = (world_coords[2] - world_coords[0]) ^ (world_coords[1] - world_coords[0]);
		n.normalize();
		float intensity = n * light_dir;
		intensity = std::min(std::abs(intensity), 1.f);
		if (intensity > 0) {
			Vec2i uv[3];
			for (int k = 0; k < 3; k++) {
				uv[k] = model->uv(i, k);
			}
			//绘制三角形
			triangle(screen_coords[0], screen_coords[1], screen_coords[2], uv[0], uv[1], uv[2], image, intensity, zbuffer);
		}
	}
	//输出到图片
	image.flip_vertically(); //左下角做原点
	image.write_tga_file("output.tga"); 
	delete model;
}

TinyRenderer学习笔记_第11张图片

 六、移动相机

TinyRenderer学习笔记_第12张图片

 TinyRenderer学习笔记_第13张图片

 TinyRenderer学习笔记_第14张图片

此处的变换矩阵仍与game101不尽相同 

TinyRenderer学习笔记_第15张图片

 这一次的颜色过渡平滑了很多,注意观察光照处添加了光的falloff(这里还是和games101不一样,别扭.....)

#include 
#include 
#include 
#include 
#include "tgaimage.h"
#include "model.h"
#include "geometry.h"
#include 
const int width = 800;
const int height = 800;
const int depth = 255;

Model* model = NULL;
int* zbuffer = NULL;

Vec3f light_dir = Vec3f(0, -1, -1).normalize();
//摄像机位置
Vec3f eye(0, 0, 3);
//焦点位置
Vec3f center(0, 0, 0);

//视角矩阵,用于将(-1,1),(-1,1),(-1,1)映射到(1/8w,7/8w),(1/8h,7/8h),(0,255)
Matrix viewport(int x, int y, int w, int h) {
    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) {
    //计算出z,根据z和up算出x,再算出y
    Vec3f z = (eye - center).normalize();
    Vec3f x = (up ^ z).normalize();
    Vec3f y = (z ^ x).normalize();
    Matrix rotation = Matrix::identity(4);
    Matrix translation = Matrix::identity(4);
    //***矩阵的第四列是用于平移的。因为观察位置从原点变为了center,所以需要将物体平移-center***
    for (int i = 0; i < 3; i++) {
       translation[i][3] = -center[i];
    }
    //正交矩阵的逆 = 正交矩阵的转置
    //矩阵的第一行即是现在的x
    //矩阵的第二行即是现在的y
    //矩阵的第三行即是现在的z
    //***矩阵的三阶子矩阵是当前视线旋转矩阵的逆矩阵***
    for (int i = 0; i < 3; i++) {
        rotation[0][i] = x[i];
        rotation[1][i] = y[i];
        rotation[2][i] = z[i];
    }
    //这样乘法的效果是先平移物体,再旋转
    Matrix res = rotation * translation;
    return res;
}

//绘制三角形(坐标1,坐标2,坐标3,顶点光照强度1,顶点光照强度2,顶点光照强度3,tga指针,zbuffer)
void triangle(Vec3i t0, Vec3i t1, Vec3i t2, float ity0, float ity1, float ity2, Vec2i uv0, Vec2i uv1, Vec2i uv2, float dis0, float dis1, float dis2, TGAImage& image, int* zbuffer) {
    //按照y分割为两个三角形
    if (t0.y == t1.y && t0.y == t2.y) return;
    if (t0.y > t1.y) { std::swap(t0, t1); std::swap(ity0, ity1); std::swap(uv0, uv1); }
    if (t0.y > t2.y) { std::swap(t0, t2); std::swap(ity0, ity2); std::swap(uv0, uv2); }
    if (t1.y > t2.y) { std::swap(t1, t2); std::swap(ity1, ity2); std::swap(uv1, uv2); }
    int total_height = t2.y - t0.y;
    for (int i = 0; i < total_height; i++) {
        bool second_half = i > t1.y - t0.y || t1.y == t0.y;
        int segment_height = second_half ? t2.y - t1.y : t1.y - t0.y;
        float alpha = (float)i / total_height;
        float beta = (float)(i - (second_half ? t1.y - t0.y : 0)) / segment_height;
        //计算A,B两点的坐标
        Vec3i A = t0 + Vec3f(t2 - t0) * alpha;
        Vec3i B = second_half ? t1 + Vec3f(t2 - t1) * beta : t0 + Vec3f(t1 - t0) * beta;
        //计算A,B两点的光照强度
        float ityA = ity0 + (ity2 - ity0) * alpha;
        float ityB = second_half ? ity1 + (ity2 - ity1) * beta : ity0 + (ity1 - ity0) * beta;
        //计算UV
        Vec2i uvA = uv0 + (uv2 - uv0) * alpha;
        Vec2i uvB = second_half ? uv1 + (uv2 - uv1) * beta : uv0 + (uv1 - uv0) * beta;
        //计算距离
        float disA = dis0 + (dis2 - dis0) * alpha;
        float disB = second_half ? dis1 + (dis2 - dis1) * beta : dis0 + (dis1 - dis0) * beta;
        if (A.x > B.x) { std::swap(A, B); std::swap(ityA, ityB); }
        //x坐标作为循环控制
        for (int j = A.x; j <= B.x; j++) {
            float phi = B.x == A.x ? 1. : (float)(j - A.x) / (B.x - A.x);
            //计算当前需要绘制点P的坐标,光照强度
            Vec3i    P = Vec3f(A) + Vec3f(B - A) * phi;
            float ityP = ityA + (ityB - ityA) * phi;
            ityP = std::min(1.f, std::abs(ityP) + 0.01f);
            Vec2i uvP = uvA + (uvB - uvA) * phi;
            float disP = disA + (disB - disA) * phi;
            int idx = P.x + P.y * width;
            //边界限制
            if (P.x >= width || P.y >= height || P.x < 0 || P.y < 0) continue;
            if (zbuffer[idx] < P.z) {
                zbuffer[idx] = P.z;
                TGAColor color = model->diffuse(uvP);
                image.set(P.x, P.y, TGAColor(color.bgra[2], color.bgra[1], color.bgra[0]) * ityP * (20.f / std::pow(disP, 2.f)));
                //image.set(P.x, P.y, TGAColor(255,255,255)* ityP);
            }
        }
    }
}

int main(int argc, char** argv) {
    //读取模型
    if (2 == argc) {
        model = new Model(argv[1]);
    }
    else {
        model = new Model("obj/african_head/african_head.obj");
    }
    //构造zbuffer并初始化
    zbuffer = new int[width * height];
    for (int i = 0; i < width * height; i++) {
        zbuffer[i] = std::numeric_limits::min();
    }
    //绘制模型
    {
        //模型变换矩阵
        Matrix ModelView = lookat(eye, center, Vec3f(0, 1, 0));

        //透视矩阵
        Matrix Projection = Matrix::identity(4);
        Projection[3][2] = -1.f / (eye - center).norm();

        //视角矩阵
        Matrix ViewPort = viewport(width / 8, height / 8, width * 3 / 4, height * 3 / 4);


        TGAImage image(width, height, TGAImage::RGB);
        for (int i = 0; i < model->nfaces(); i++) {
            std::vector face = model->face(i);
            Vec3i screen_coords[3];
            float intensity[3];
            float distance[3];
            for (int j = 0; j < 3; j++) {
                Vec3f v = model->vert(face[j]);
                Matrix m_v = ModelView * Matrix(v);
                screen_coords[j] = Vec3f(ViewPort * Projection * m_v);
                intensity[j] = model->norm(i, j) * light_dir;
                Vec3f new_v = Vec3f(m_v);
                distance[j] = std::pow((std::pow(new_v.x - eye.x, 2.0f) + std::pow(new_v.y - eye.y, 2.0f) + std::pow(new_v.z - eye.z, 2.0f)), 0.5f);
            }
            Vec2i uv[3];
            for (int k = 0; k < 3; k++) {
                uv[k] = model->uv(i, k);
            }
            triangle(screen_coords[0], screen_coords[1], screen_coords[2], intensity[0], intensity[1], intensity[2], uv[0], uv[1], uv[2], distance[0], distance[1], distance[2], image, zbuffer);
        }
        image.flip_vertically();
        image.write_tga_file("output.tga");
    }
    //输出zbuffer图像
    {
        TGAImage zbimage(width, height, TGAImage::GRAYSCALE);
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                zbimage.set(i, j, TGAColor(zbuffer[i + j * width]));
            }
        }
        zbimage.flip_vertically();
        zbimage.write_tga_file("zbuffer.tga");
    }
    delete model;
    delete[] zbuffer;
    return 0;
}

你可能感兴趣的:(图形学,c++,图形渲染)