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");