How to draw triangular faces

preface

Last class, we can draw straight lines, but it's not enough. The models are composed of triangular surfaces, so this time we need to draw triangular surfaces and eliminate the surfaces we can't see.

Dmitry V. Sokolov
gamma formula
Games101 rasterization (discretization of triangles)
Games101 shading (interpolation, advanced texture mapping)
This series mainly refers to the relevant courses and courseware of Gams101 and the soft rendering tutorial of Dmitry V. Sokolov. For detailed tutorials, please read the reference links. It is recommended to read teacher Yan's course because it is very clear and easy to understand.

1, Draw triangular faces

Drawing a triangular surface is actually much simpler than drawing a straight line. The principle is also very simple. If you judge whether a point is in a triangle, you draw it, and if not, skip it.

1.1 judge whether the point is in the triangle - Cross multiplication

Cross multiplication is the easiest way to determine whether a point is in a triangle. Connect the three vertices of the triangle and the points we want to determine into vectors, and then judge whether they are in the triangle by comparing the directionality of the z values of the three normal vectors multiplied by the cross. If the directivity is the same, then the point is in the triangle. If one is different, then it is not in the triangle.

struct triangle
{
    triangle() {};
    triangle(Vec3f _0, Vec3f _1, Vec3f _2) : p0(_0), p1(_1), p2(_2) {};
    Vec3f vec[3];

    Vec3f& p0 = vec[0];
    Vec3f& p1 = vec[1];
    Vec3f& p2 = vec[2];
};

bool inside(const triangle& tri, const Vec3f& point)
{
    Vec3f AB = tri.p1 - tri.p0;
    Vec3f BC = tri.p2 - tri.p1;
    Vec3f CA = tri.p0 - tri.p2;

    Vec3f AP = point - tri.p0;
    Vec3f BP = point - tri.p1;
    Vec3f CP = point - tri.p2;

    float a = (AB ^ AP).z;
    float b = (BC ^ BP).z;
    float c = (CA ^ CP).z;

    if ((a < 0 && b < 0 && c < 0) || (a > 0 && b > 0 && c > 0)) return true;
    return false;

}

1.2 judge whether the point is in the triangle - center of gravity method

The center of gravity of a triangle can be used to interpolate point, UV, color, normal and so on
Because the center of gravity of a triangle can be used for interpolation, I suggest using the center of gravity to judge whether the point is in the triangle. At the same time, we can also use the center of gravity to interpolate other attributes we want.

Triangle center of gravity formula:
V = α V A + β V B + γ V C V = \alpha V_A + \beta V_B + \gamma V_C V=αVA​+βVB​+γVC​
Then at the same time, Mr. Yan told us that we can use the area ratio to calculate the values of three variables.

Then you can write the corresponding code.

Vec3f barycentricCoord(const triangle& tri, const Vec2f& point)
{
    float alpha = (-(point.x - tri.p1.x) * (tri.p2.y - tri.p1.y) + (point.y - tri.p1.y) * (tri.p2.x - tri.p1.x)) / (-(tri.p0.x - tri.p1.x) * (tri.p2.y - tri.p1.y) + (tri.p0.y - tri.p1.y) * (tri.p2.x - tri.p1.x));
    float beta = (-(point.x - tri.p2.x) * (tri.p0.y - tri.p2.y) + (point.y - tri.p2.y) * (tri.p0.x - tri.p2.x)) / (-(tri.p1.x - tri.p2.x) * (tri.p0.y - tri.p2.y) + (tri.p1.y - tri.p2.y) * (tri.p0.x - tri.p2.x));
    float gama = 1 - alpha - beta;

    return Vec3f(alpha, beta, gama);
}

It should be noted here that the three returned values cannot be exchanged randomly. They correspond to the formula one by one, otherwise there will be image bug s. For example, around the eyes below.

2.1 enclosure

It's not enough to judge whether the point is in the triangle. We also have to know the range of the triangle. Otherwise, it's a waste of time to traverse all pixels.
It is also very simple to obtain the range of the triangle. You only need to know the horizontal maximum and minimum value and the vertical maximum and minimum value of the triangle.

    int x_boundingBoxMax = (int)(std::max((std::max(tri.p0.x, tri.p1.x)), tri.p2.x));
    int x_boundingBoxMin = (int)(std::min((std::min(tri.p0.x, tri.p1.x)), tri.p2.x));
    int y_boundingBoxMax = (int)(std::max((std::max(tri.p0.y, tri.p1.y)), tri.p2.y));
    int y_boundingBoxMin = (int)(std::min((std::min(tri.p0.y, tri.p1.y)), tri.p2.y));

In fact, we can render our mesh here, but it's not good enough, because we also render the triangles on the back, which we don't need. We have to find a way to eliminate it.

3.1 removing triangles

Painter algorithm, this algorithm is superimposed layer by layer from far to near. It sounds feasible, but there are still problems in judging the distance and the intersection of triangular planes. Interested in direct search, not detailed here.
zbuffer,zbuffer is a more common way to eliminate triangles. The method of judging whether to draw the point to pixel by recording the z value of the point to be drawn. Search for details directly.

The problem to be solved here is how to calculate the z value except for the vertices of the triangle, for example, how to calculate the points inside the triangle? The answer is above. As we said above, the center of gravity of a triangle can interpolate many properties, including z value of course

float zOrder = A.z * bc.x + B.z * bc.y + C.z * bc.z;

Here we finally draw our model!!

2, Complete code

Vec3f barycentricCoord(const triangle& tri, const Vec2f& point)
{
    float alpha = (-(point.x - tri.p1.x) * (tri.p2.y - tri.p1.y) + (point.y - tri.p1.y) * (tri.p2.x - tri.p1.x)) / (-(tri.p0.x - tri.p1.x) * (tri.p2.y - tri.p1.y) + (tri.p0.y - tri.p1.y) * (tri.p2.x - tri.p1.x));
    float beta = (-(point.x - tri.p2.x) * (tri.p0.y - tri.p2.y) + (point.y - tri.p2.y) * (tri.p0.x - tri.p2.x)) / (-(tri.p1.x - tri.p2.x) * (tri.p0.y - tri.p2.y) + (tri.p1.y - tri.p2.y) * (tri.p0.x - tri.p2.x));
    float gama = 1 - alpha - beta;

    return Vec3f(alpha, beta, gama);
}

void drawTriangle(const triangle& tri,const TGAColor& color,TGAImage &image)
{
    int x_boundingBoxMax = (int)(std::max((std::max(tri.p0.x, tri.p1.x)), tri.p2.x));
    int x_boundingBoxMin = (int)(std::min((std::min(tri.p0.x, tri.p1.x)), tri.p2.x));
    int y_boundingBoxMax = (int)(std::max((std::max(tri.p0.y, tri.p1.y)), tri.p2.y));
    int y_boundingBoxMin = (int)(std::min((std::min(tri.p0.y, tri.p1.y)), tri.p2.y));

    for (int x = x_boundingBoxMin; x <= x_boundingBoxMax; x++)
    {
        for (int y = y_boundingBoxMin; y <= y_boundingBoxMax; y++)
        {
            Vec3f bc = barycentricCoord(tri, Vec2f(x, y));
            if (bc.x < 0 || bc.y < 0 || bc.z < 0) continue;// Skip points that are not within a triangle

            float zOrder = tri.p0.z * bc.x + tri.p1.z * bc.y + tri.p2.z * bc.z;
            int index = x + y * width;

            if (depthBuffer[index] < zOrder)
            {
                depthBuffer[index] = zOrder;

                image.set(x, y, color);
            }
        }
    }
}

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

int main(int argc, char** argv) {
    TGAImage image(width, height, TGAImage::RGB);

    depthBuffer = new float[width * height];
    for (int i = width * height; i--; depthBuffer[i] = -std::numeric_limits<float>::max());


    if (2==argc) {
        model = new Model(argv[1]);
    } else {
        model = new Model("obj/african_head.obj");
    }

    triangle tri;
    Vec3f worldCoords[3];
    Vec3f lightDir(0.f, 0.f, -1.f);

    // draw line
    /*for (int i = 0; i < model->nfaces(); i++) {
        std::vector<int> face = model->face(i);
        for (int j = 0; j < 3; j++)
        {
            tri.vec[j] = world2screen(model->vert(face[j]));
        }
        drawLine(tri, white, image);
    }*/

    // drawTriangle
    for (int i = 0; i < model->nfaces(); i++)
    {
        std::vector<int> face = model->face(i);
        for (int j = 0; j < 3; j++)
        {
            tri.vec[j] = world2screen(model->vert(face[j]));
            worldCoords[j] = model->vert(face[j]);
        }

        Vec3f vertexNormal = ((worldCoords[2] - worldCoords[0]) ^ (worldCoords[1] - worldCoords[0])).normalize();
        float indensity = std::max(0.f,vertexNormal * lightDir);
        drawTriangle(tri, TGAColor(indensity * 255, indensity * 255, indensity * 255, 255), image);
    }

    image.flip_vertically(); // i want to have the origin at the left bottom corner of the image
    image.write_tga_file("output.tga");
    delete model;
    delete depthBuffer;
    return 0;
}

Tags: linear algebra

Posted on Wed, 08 Sep 2021 03:54:06 -0400 by mailtome