English Welcome Kecy Programování 3D - Engine Guestbook Odkazy Downloady O autorovi Napiš mi Mailform
box_cz 3D - Engine 04 - klipovací pyramida a kreslení polygonů box_cz

Tak jsem tu a se mnou i čtvrtý díl našeho seriálu. Pokud někdo nezačne pořádně rychle projevovat zájem o seriál, tak to balím. Chápu že o tom moc lidí asi neví a tak když dá mailem někdo vědět, že chce, abych pokračoval tak zase budu. Ale doteď mi napsal jen jeden človíček ohledně céčka ! Takže pište, parchantíci :-) No, když už to začínalo být zajímavé, tak končím. Dneska jsem vám chtěl vysvětlit minule pouze okrajově vzpomínanou klipovací pyramidu a s ní i kreslení polygonů. Takže první skutečný engine a vy to nečtete ! Takže jak jsem říkal už minule, klipovací pyramida je čtveřice rovin o kterou se polygony "ořezávají", aby byly pouze v prostoru obrazovky. Obecnou rovinu vytvoříte ze tří bodů takhle :


struct Plane {
    float a, b, c, d;
};

struct Vector {
    float x, y, z;
};

#define _Len(A) ((float)sqrt(A.x * A.x + A.y * A.y + A.z * A.z))
// spočítá délku vektoru

#define _Normalize(A) {float s = _Len(A); A.x /= s; A.y /= s; A.z /= s; }
// vytvoří jednotkový vektor

#define _Dot(A,B) (B.x * A.x + A.y * B.y)
// skalární součin

#define _Cross(A,B,C) C.x = B.y * A.z - A.y * B.z; \
                      C.y = B.z * A.x - A.z * B.x; \
                      C.z = B.x * A.y - A.x * B.y;
// vektorový součin

Vector u, v, w;

u.x = c.x - a.x;
u.y = c.y - a.y;
u.z = c.z - a.z;
v.x = b.x - a.x;
v.y = b.y - a.y;
v.z = b.z - a.z;
// 2 vektory, ležící v rovině
    
_Cross(u, v, w);
_Normalize(w);
// spočítá normálový vektor roviny

p_plane->a = w.x;
p_plane->b = w.y;
p_plane->c = w.z;
p_plane->d = -(p_plane->a * a1 + p_plane->b * a2 + p_plane->c * a3);
// spočítá ještě komponent D

Tak :-) a co s tím ? Plane je rovina, definovaná normálovým vektorem (a, b, c) a kolmou vzdáleností od počátku souřadnicového systému (d). Vector je vektor se souřadnicemi. _Len spočítá délku vektoru. _Normalize upraví vektor tak, aby jeho délka byla jedna. No a _Cross spočítá tzv. cross-product, tj. do w se zapíše vektor, kolmý na vektor u a zároveň na vektor v. Vektory u a v jsou dva vektory, ležící v rovině, definované třemi body a, b a c. Teď už by snad mohlo být jasno. Tak zpátky k pyramidě. Její definice bude vypadat nějak takhle :


struct Plane {
    float a, b, c, d;
};

struct Pyramid {
    Plane p0, p1, p2, p3; 
};

Pyramid pyramid;
// přibude ještě jedna globální proměnná, kterou musíme přim startu připravit

Mohl bych ještě říct že se rovina dá transformovat maticí (jako snad všechno ;-)) Takže vidíme, že pyramida je opravdu ze čtyř rovin. Ale jak je spočítat ? V praxi se zadá rozlišení a perspektivní zkreslení, které musí být stejné jako při výpočtu 2d souřadnic bodu. A vypadá to asi takhle :


void SetFOV(float n_Width, float n_Height, float f_z_delta, Pyramid *p_pyramid)
{
    float x_right, y_bottom, x_left, y_top;

    x_left = -n_Width / (2 * f_z_delta);
    y_top =  n_Height / (2 * f_z_delta);
    x_right =  n_Width / (2 * f_z_delta);
    y_bottom = -n_Height / (2 * f_z_delta);
    // spočítá souřadnice průsečíků pyramidy s osami X a Y

    SetPlane(&p_pyramid->p0, 0, 0, 0, x_right, 0, 1, x_right, 1, 1);
    SetPlane(&p_pyramid->p1, 0, 0, 0, x_left, 1, 1, x_left, 0, 1);
    SetPlane(&p_pyramid->p2, 0, 0, 0, 0, y_bottom, 1, 1, y_bottom, 1);
    SetPlane(&p_pyramid->p3, 0, 0, 0, 1, y_top, 1, 0, y_top, 1);
    // spočítá rovnice jednotlivých rovin
}

Tak. Parametry n_Width a n_Height je výška a šířka obrazovky, f_z_delta je ona konstanta perspektivního zkreslení (měly by být dvě, jedna pro horizontální a druhá pro vertikální rozměr (obrazovka není čtvercová), ale my používáme jen jednu). Pro 320×200 je to přesně 320 pro 45°, už jsem to jednou říkal, i jak to spočítat pro jiný úhel. p_pyramid je klipovací pyramida, která se zrovna počítá. Tak, pyramidu už máme vygenerovanou. Ale co teď s ní ? Pro zjednodušení napíšeme ještě dvě funkce, které by neměl být problém pochopit.


enum {
    _Front,
    _Back,
    _Onplane,
    _Split
};

int inline ClassifyVtxPl(Vertex *p_vertex, Plane *p_plane)
{
    float p;

    p = p_plane->a * p_vertex->x + p_plane->b * p_vertex->y +
        p_plane->c * p_vertex->z + p_plane->d;
    // spočítá rovnici roviny

    if(p > 0) 
        return _Front;
    if(p < 0)
        return _Back;
    return _Onplane;
}

int inline ClassifyFacePl(Face *p_face, Plane *p_plane)
{
    int fn = 0, bn = 0, i;

    for(i = 0; i < 3; i ++){
        switch(ClassifyVtxPl(p_face->vertex[i], p_plane)){
        case _Front:
            fn ++;
            break;
        case _Back:
            bn ++;
            break;
        case _Onplane:
            fn ++;
            bn ++;
            break;
        }
    }

    if(fn == 3)
        return _Front;
    if(bn == 3)
        return _Back;
    return _Split;
}

Tak. Teď už zjistíme, jestli face leží před, za nebo je protínán rovinou. Ale ještě ho neumíme rozdělit. Face se dělí po jednotlivých hranách (úsečkách), které ho tvoří. Jak získat průsečík přímky, definované dvěma body s rovinou popisuje následující funkce.


Vertex IntersectSegmentPl(Vertex *p_v1, Vertex *p_v2, Plane *p_plane)
{
    float a, b, t;
    Vertex vertex;

    a = p_plane->a * p_v1->x + p_plane->b * p_v1->y +
        p_plane->c * p_v1->z + p_plane->d;
    b = p_plane->a * p_v2->x + p_plane->b * p_v2->y +
        p_plane->c * p_v2->z + p_plane->d;
    // spočítá vzdálenosti od roviny pro oba body

    if(b - a == 0)
        return *p_v1;
    // singulární případ

    t = a / (b - a);
    // spočítá poměr vzdáleností

    vertex.x = p_v1->x + t * (p_v1->x - p_v2->x);
    vertex.y = p_v1->y + t * (p_v1->y - p_v2->y);
    vertex.z = p_v1->z + t * (p_v1->z - p_v2->z);
    vertex.u = p_v1->u + t * (p_v1->u - p_v2->u);
    vertex.v = p_v1->v + t * (p_v1->v - p_v2->v);
    // rozdělí souřadnice v poměru vzdáleností

    return vertex;
}

Tak a máme to skoro všechno. Doufám, že jste to z toho pochopili. Kdyby ne, napište a já to budu víc rozepisovat :-) Teď už jen zbývá stvořit funkci, která rozdělí face rovinou. My z něj budeme potřebovat vždy jen jednu stranu (tu uvnitř pyramidy), takže to bude jednodušší. Vezmeme vždy jednu hranu (edge) a testujeme její vrcholy. Pokud leží oba na jedné straně, hrana se buď použije celá (_Front), nebo se celá zahodí (_Back). Pokud ne, hrana se rozdělí a přidá se nový vrchol. Je to docela jednoduché :


struct Polygon {
    int n_vertex_num;
    Vertex vertex[7];
};

Polygon SplitPolygon(Polygon *p_poly, Plane *p_plane)
{
    int i, prev, status[7];
    Polygon tmp;

    tmp.n_vertex_num = 0;

    for(i = 0; i < p_poly->n_vertex_num; i ++)
        status[i] = ClassifyVtxPl(&p_poly->vertex[i], p_plane);
    // vytvoří seznam všech pozic vrcholů polygonu

    prev = p_poly->n_vertex_num - 1;
    // předchozí vrchol

    for(i = 0; i < p_poly->n_vertex_num; i ++) {
        if(status[i] == _Front) {
            if(status[prev] == _Back) {
                tmp.vertex[tmp.n_vertex_num] =
                    IntersectSegmentPl(&p_poly->vertex[prev],
                    &p_poly->vertex[i], p_plane);
                // spočítá se nový vrchol

                tmp.n_vertex_num ++;
            }
            // hrana prochází plochou

            tmp.vertex[tmp.n_vertex_num] = p_poly->vertex[i];
            tmp.n_vertex_num ++;
            // vrchol je uvnitř pyramidy
        } else if(status[prev] == _Front) {
            tmp.vertex[tmp.n_vertex_num] =
                IntersectSegmentPl(&p_poly->vertex[prev],
                &p_poly->vertex[i], p_plane);
            // spočítá se nový vrchol

            tmp.n_vertex_num ++;
            // hrana prochází plochou
        }

        prev = i;
        // předchozí vrchol
    }

    return tmp;
}

No, ani to nebolelo. Teď už se to složí všechno jen do těla jedné funkce a zábava může začít ! Ona svatá funkce, která ušetří spoustu času při počítání polygonů je zde :


Polygon Clip_Face(Face *p_face, Pyramid *p_pyramid)
{
    int a, b, c, d, i;
    Polygon poly;

    a = ClassifyFacePl(p_face, &p_pyramid->p0);
    b = ClassifyFacePl(p_face, &p_pyramid->p1);
    c = ClassifyFacePl(p_face, &p_pyramid->p2);
    d = ClassifyFacePl(p_face, &p_pyramid->p3);
    // otestuje face proti jednotlivým rovinám

    poly.n_vertex_num = 3;
    for(i = 0; i < 3; i ++)
        poly.vertex[i] = p_face->vertex[i][0];
    // zkopíruje vertexy do polygonu

    if(a == _Front && b == _Front && c == _Front && d == _Front)
        return poly;
    // pokud je uvnitř všech rovin, nic se neděje

    if(a == _Back || b == _Back || c == _Back || d == _Back) {
        poly.n_vertex_num = 0;
        return poly;
    }
    // pokud je za jednou z rovin, nebude vůbec vidět

    if(a == _Split)
        poly = SplitPolygon(&poly, &p_pyramid->p0);
    if(b == _Split)
        poly = SplitPolygon(&poly, &p_pyramid->p1);
    if(c == _Split)
        poly = SplitPolygon(&poly, &p_pyramid->p2);
    if(d == _Split)
        poly = SplitPolygon(&poly, &p_pyramid->p3);
    // jinak se musí podle rovin "ořezat"

    return poly;
}

To by jsme měli. Když to přebereme, bude naše stará - ne ta stará ! je vidět na co myslíte, hovada :-) - DrawObject() vypadat nějak takhle :


void DrawObject(Matrix *m_object, Matrix *m_camera,
    Object *p_object, unsigned __int32 *video)
{
    Face *cur_face, face;
    float tx, ty, tz;
    float x, y, z;    
    Matrix m, im;
    Polygon poly;
    Vertex v[3];
    int i, j;

    face.vertex[0] = &v[0];
    face.vertex[1] = &v[1];
    face.vertex[2] = &v[2];
    // nastaví vertexy pro naši dočasnou face

    Matrix_Inverse(m_camera, &m);
    Matrix_Multiply(&m, m_object, &m);
    Matrix_Inverse(&m, &im);
    // spočítá transformační matice

    for(i = 0; i < p_object->n_face_num; i ++){
        if((p_object->face[i].normal.c * im[3][2] +
           p_object->face[i].normal.a * im[3][0] +
           p_object->face[i].normal.b * im[3][1] +
           p_object->face[i].normal.d) > 0.1)
            continue;
        // backface culling (ne cooling : cull - třídit)

        cur_face = &p_object->face[i];
        for(j = 0; j < 3; j ++){
            x = cur_face->vertex[j]->x;
            y = cur_face->vertex[j]->y;
            z = cur_face->vertex[j]->z;
            //
            tx = x * m[0][0] + y * m[1][0] + 
                 z * m[2][0] +     m[3][0];
            ty = x * m[0][1] + y * m[1][1] + 
                 z * m[2][1] +     m[3][1];
            tz = x * m[0][2] + y * m[1][2] + 
                 z * m[2][2] +     m[3][2];
            //
            face.vertex[j]->x = tx;
            face.vertex[j]->y = ty;
            face.vertex[j]->z = tz;
        }

        poly = Clip_Face(&face, &pyramid);
        // prožene face pyramidou

        if(poly.n_vertex_num) { // pokud je face vidět, bude to nenulové číslo
            for(j = 0; j < poly.n_vertex_num; j ++) {
                if(poly.vertex[j].z < 0.1) {
                    poly.n_vertex_num = 0;
                    break;
                }
                // pokud je z - souřadnice moc malá, zahodíme polygon

                poly.vertex[j].x = (n_Width / 2) + (poly.vertex[j].x *
                    z_delta) / poly.vertex[j].z;
                poly.vertex[j].y = (n_Height / 2) + (poly.vertex[j].y *
                    z_delta) / poly.vertex[j].z;
                // perspektivní korekce
            }

            DrawPolygon(&poly, video);
            // vykreslí polygon na obrazovku
        }
    }
}

Je to krásné, jasné a čisté. Ale jde to ještě trochu vylepšit. Jestli jste si to pořádně prohlídli, asi jste zjistili, že face se napřed transformuje a až potom se zjišťuje, jestli vůbec bude vidět v klipovací pyramidě. Tenhle proces jde i obrátit a jde transformovat klipovací pyramida. Není to sice tak jednoduché, jako transformovat vektor, ale zase moc složitá to taky není. (takže se to vyplatí - většinou) Dělá se to asi takhle :


void TransformPlane(Plane *p, Matrix m)
{
    p->ta = p->a * m[0][0] + p->b * m[1][1] + p->c * m[1][2];
    p->tb = p->a * m[1][0] + p->b * m[1][1] + p->c * m[0][2];
    p->tc = p->a * m[2][0] + p->b * m[2][1] + p->c * m[2][2];
    p->td = p->d - (p->ta * m[3][0] + p->tb * m[3][1] + p->tc * m[3][2]);
    // používáme transponovanou matici !!!
}
// Nezapomeňte změnit ClassifyVtxPl() a IntersectSegmentPl() tak, aby
// používaly nové hodnoty (vyměníte a -> ta, b -> tb, c -> tc, d -> td)

A je to. Otázkou je, o kolik rychlejší to bude, protože se zase bude transformovat víc vertexů na rozdělených polygonech. Záleží na tom, jestli svět bude 'rotující brambora', nebo třeba aréna z Ut / Q3 (cca 10.000 polygonů). No, ještě přepíšu draw object (pro jistotu) a vrhneme se na rasterizaci polygonů.


void DrawObject(Matrix *m_object, Matrix *m_camera,
    Object *p_object, unsigned __int32 *video)
{
    Face *cur_face, face;
    float tx, ty, tz;
    float x, y, z;    
    Matrix m, im;
    Polygon poly;
    Vertex v[3];
    int i, j;

    face.vertex[0] = &v[0];
    face.vertex[1] = &v[1];
    face.vertex[2] = &v[2];
    // nastaví vertexy pro naši dočasnou face

    Matrix_Inverse(m_camera, &m);
    Matrix_Multiply(&m, m_object, &m);
    Matrix_Inverse(&m, &im);
    // spočítá transformační matice

    TransformPlane(&pyramid.p0, camera);
    TransformPlane(&pyramid.p1, camera);
    TransformPlane(&pyramid.p2, camera);
    TransformPlane(&pyramid.p3, camera);
    // Natransformuje pyramidu

    for(i = 0; i < p_object->n_face_num; i ++){
        if((p_object->face[i].normal.c * im[3][2] +
           p_object->face[i].normal.a * im[3][0] +
           p_object->face[i].normal.b * im[3][1] +
           p_object->face[i].normal.d) > 0.1)
            continue;
        // backface culling

        poly = Clip_Face(&p_object->face[i], &pyramid);
        // prožene face pyramidou

        if(!poly.n_vertex_num)
            continue;
        // pokud je face vidět, bude to nenulové číslo

        for(j = 0; j < poly.n_vertex_num; j ++) {
            x = cur_face->vertex[j]->x;
            y = cur_face->vertex[j]->y;
            z = cur_face->vertex[j]->z;
            //
            tx = x * m[0][0] + y * m[1][0] + 
                 z * m[2][0] +     m[3][0];
            ty = x * m[0][1] + y * m[1][1] + 
                 z * m[2][1] +     m[3][1];
            tz = x * m[0][2] + y * m[1][2] + 
                 z * m[2][2] +     m[3][2];
            //
            if((poly.vertex[j].z = tz) < 0.1) {
                poly.n_vertex_num = 0;
                break;
            }
            // pokud je z - souřadnice moc malá, zahodíme polygon

            poly.vertex[j].x = (n_Width / 2) + (tx * z_delta) / tz;
            poly.vertex[j].y = (n_Height / 2) + (ty * z_delta) / tz;
            // perspektivní korekce
        }

        if(poly.n_vertex_num)
            DrawPolygon(&poly, video);
            // vykreslí polygon na obrazovku
    }
}

No a jak jsem sliboval na začátku, dáme si ještě rasterizaci polygonů, abysme to lépe viděli :-) Jestli si pamatujete, jak jsem ukazoval Brenshamův algoritmus pro kreslení čar, tak s polygony je to podobné. Vygeneruje se datová struktura, zvaná edge pro každou hranu polygonu, která není vodorovná (pak ji nahradí její okolní hrany). Hrany jsou ve dvou listech (pro levou a pravou polovinu polygonu). To se určí tak, že se vezme bod s nejmenší a největší y (2d) souřadnicí a pravá strana je od vrcholu s min. y po vrchol s max. y a levá od vrcholu s max. y po vrchol s min. y (=> záleží na pořadí vrcholů - 3d studio je má po směru hod. ručiček - myslím :-| ). Potom se vezme pravá a levá hrana a jde se po obrazových řádkách odshora dolů a pro každý řádek se určí dvě x-ové souřadnice a od té menší k té větší se udělá jednoduše čára (bez textury - zatím). Napřed se podíváme na ty edge :


struct Poly_Edge {                            
    int x, y, num, dxdy;            
};

static Poly_Edge left_edge_buff[7];
static Poly_Edge right_edge_buff[7];
static Poly_Edge *p_left_edge;
static Poly_Edge *p_right_edge;
static int n_left_edge_num, n_right_edge_num;

Někteří si možná říkáte, proč je dxdy integer ? Vždyť to přece nemůže stačit ! Ale on to není zase až tak moc integer. Je to malá programátorská finta, kterou použijeme ještě mnohokrát. Říká se tomu "fixed point". Tohle je konkrétně 16:16 fixed point. To znamená, že desetinná část čísla zabere 16 bitů a celá taky. Dělá se to tak, že se float - hodnota vynásobí 0x10000 (2 na 16 = 65536) a zaokrouhlí. Pak zabere 32 bitů a mezi sebou se pak můžou normálně násobit a sčítat nebo cokoli jiného. Když chcete zpátky hodnotu čísla, vydělíte ho zase 0x10000 a dostanete float. Pokud vám stačí integer, použijete rychlejší posun o šestnáct míst doprava. Kdyby někdo nevěděl jak to v c-čku zapsat, tak je to nějak takhle : int = fix >> 16. Jasno ? Fixed point ušetří hodně času procesoru, když se s ním umí zacházet. Pro fixed point (tj. integer) se totiž používají jen integerovské operace. A ty jsou rychlejší, než float - operace. No, zase jsme se trochu zdrželi, je na čase ukázat si, jak se vygeneruje taková struktura edge.


int Add_Edge(Vertex *v_cur, Vertex *v_next, Poly_Edge *edge)
{
    float y1, y2;
    int num;

    y1 = (float)ceil(v_cur->y);
    y2 = (float)ceil(v_next->y);
    num = (int)(y2 - y1);

    if(!num)
        return 0;

    edge->dxdy = (int)((v_next->x - v_cur->x) / (v_next->y - v_cur->y) * 65536);
    edge->x = (int)(v_cur->x * 65536 + (y1 - v_cur->y) * edge->dxdy);
    edge->y = (int)y1;
    edge->num = num;
    // spočítá první x a y, sklon (dxdy) a počet obrazových řádek

    return 1;
}

int Create_Edges(Polygon *p_poly)
{
    int n_bottom, n_top, n_cur;
    float f_min_y;
    float f_max_y;

    n_left_edge_num = 0;
    n_right_edge_num = 0;
    // počty hran

    f_min_y =  0x10000;
    f_max_y = -0x10000;
    n_cur = 0;
    while(n_cur < p_poly->n_vertex_num) {
        if(p_poly->vertex[n_cur].y < f_min_y) {
            f_min_y = p_poly->vertex[n_cur].y;
            n_top = n_cur;
        }
        if(p_poly->vertex[n_cur].y > f_max_y) {
            f_max_y = p_poly->vertex[n_cur].y;
            n_bottom = n_cur;
        }
        n_cur ++;
    }
    // najde nejvrchnější a nejspodnější vrchol

    if((int)ceil(f_min_y) >= (int)ceil(f_max_y))
        return 0;
    // zkontroluje zda se vejde do rastru

    n_cur = n_top;
    while(n_cur != n_bottom) {
        if(Add_Edge(&p_poly->vertex[n_cur], &p_poly->vertex[(n_cur + 1) %
           p_poly->n_vertex_num], &right_edge_buff[n_right_edge_num]))
            n_right_edge_num ++;
        n_cur = (n_cur + 1) % p_poly->n_vertex_num;
    }
    // sjede pravou větev

    n_cur = n_bottom;
    while(n_cur != n_top) {
        if(Add_Edge(&p_poly->vertex[(n_cur + 1) % p_poly->n_vertex_num],
           &p_poly->vertex[n_cur], &left_edge_buff[n_left_edge_num]))
            n_left_edge_num ++;
        n_cur = (n_cur + 1) % p_poly->n_vertex_num;
    }

    p_left_edge = left_edge_buff + n_left_edge_num - 1;
    p_right_edge = right_edge_buff;

    return 1;
}

No, máme vygenerované edge a už stačí jen vykreslovat, ale nejen ti pozornější si jistě všimli, že polygony jsou kreslené jen jednou barvou. To bysme ale pořádně neviděli ani, jak objekty vypadají a tak ještě přidáme něco jednoduchého. Je to stínování. Metoda, kterou si budeme popisovat se jmenuje "flat shading" a je to určení barvy podle normálového vektoru facu. Když se natransformuje, tak jeho hodnota z (musí být normalizovaný udává vlastně sklon vůči kameře - světlost) -> stačí jen násobit r, g a b touto hodnotou a pak dopočítat výslednou barvu v RGB. Takže to vypadá nějak takhle :


void DrawObject(Matrix *m_object, Matrix *m_camera,
    Object *p_object, unsigned __int32 *video, unsigned __int32 color)
{
    unsigned __int32 n_color;
    float tx, ty, tz;
    float x, y, z;    
    Matrix m, im;
    Polygon poly;
    Vertex v[3];
    Face *cur_face, face;
    int i, j;

    face.vertex[0] = &v[0];
    face.vertex[1] = &v[1];
    face.vertex[2] = &v[2];
    // nastaví vertexy pro naši dočasnou face

    Matrix_Inverse(m_camera, &m);
    Matrix_Multiply(&m, m_object, &m);
    Matrix_Inverse(&m, &im);
    // spočítá transformační matice

    for(i = 0; i < p_object->n_face_num; i ++){
        if((p_object->face[i].normal.c * im[3][2] +
           p_object->face[i].normal.a * im[3][0] +
           p_object->face[i].normal.b * im[3][1] +
           p_object->face[i].normal.d) > 0.1)
            continue;
        // backface culling

        cur_face = &p_object->face[i];
        for(j = 0; j < 3; j ++){
            x = cur_face->vertex[j]->x;
            y = cur_face->vertex[j]->y;
            z = cur_face->vertex[j]->z;
            //
            tx = x * m[0][0] + y * m[1][0] + 
                 z * m[2][0] +     m[3][0];
            ty = x * m[0][1] + y * m[1][1] + 
                 z * m[2][1] +     m[3][1];
            tz = x * m[0][2] + y * m[1][2] + 
                 z * m[2][2] +     m[3][2];
            //
            face.vertex[j]->x = tx;
            face.vertex[j]->y = ty;
            face.vertex[j]->z = tz;
        }

        poly = Clip_Face(&face, &pyramid);
        // prožene face pyramidou

        x = p_object->face[i].normal.a;
        y = p_object->face[i].normal.b;
        z = p_object->face[i].normal.c;
        tx = x * m[0][0] + y * m[1][0] + z * m[2][0];
        ty = x * m[0][1] + y * m[1][1] + z * m[2][1];
        tz = x * m[0][2] + y * m[1][2] + z * m[2][2];
        // natransformuje normálu

        x = (float)sqrt(tx * tx + ty * ty + tz * tz);
        // normalizuje ji

        j = abs((int)(tz / x * 255));
        if(j > 255)
            j = 255;
        // a spočítá osvětlení podle jednoduchého pravidla
        // čím je face přikloněnější ke kameře, tím víc bude
        // osvětlená. to vlastně znamená čím větší bude z-ová
        // složka normálového vektoru větší (nebo x a y menší)
        
        n_color = ((((color & 0xff0000) * j) & 0xff000000) +
                   (((color & 0x00ff00) * j) & 0x00ff0000) +
                   (((color & 0x0000ff) * j) & 0x0000ff00)) >> 8;
        // spočítá novou barvu - rozloží na složky R, B a B
        // a ty vynásobí světlostí

        if(poly.n_vertex_num) { // pokud je face vidět, bude to nenulové číslo
            for(j = 0; j < poly.n_vertex_num; j ++) {
                if(poly.vertex[j].z < 0.1) {
                    poly.n_vertex_num = 0;
                    break;
                }
                // pokud je z - souřadnice moc malá, zahodíme polygon

                poly.vertex[j].x = (n_Width / 2) + (poly.vertex[j].x *
                    z_delta) / poly.vertex[j].z;
                poly.vertex[j].y = (n_Height / 2) + (poly.vertex[j].y *
                    z_delta) / poly.vertex[j].z;
                // perspektivní korekce
            }

            DrawPolygon(&poly, video, n_color);
            // vykreslí polygon na obrazovku
        }
    }
}

Doufám, že to dokážete takhle rychle vstřebávat. Zatím se nepoužívá transformace klipovací pyramidy, ať máte doma taky co zkoušet. Teď už nám schází jen funkce DrawPolygon(poly, video, color). Tak hrr na ní :


int DrawPolygon(Polygon *p, unsigned __int32 *video, unsigned __int32 color)
{
    int start_y, left_x, right_x;
    int l, c;

    if(!Create_Edges(p)) 
        return 0;

    start_y = p_right_edge->y;
    left_x = p_left_edge->x;
    right_x = p_right_edge->x;
    // zkopíruje vlastnosti edge

    video += n_Width * start_y;
    // nastaví pointer na prvni řádku rastru

    while(1) {
        c = left_x >> 16;
        l = right_x >> 16;
        // převod zpátky na int

        for(; l < c; l ++)
            video[l] = color;
        // kreslí vodorovny segment

        video += n_Width;
        z_buff += n_Width;
        z += dzdy;
        // posun na další řádek obrazovky

        if(-- p_left_edge->num == 0) {
            if(p_left_edge -- == left_edge_buff)
                return 1;
            // pokud dojde na konec bufferu, je konec polygonu
            left_x = p_left_edge->x;
        } else
            left_x += p_left_edge->dxdy;

        if(-- p_right_edge->num == 0) {
            p_right_edge ++;
            right_x = p_right_edge->x;
        } else 
            right_x += p_right_edge->dxdy;
    }

    return 1;
}

No.. tolik zdrojáku jsem ještě za jeden den nenapsal (myslím). Tentokrát vám dám jenom krátkej sampl bez zdrojáků. No a jestli to někdo bude číst (třeba jen jeden nebo líp jedna) tak mi napíše hezkej email (a nebo víc :-) ) a já budu v příštím díle pokračovat s tím Z-Bufferem. (Teď se spráně vykreslují jen konvexní útvary - kostka, koule, pyramida ...) Můžete zkusit seřadit facy podle vzdálenosti středu od kamery a pak to bude trochu fungovat, když se objekty nebudou protínat. Jestli jste hráli Tomb - Raidera I nebo ][, tak víte, o čem mluvím. Odborně se tomu říká "malířův algoritmus", ale ten si zkuste sami. Tož se mejte !

    -tHE SWINe-

lara
Tomb raider 2 - Lara má menší polygony než plošina a levá noha se seřadila špatně

flatshaded

 Flat - shading

painters_alg

 Malířův algoritmus

zpět


Valid HTML 4.01!
Valid HTML 4.01