TinyRenderer学习笔记

目录

一、从画线开始

二、渲染模型线框

三、对三角形着色

四、添加zBuffer

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

 六、移动相机

七、封装,尝试几种不同的着色方法

FlatShader

GouraudShader

PhongShader

ToonShader

整体代码:

使用了几何类、model类,tgaimage三个类来辅助完成

TinyRenderer项目用tga文件来查看渲染出的图片

【问题】没有标准的MVP变换,定义透视矩阵的方式和games101不同,正确性未证明    

                法向量计算,光照方向似乎都反了,但也能得到正确结果

一、从画线开始

#include<iostream>
#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画出来不是连续的线

 为什么?因为它的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对称的点

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

#include<iostream>
#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"); 
}

二、渲染模型线框

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

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

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

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


	//模型的面作为循环控制变量
	for (int i = 0; i < model->nfaces(); i++) {
		//face数组:存储一个面的三个顶点坐标
		std::vector<int>face = 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 <vector>
#include <cmath>
#include "tgaimage.h"   //tga画图库
#include "model.h"      //模型类,主要实现模型的读取
#include "geometry.h"   //几何库,主要定义了Vec2和Vec3类型
#include <Eigen\Dense>

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::vector<int>face = 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;
}

三、对三角形着色

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

//对三角形着色
//参数: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 <vector>
#include <cmath>
#include "tgaimage.h"   //tga画图库
#include "model.h"      //模型类,主要实现模型的读取
#include "geometry.h"   //几何库,主要定义了Vec2和Vec3类型
#include <Eigen\Dense>

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::vector<int>face = 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似乎还是很别扭。。。)

至此达到的效果:

四、添加zBuffer

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

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

重心坐标的计算:

 推导过程大概是这样

 加入zbuffer后的完整代码:

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

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

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<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);  //不能超过图像的大小

	//确定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<float>::max());
	//(添加的模型变换、光照模型部分并不严谨!)

	//指定光照方向
	Vec3f light_dir(0, 0, -1);
	//模型面作为循环控制变量
	for (int i = 0; i < model->nfaces(); i++) {
		std::vector<int>face = 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;
}

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

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

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

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<int>::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::vector<int>face = 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;
}

 六、移动相机

 

 

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

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

#include <vector>
#include <iostream>
#include <cmath>
#include <limits>
#include "tgaimage.h"
#include "model.h"
#include "geometry.h"
#include <algorithm>
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<int>::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<int> 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;
}

七、封装,尝试几种不同的着色方法

FlatShader

//FlatShader:对平面着色
struct FlatShader : public IShader {
    //三个点的信息
    mat<3, 3, float> varying_tri;

    virtual ~FlatShader() {}

    //顶点着色器:计算对iface的nthvert顶点进行变换
    virtual Vec4f vertex(int iface, int nthvert) {
        Vec4f gl_Vertex = embed<4>(model->vert(iface, nthvert));
        gl_Vertex = Projection * ModelView * gl_Vertex;
        varying_tri.set_col(nthvert, proj<3>(gl_Vertex / gl_Vertex[3]));
        gl_Vertex = Viewport * gl_Vertex;
        return gl_Vertex;
    }

    //片元着色器:利用叉乘算出每个面的法向量,即为该面的颜色
    virtual bool fragment(Vec3f bar, TGAColor& color) {
        //直接用叉乘计算法向量
        Vec3f n = cross(varying_tri.col(1) - varying_tri.col(0), varying_tri.col(2) - varying_tri.col(0)).normalize();
        float intensity = n * light_dir; //整个平面都用一个颜色
        color = TGAColor(255, 255, 255) * intensity;
        return false;
    }
};

GouraudShader

 

//GouraudShader:对顶点着色,中间像素用插值算
struct GouraudShader :public IShader {
    Vec3f varying_intensity;
    mat<2, 3, float> varying_uv; //mat是Matrix类   两行三列矩阵

    //接受两个变量,(面序号,顶点序号)
    //顶点着色器。计算了光照强度,返回了变换后的屏幕坐标(不含透视校正)
    virtual Vec4f vertex(int iface, int nthvert) {
        //根据面序号和顶点序号读取模型对应顶点,并扩展为4维 
        Vec4f gl_Vertex = embed<4>(model->vert(iface, nthvert));  //第iface面的nthvert号点的顶点坐标(x y z 1)
        varying_uv.set_col(nthvert, model->uv(iface, nthvert));   //第iface面的nthvert号点的uv坐标
        //变换顶点坐标到屏幕坐标(视角矩阵*投影矩阵*变换矩阵*v)
        mat<4, 4, float> uniform_M = Projection * ModelView;  //投影矩阵*(平移矩阵*旋转矩阵)
        mat<4, 4, float> uniform_MIT = ModelView.invert_transpose(); //逆转置
        gl_Vertex = Viewport * uniform_MIT * gl_Vertex; //视角*变换矩阵的逆矩阵*世界坐标----->屏幕坐标
        //计算光照强度(顶点法向量*光照方向)
        Vec3f normal = proj<3>(embed<4>(model->normal(iface, nthvert))).normalize();//顶点法线
        varying_intensity[nthvert] = std::max(0.f, model->normal(iface, nthvert) * light_dir); //nthver号点的光照强度(法线*光照方向)
        return gl_Vertex;
    }

    //根据传入的质心坐标,颜色,以及varying_intensity计算出当前像素的颜色
    //片元着色器:插值算颜色(用到了光照强度和纹理坐标)。
    virtual bool fragment(Vec3f bar, TGAColor& color) {
        Vec2f uv = varying_uv * bar; //插值后的uv坐标
        TGAColor c = model->diffuse(uv); //获取uv贴图
        float intensity = varying_intensity * bar; //插值后的光照强度
        color = c * intensity; //该点应有的颜色:纹理*光强
        return false;                          
    }
};

PhongShader

 

//PhongShader:插值的是顶点的法线,对每一像素算它的颜色
struct PhongShader : public IShader {
    mat<2, 3, float> varying_uv;  // same as above
    mat<4, 4, float> uniform_M = Projection * ModelView;
    mat<4, 4, float> uniform_MIT = ModelView.invert_transpose();

    virtual Vec4f vertex(int iface, int nthvert) {
        varying_uv.set_col(nthvert, model->uv(iface, nthvert));
        Vec4f gl_Vertex = embed<4>(model->vert(iface, nthvert)); // read the vertex from .obj file
        return Viewport * Projection * ModelView * gl_Vertex; // transform it to screen coordinates
    }

    virtual bool fragment(Vec3f bar, TGAColor& color) {
        Vec2f uv = varying_uv * bar; 
        Vec3f n = proj<3>(uniform_MIT * embed<4>(model->normal(uv))).normalize();
        Vec3f l = proj<3>(uniform_M * embed<4>(light_dir)).normalize();

        //用插值出的法线去算光照
        Vec3f r = (n * (n * l * 2.f) - l).normalize();   //reflect light
        float spec = pow(std::max(r.z, 0.0f), model->specular(uv)); //镜面反射
        float diff = std::max(0.f, n * l);  //漫反射
        TGAColor c = model->diffuse(uv);
        color = c;
        
        //5是环境光,c[i]是uv纹理,乘上一个光的系数,其中0.6*镜面反射,让其看起来不至于太亮
        for (int i = 0; i < 3; i++) color[i] = std::min<float>(5 + c[i] * (diff + .6 * spec), 255); 
        return false;
    }

};

ToonShader

TinyShader的作者也实现了toon风格着色,就是在一定范围内的颜色赋一个一样的值

 

//ToonShader:实现一种卡通的效果
//将一定阈值内的光照强度给替换为一种
struct ToonShader : public IShader {
    mat<3, 3, float> varying_tri;
    Vec3f          varying_ity;

    virtual ~ToonShader() {}

    virtual Vec4f vertex(int iface, int nthvert) {
        Vec4f gl_Vertex = embed<4>(model->vert(iface, nthvert));
        gl_Vertex = Projection * ModelView * gl_Vertex;
        varying_tri.set_col(nthvert, proj<3>(gl_Vertex / gl_Vertex[3]));

        varying_ity[nthvert] = model->normal(iface, nthvert) * light_dir;

        gl_Vertex = Viewport * gl_Vertex;
        return gl_Vertex;
    }

    virtual bool fragment(Vec3f bar, TGAColor& color) {
        float intensity = varying_ity * bar;
        if (intensity > .85) intensity = 1;
        else if (intensity > .60) intensity = .80;
        else if (intensity > .45) intensity = .60;
        else if (intensity > .30) intensity = .45;
        else if (intensity > .15) intensity = .30;
        color = TGAColor(255, 155, 0) * intensity;
        return false;
    }
};

整体代码:

封装的头文件

#ifndef __OUR_GL_H__
#define __OUR_GL_H__

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

extern Matrix ModelView; //坐标轴随相机移动
extern Matrix Viewport; //映射至屏幕
extern Matrix Projection; //投影矩阵

void viewport(int x, int y, int w, int h);
void projection(float coeff = 0.f); // coeff = -1/c
void lookat(Vec3f eye, Vec3f center, Vec3f up);

struct IShader {
    virtual ~IShader();
    virtual Vec4f vertex(int iface, int nthvert) = 0;       //顶点着色器
    virtual bool fragment(Vec3f bar, TGAColor& color) = 0;  //片元着色器
};

void triangle(Vec4f* pts, IShader& shader, TGAImage& image, TGAImage& zbuffer);

#endif //__OUR_GL_H__

源文件:

#include <cmath>
#include <limits>
#include <cstdlib>
#include "our_gl.h"
#include <algorithm>

Matrix ModelView;
Matrix Viewport;
Matrix Projection;

IShader::~IShader(){}

//进行到窗口的映射
void viewport(int x, int y, int w, int h) {
	Viewport = Matrix::identity();
	//平移
	Viewport[0][3] = x+w/2.f;
	Viewport[1][3] = y+h/2.f;
	Viewport[2][3] = 255.f/2.f;

	//缩放
	Viewport[0][0] = w / 2.f;
	Viewport[1][1] = h / 2.f;
	Viewport[2][2] = 255.f / 2.f;
}


//投影矩阵
void projection(float coeff) {
    Projection = Matrix::identity();
    Projection[3][2] = coeff;
}

//变换矩阵
void lookat(Vec3f eye, Vec3f center, Vec3f up) {
	Vec3f z = (eye - center).normalize();
	Vec3f x = cross(up, z).normalize();
	Vec3f y = cross(z, x).normalize();
	ModelView = Matrix::identity();
	Matrix translation = Matrix::identity();
	Matrix rotation = Matrix::identity();
	//指向摄像机所看的点
	for (int i = 0; i < 3; i++) {
		translation[i][3] = -center[i];
	}
	//将原本的坐标轴旋转至现在相机所在的坐标轴
	for (int i = 0; i < 3; i++) {
		rotation[0][i] = x[i];
		rotation[1][i] = y[i];
		rotation[2][i] = z[i];
	}
	ModelView = translation * rotation;
}


//计算重心坐标
Vec3f barycentric(Vec2f v1, Vec2f v2, Vec2f v3, Vec2f 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);
}


//绘制三角形(着色)
void triangle(Vec4f* pts/*三个顶点坐标*/, IShader& shader, TGAImage& image, TGAImage& zbuffer) {
    //初始化三角形边界框
    Vec2f bboxmin(std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
    Vec2f bboxmax(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max());
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 2; j++) {
            //这里pts除以了最后一个分量,实现了透视中的缩放,所以作为边界框
            bboxmin[j] = std::min(bboxmin[j], pts[i][j] / pts[i][3]);
            bboxmax[j] = std::max(bboxmax[j], pts[i][j] / pts[i][3]);
        }
    }
    //当前像素坐标P,颜色color
    Vec2i P;
    TGAColor color;
    //遍历边界框中的每一个像素
    for (P.x = bboxmin.x; P.x <= bboxmax.x; P.x++) {
        for (P.y = bboxmin.y; P.y <= bboxmax.y; P.y++) {
            //c为当前P对应的质心坐标
            //这里pts除以了最后一个分量,实现了透视中的缩放,所以用于判断P是否在三角形内
            Vec3f c = barycentric(proj<2>(pts[0] / pts[0][3]), proj<2>(pts[1] / pts[1][3]), proj<2>(pts[2] / pts[2][3]), proj<2>(P));
            //插值计算P的zbuffer
            //pts[i]为三角形的三个顶点
            //pts[i][2]为三角形的z信息(0~255)
            //pts[i][3]为三角形的投影系数(1-z/c)

            float z_P = (pts[0][2] / pts[0][3]) * c.x + (pts[1][2] / pts[1][3]) * c.y + (pts[2][2] / pts[2][3]) * c.z;
            int frag_depth = std::max(0, std::min(255, int(z_P + .5)));
            //P的任一质心分量小于0或者zbuffer小于已有zbuffer,不渲染
            if (c.x < 0 || c.y < 0 || c.z<0 || zbuffer.get(P.x, P.y)[0]>frag_depth) continue;
            //调用片元着色器计算当前像素颜色
            bool discard = shader.fragment(c, color);
            if (!discard) {
                //zbuffer
                zbuffer.set(P.x, P.y, TGAColor(frag_depth));
                //为像素设置颜色
                image.set(P.x, P.y, color);
            }
        }
    }
}

 

主函数(内含shader定义) 

#include <vector>
#include <iostream>
#include <algorithm>
#include "tgaimage.h"
#include "model.h"
#include "geometry.h"
#include "our_gl.h"

Model* model = NULL;
const int width = 800;
const int height = 800;

//对比games101   定义的是光线方向,光照强度
                      //相机位置 gaze向量

//本demo涉及的特定数值较多,坐标系有点混乱

Vec3f light_dir(0, 1, 1);     //光线方向的负方向
Vec3f       eye(1, 0.5, 1.5); //相机位置
Vec3f    center(0, 0, 0);     //相机朝向位置
Vec3f        up(0, 1, 0);     //用来和算相机旋转后的z算x轴朝向的up向量



//FlatShader:对平面着色
struct FlatShader : public IShader {
    //三个点的信息
    mat<3, 3, float> varying_tri;

    virtual ~FlatShader() {}

    //顶点着色器:计算对iface的nthvert顶点进行变换
    virtual Vec4f vertex(int iface, int nthvert) {
        Vec4f gl_Vertex = embed<4>(model->vert(iface, nthvert));
        gl_Vertex = Projection * ModelView * gl_Vertex;
        varying_tri.set_col(nthvert, proj<3>(gl_Vertex / gl_Vertex[3]));
        gl_Vertex = Viewport * gl_Vertex;
        return gl_Vertex;
    }

    //片元着色器:利用叉乘算出每个面的法向量,即为该面的颜色
    virtual bool fragment(Vec3f bar, TGAColor& color) {
        //直接用叉乘计算法向量
        Vec3f n = cross(varying_tri.col(1) - varying_tri.col(0), varying_tri.col(2) - varying_tri.col(0)).normalize();
        float intensity = n * light_dir; //整个平面都用一个颜色
        color = TGAColor(255, 255, 255) * intensity;
        return false;
    }
};

//GouraudShader:对顶点着色,中间像素用插值算
struct GouraudShader :public IShader {
    Vec3f varying_intensity;
    mat<2, 3, float> varying_uv; //mat是Matrix类   两行三列矩阵

    //接受两个变量,(面序号,顶点序号)
    //顶点着色器。计算了光照强度,返回了变换后的屏幕坐标(不含透视校正)
    virtual Vec4f vertex(int iface, int nthvert) {
        //根据面序号和顶点序号读取模型对应顶点,并扩展为4维 
        Vec4f gl_Vertex = embed<4>(model->vert(iface, nthvert));  //第iface面的nthvert号点的顶点坐标(x y z 1)
        varying_uv.set_col(nthvert, model->uv(iface, nthvert));   //第iface面的nthvert号点的uv坐标
        //变换顶点坐标到屏幕坐标(视角矩阵*投影矩阵*变换矩阵*v)
        mat<4, 4, float> uniform_M = Projection * ModelView;  //投影矩阵*(平移矩阵*旋转矩阵)
        mat<4, 4, float> uniform_MIT = ModelView.invert_transpose(); //逆转置
        gl_Vertex = Viewport * uniform_MIT * gl_Vertex; //视角*变换矩阵的逆矩阵*世界坐标----->屏幕坐标
        //计算光照强度(顶点法向量*光照方向)
        Vec3f normal = proj<3>(embed<4>(model->normal(iface, nthvert))).normalize();//顶点法线
        varying_intensity[nthvert] = std::max(0.f, model->normal(iface, nthvert) * light_dir); //nthver号点的光照强度(法线*光照方向)
        return gl_Vertex;
    }

    //根据传入的质心坐标,颜色,以及varying_intensity计算出当前像素的颜色
    //片元着色器:插值算颜色(用到了光照强度和纹理坐标)。
    virtual bool fragment(Vec3f bar, TGAColor& color) {
        Vec2f uv = varying_uv * bar; //插值后的uv坐标
        TGAColor c = model->diffuse(uv); //获取uv贴图
        float intensity = varying_intensity * bar; //插值后的光照强度
        color = c * intensity; //该点应有的颜色:纹理*光强
        return false;                          
    }
};


//PhongShader:插值的是顶点的法线,对每一像素算它的颜色
struct PhongShader : public IShader {
    mat<2, 3, float> varying_uv;  // same as above
    mat<4, 4, float> uniform_M = Projection * ModelView;
    mat<4, 4, float> uniform_MIT = ModelView.invert_transpose();

    virtual Vec4f vertex(int iface, int nthvert) {
        varying_uv.set_col(nthvert, model->uv(iface, nthvert));
        Vec4f gl_Vertex = embed<4>(model->vert(iface, nthvert)); // read the vertex from .obj file
        return Viewport * Projection * ModelView * gl_Vertex; // transform it to screen coordinates
    }

    virtual bool fragment(Vec3f bar, TGAColor& color) {
        Vec2f uv = varying_uv * bar; 
        Vec3f n = proj<3>(uniform_MIT * embed<4>(model->normal(uv))).normalize();
        Vec3f l = proj<3>(uniform_M * embed<4>(light_dir)).normalize();

        //用插值出的法线去算光照
        Vec3f r = (n * (n * l * 2.f) - l).normalize();   //reflect light
        float spec = pow(std::max(r.z, 0.0f), model->specular(uv)); //镜面反射
        float diff = std::max(0.f, n * l);  //漫反射
        TGAColor c = model->diffuse(uv);
        color = c;
        
        //5是环境光,c[i]是uv纹理,乘上一个光的系数,其中0.6*镜面反射,让其看起来不至于太亮
        for (int i = 0; i < 3; i++) color[i] = std::min<float>(5 + c[i] * (diff + .6 * spec), 255); 
        return false;
    }

};

//ToonShader:实现一种卡通的效果
//将一定阈值内的光照强度给替换为一种
struct ToonShader : public IShader {
    mat<3, 3, float> varying_tri;
    Vec3f          varying_ity;

    virtual ~ToonShader() {}

    virtual Vec4f vertex(int iface, int nthvert) {
        Vec4f gl_Vertex = embed<4>(model->vert(iface, nthvert));
        gl_Vertex = Projection * ModelView * gl_Vertex;
        varying_tri.set_col(nthvert, proj<3>(gl_Vertex / gl_Vertex[3]));

        varying_ity[nthvert] = model->normal(iface, nthvert) * light_dir;

        gl_Vertex = Viewport * gl_Vertex;
        return gl_Vertex;
    }

    virtual bool fragment(Vec3f bar, TGAColor& color) {
        float intensity = varying_ity * bar;
        if (intensity > .85) intensity = 1;
        else if (intensity > .60) intensity = .80;
        else if (intensity > .45) intensity = .60;
        else if (intensity > .30) intensity = .45;
        else if (intensity > .15) intensity = .30;
        color = TGAColor(255, 155, 0) * intensity;
        return false;
    }
};

int main(int argc, char** argv) {
    //加载模型
    if (2 == argc) {
        model = new Model(argv[1]);
    }
    else {
        model = new Model("obj/african_head/african_head.obj");
    }

    //初始化变换矩阵(相机怎么旋转),投影矩阵,视角矩阵
    lookat(eye, center, up);
    projection(-1.f / (eye - center).norm());//(-(1/c))
    viewport(width / 8, height / 8, width * 3 / 4, height * 3 / 4);

    light_dir.normalize();

    //初始化image和zbuffer
    TGAImage image(width, height, TGAImage::RGB);
    TGAImage zbuffer(width, height, TGAImage::GRAYSCALE);


    //FlatShader shader;
    //实例化高洛德着色
    //GouraudShader shader;
    //实例化Phong着色
    //PhongShader shader;
    //实例化Toon着色
    //ToonShader shader;
    

    //以模型面作为循环控制量
    for (int i = 0; i < model->nfaces(); i++) {
        Vec4f screen_coords[3];
        for (int j = 0; j < 3; j++) {
            //通过顶点着色器读取模型顶点
            //变换顶点坐标到屏幕坐标(视角矩阵*投影矩阵*变换矩阵*v) ***其实并不是真正的屏幕坐标,因为没有除以最后一个分量
            //计算光照强度
            screen_coords[j] = shader.vertex(i, j);
        }
        //遍历完3个顶点,一个三角形光栅化完成
        //绘制三角形,triangle内部通过片元着色器对三角形着色
        triangle(screen_coords, shader, image, zbuffer);
    }

    image.flip_vertically();
    zbuffer.flip_vertically();
    image.write_tga_file("output.tga");
    zbuffer.write_tga_file("zbuffer.tga");

    delete model;
    return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值