目录
使用了几何类、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; }