2.0 Lession 2 三角形和面剔除
[TOC]
画三角形-描边
通过Brensenham画线算法完成
void line(Vec2i p0, Vec2i p1, TGAColor color, TGAImage &image);
void triangle_line(Vec2i p0, Vec2i p1, Vec2i p2, TGAColor color, TGAImage &image) {
line(p0, p1, color, image);
line(p1, p2, color, image);
line(p0, p2, color, image);
}
void line(Vec2i p0, Vec2i p1, TGAColor color, TGAImage &image) {
bool steep = std::abs(p1.y - p0.y) > std::abs(p1.x - p0.x);
if (steep) {
std::swap(p0.x, p0.y);
std::swap(p1.x, p1.y);
}
if(p0.x > p1.x) {
std::swap(p0, p1);
}
int dy = p1.y - p0.y;
int dx = p1.x - p0.x;
float derror = (float )std::abs(dy) * 2;
float delta = dx * 2;
float error = 0;
int yStep = dy > 0 ? 1 : -1;;
float y = p0.y;
for (int x = p0.x; x <= p1.x; x++) {
if (steep) {
image.set(y, x, color);
} else {
image.set(x, y, color);
}
error += derror;
if (error > dx) {
error -= delta;
y += yStep;
}
}
}
画三角形-填充
行扫描
void triangle_line_sweeping_seperate(Vec2i p0, Vec2i p1, Vec2i p2, TGAColor color, TGAImage &image) {
//冒泡
if (p0.y > p1.y) std::swap(p0, p1);
if (p0.y > p2.y) std::swap(p0, p2);
if (p1.y > p2.y) std::swap(p1, p2);
std::cout<< "after swap\n" << p0 << p1 << p2;
//总高度
int totalHeight = p2.y - p0.y + 1;
//第一段三角形高度
int segmentHeight_1 = p1.y - p0.y + 1;
int segmentHeight_2 = p2.y - p1.y + 1;
std::cout<< "totalHeight=" << totalHeight << " segmentHeight=" << segmentHeight_1<<"\n";
//绘制第一段三角形
for(int y = p0.y; y <= p1.y; y++) {
//计算比例
float alpha = ((float) (y - p0.y + 1) / segmentHeight_1);
Vec2i A = p0 + (p1 - p0) * alpha;
float beta = ((float) (y - p0.y + 1) / totalHeight);
Vec2i B = p0 + (p2 - p0) * beta;
std::cout<< "A = "<< A << "B= "<< B;
if (A.x > B.x) {
std::swap(A, B);
}
for (int x = A.x; x <= B.x; x++) {
image.set(x, y, color);
}
}
//绘制第二段三角形
for(int y = p1.y; y <= p2.y; y++) {
float alpha = ((float) (y - p1.y + 1) / segmentHeight_2);
Vec2i A = p1 + (p2 - p1) * alpha;
float beta = ((float) (y - p0.y + 1) / totalHeight);
Vec2i B = p0 + (p2 - p0) * beta;
std::cout<< "A = "<< A << "B= "<< B;
if (A.x > B.x) {
std::swap(A, B);
}
for (int x = A.x; x <= B.x; x++) {
image.set(x, y, color);
}
}
}
void triangle_line_merge(Vec2i p0, Vec2i p1, Vec2i p2, TGAColor color, TGAImage &image) {
//冒泡
if (p0.y > p1.y) std::swap(p0, p1);
if (p0.y > p2.y) std::swap(p0, p2);
if (p1.y > p2.y) std::swap(p1, p2);
std::cout<< "after swap\n" << p0 << p1 << p2;
//总高度
int totalHeight = p2.y - p0.y + 1;
//第一段三角形高度
int segmentHeight_1 = p1.y - p0.y + 1;
//第二段三角形高度
int segmentHeight_2 = p2.y - p1.y + 1;
std::cout<< "totalHeight=" << totalHeight << " segmentHeight=" << segmentHeight_1<<"\n";
for(int y = p0.y; y <= p2.y; y++) {
bool isPart1 = y > p1.y - 1 ? false : true;
float alpha = isPart1 ? ((float) (y - p0.y + 1) / segmentHeight_1) : ((float) (y - p1.y + 1) / segmentHeight_2);
Vec2i A = isPart1 ? p0 + (p1 - p0) * alpha : p1 + (p2 - p1) * alpha;
float beta = ((float) (y - p0.y) / totalHeight);
Vec2i B = p0 + (p2 - p0) * beta;
std::cout<< "A = "<< A << "B= "<< B;
if (A.x > B.x) {
std::swap(A, B);
}
for (int x = A.x; x <= B.x; x++) {
image.set(x, y, color);
}
}
}
重心坐标
关于重心坐标,可以参考数学基础部分。
思想:
- 确定三角形的范围
- 然后逐点判断 点是否在三角形中
这里计算重心坐标时计算了好久,这里记录一下
AP = uAB + vAC
其中 u v 在[0,1]之间
展开:
P-A = u(B-A) + v(C-A)
P = (1-u-v)A + uB + vC
由AP = uAB + vAC
得到
uAB + vAC + PA = 0;
uABx + vACx + PAx = 0;
uABy + vACy + PAy = 0;
正交,实际找到 [ABx, ACx,PAx], [ABy+ ACy + PAy]的叉乘
得到的结果为 [a,b,c]
带入原子式,
aAB + bAC + cPA = 0
展开:
a(B-A) + b(C-A) + c(P-A) = 0;
得到:
P= (1-a/c-b/c)A + a/cB + b/cC
Vec3f barycentric(Vec2i p0, Vec2i p1, Vec2i p2, Vec2i p) {
Vec3f bc = Vec3f(p1.x - p0.x, p2.x - p0.x, p0.x - p.x) ^Vec3f(p1.y - p0.y, p2.y - p0.y, p0.y - p.y);
int t = bc.z > 0 ? bc.z : -bc.z;
//bc.z != 0 并且我们传入的值是Vec2i,所以这里可以提前计算
if (t < 1) {
std::cout << "barycentric" << bc;
return Vec3f(-1, 1, 1);
}
return Vec3f(1.f - (bc.x + bc.y) / bc.z, bc.y / bc.z, bc.x / bc.z);
}
void triangle_barycentric(Vec2i p0, Vec2i p1, Vec2i p2, TGAColor color, TGAImage &image) {
Vec2i rectFMax(0, 0);
Vec2i rectFMin(image.get_width() - 1, image.get_height() - 1);
Vec2i clamp(image.get_width() - 1, image.get_height() - 1);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {
Vec2i *t;
if (i == 0) t = &p0;
else if (i == 1) t = &p1;
else t = &p2;
int r = 0;
if (j == 0) r = t->x;
else r = t->y;
rectFMin[j] = std::max(0, std::min(rectFMin[j], r));
rectFMax[j] = std::min(clamp[j], std::max(rectFMax[j], r));
}
}
Vec2i P;
for (P.x = rectFMin.x; P.x <= rectFMax.x; P.x++) {
for (P.y = rectFMin.y; P.y <= rectFMax.y; P.y++) {
Vec3f bc_screen = barycentric(p0, p1, p2, P);
if (bc_screen.x < 0 || bc_screen.y < 0 || bc_screen.z < 0) continue;
image.set(P.x, P.y, color);
}
}
}
面剔除 Back-face culling
TGAImage image5(1000, 1000, TGAImage::RGB);
Model model("../obj/african_head.obj");
int width = 1000;
int height = 1000;
Vec3f light_dir(0,0,-1);
for (int face = 0; face < model.nfaces(); face++) {
std::vector<int> vertIdxes = model.face(face);
Vec2i screen_coordinate[3];
Vec3f world_coordinate[3];
for (int j = 0; j < 3; j++) {
Vec3f v = model.vert(vertIdxes[j]);
screen_coordinate[j] = Vec2i((v.x+1.)*width/2., (v.y+1.)*height/2.);
world_coordinate[j] = v;
}
//obj 顶点顺序都是逆时针,所以计算法向量需要注意顺序
//假设顶点是ABC 那么 AC AB 的叉积 指向内方向
Vec3f norm = (world_coordinate[2] - world_coordinate[0]) ^ (world_coordinate[1] - world_coordinate[0]);
//计算点积 值的正负代表cos值的正负, cos 0-90 > 0 90-180 < 0
//光源方向 z轴负方向
norm.normalize();
float intensity = norm.dotProduct(light_dir);
if (intensity > 0) {
triangle_barycentric(screen_coordinate[0], screen_coordinate[1], screen_coordinate[2], TGAColor(intensity*255, intensity*255, intensity*255, 255), image5);
}
}
image5.flip_vertically();
image5.write_tga_file("lession2_review_backface_culling.tga");