English Welcome Kecy Programování 3D - Engine Guestbook Odkazy Downloady O autorovi Napiš mi Mailform
box_cz 3D - Engine 05 - z-buffer a texture-mapping box_cz

Hallejujah ! Jestliže věříte v boha, děkujte mu za další pokračování seriálu o 3D Enginech, který už mnozí z vás začali pokládat za dead project. Ale jsem tu zas s novým dílem (a taky s kupou novejch průserů, ale o ty se s váma nemíním dělit :-) ). Plánuju, že dneska si povíme o texturování a z-bufferování. Předem bych chtěl upozornit, že inspirací byl krásný dokument, zvaný "Fatmap" (Fast affine texture mapping) od Matse Byggmastara a.k.a. MRI / Doomsday, originál si můžete najít na internetu, je mnohem optimalizovanější, ale nahlíží na problém z trochu jiné strany (podobné jsou jen názvy některých proměnných a základní nápad mapování). Asi první, kdo dokázal udělat texturemapping "realtime" byl >Doomtvůrce< Adrian Carmack, který ho dokázal optimalizovat tak, abychom si mohli večer krásně zapařit Dooma i na i486 hezky plynule :-) ... Možná se někteří ptáte, co má společného texturemapping a Z-Buffer. Povím vám o co jde : Z-Buffer (někdo to překládá jako paměť hloubky) je pomůcka, jak zjistit, jestli bod bude na výsledném obrázku vidět. Je to vlastně pole o stejné velikosti jako obrazovka, které se napřed naplní nulovými hodnotami (zpravidla jde o fixed point) a potom když se kreslí nějáký bod polygonu, program se zeptá, jestli už tam není nějaký bod, který leží blíž než ten, co se zrovna kreslí. Pokud ne, hodnota v Z-Bufferu se přepíše novou hodnotou a do videopaměti se vykreslí nový bod, který překryje ten předchozí. Pokud už tam je, nic se nedělá a jde se na další pixel. Mezi výhody Z-Bufferu patří třeba perfektní prolínání objektů :


z-buffer


Mezi nevýhody patří třeba to, že je hodně pomalý : Na vykreslení každého bodu musíte porovnat číslo a body se často mnohokrát překreslují. Z-Buffer navíc vyžaduje poměrně přesné ukládání čísel a tak zabere hodně paměti. Ale pořád jsem vám ještě neřekl tu spojitost s texturováním. Tou je souřadnice Z, která se musí "rozmáznout" po celém povrchu polygonu. Dělá se to tak, že pro první tři souřadnice se vytvoří rovina (rovina je definovaná třemi body), přičemž souřadnice jsou 2D - x, 2D - y a 3D - z (to znamená, že x a y jsou perspektivně korigované, ale z je to, co "vylezlo" z transformace maticí). Rovnice roviny zní :


rovnice roviny


Potom si můžeme vyjádřit z-ovou souřadnici jako rovnici :


z = - (ax + by + cz) / c


No a z toho vidíme, že tu jsou nějaké konstanty, které si můžeme předpočítat a použít při mapování :


dzdx = - a / c  dzdy = - b / c  begz = - d / c


Přičemž dzdx je "delta z / delta x", dzdy je "delta z / delta y" a begz je souřadnice z v bodě x = 0, y = 0 (levý horní roh obrazovky). Už vám to začíná docházet ? Prostě spočítáme souřadnici pro každý řádek a pak už k ní jen přičítáme dzdx po každém pixelu. Už nám schází jen to D-čko. To si odvodíme jinak a spočítáme přímo :


- d / c = z - x * - a / c - y * - b / c


V c-čku by se to všechno spočítalo asi takhle :


struct Vertex {
    float x, y, z;
    float u, v;
    //
    float illum;
    //
    Vector normal;
};

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

float didx, didy, begi;
// i jako illumination (světlost)

int Compute_Interpolation_Consts(POLYGON *p)
{
    float x[2], y[2], l[2];
    float a, b, c;
    
    x[0] = p->vtx[1].x - p->vtx[0].x;
    x[1] = p->vtx[2].x - p->vtx[0].x;

    y[0] = p->vtx[1].y - p->vtx[0].y;
    y[1] = p->vtx[2].y - p->vtx[0].y;
    
    l[0] = (float)p->vtx[1].illum - (float)p->vtx[0].illum; 
    l[1] = (float)p->vtx[2].illum - (float)p->vtx[0].illum;
    // vektory ležící na naší rovině ...

    a = y[0] * l[1] - y[1] * l[0];
    b = l[0] * x[1] - l[1] * x[0];
    c = x[0] * y[1] - x[1] * y[0];
    // cross product - spočítá normálu

    if(c == 0) 
        return 0;
    
    didx = - a / c;
    didy = - b / c;
    // přesně podle vzorečků ...

    begi = (float)p->vtx[0].illum - p->vtx[0].x * didx - p->vtx[0].y * didy;
    // rychlejší určení - d / c

    return 1;
}

int DrawPolygon(Polygon *p, unsigned __int32 *video, unsigned __int32 color)
{
    int start_y, left_x, right_x;
    int didx_fp, k, r_k;
    float illum;
    int l, c;

    if(!Create_Edges(p)) 
        return 0;

    if(!Compute_Interpolation_Consts(p)) 
        return 0;

    start_y = p_right_edge->y;
    left_x = p_left_edge->x;
    right_x = p_right_edge->x;
    // vlastnosti první hrany ...

    video += n_Width * start_y;
    // posune video na první řádek

    illum = begi + didy * start_y;
    // světlost na prvním řádku

    didx_fp = (int)(didx * 65536);
    // 16:16 fixed point

    while(1) {
        l = left_x >> 16;
        c = right_x >> 16;
        // zpět na int

        k = (int)(illum * 65536) + c * didx_fp;
        // 16:16 fixed point

        for(; c < l; c ++) {
            r_k = k >> 16;
            // zpět na int ...

            if(r_k > 255)
                r_k = 255;
            if(r_k < 0)
                r_k = 0;
            // zkontroluje meze ... (to by tu nemělo být,
            // ale jinak to bude blikat)

            video[c] = ((((color & 0xff0000) * r_k) & 0xff000000) +
                (((color & 0x00ff00) * r_k) & 0x00ff0000) +
                (((color & 0x0000ff) * r_k) & 0x0000ff00)) >> 8;
            // mixne barvu
            
            k += didx_fp;
            // další pixel
        }
        // vykreslí segment

        video += n_Width;
        illum += didy;
        // další řádka

        if(-- p_left_edge->num == 0) {
            if(p_left_edge -- == left_edge_buff)
                return 1;
            // zjistí jestli je konec
            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;
}

Nakonec to ale nebude tak jednoduché, jak se zdá, protože takhle rozkládáme souřadnici lineárně (a proto taky rychle), ale v normálním životě "textury nejsou lineární" :


nonlinear
Je vidět jak čtverce blíž kamery jsou větší, než ty dál od kamery..


No, ale abych vám nezkazil radost, je tady zdroják pro jednoduchý engine se Smooth shading (to se určí pseudonormálové vektory jednotlivých vertexů zprůměrováním normálových vektorů faců, které mají ten vrchol společný - prohlédněte si funkci ComputeVertexNormals() v modulu load3ds.cpp a tak se určí světlost jako při flat shading, ale pro jednotlivé vrcholy. Potom už se světlost jen interpoluje po povrchu a naštěstí není vidět, že se interpoluje lineárně). Běží to pod DirectX 7.01, můžete si stáhnout nový modul, jen v Project > Settings v záložce Link musíte přidat kromě "ddraw.lib" ještě "dxguid.lib" do seznamu knihoven. (Budete si asi muset stáhnout knihovny a hlavičky pro práci s direct x ze sekce downloads a nakopírovat je do lib a include složek vašeho kompilátoru ...)


smooth shading

 Smooth engine - Font engine + Antialiassing


Doufám, že se vám to bude líbit :-), dal jsem si s tím docela záležet. Používá to zatím ještě pořád malířův algoritmus, má to jednoduchý modul pro kreslení fontů a používá to skoro-Quincunx pro antialiasování výsledného obrazu. Ale teď půjdeme dál..


Perspektivně korektní mapování

Už jsem řekl, že mapování nebude lineární.. No jo, ale co s tím ? Chceme mít výhody lineárního mapování, ale potřebujeme mapování hyperbolické... Někdo chytrý vymyslel trik, převádějící nelineární závislost na lineární, a pak ho použil ve své hře (Quake ?). Klidně si stáhněte z www.IDSoftware.com zdrojáky Quaka 1 a tam si ten trik můžete najít. Funguje to tak, že hyperbolickou funkci (y = 1 / x) převedeme na lineární (x) a když potřebujeme její hodnotu (y), musíme zase zpětně převádět zpátky. Zpětný převod je však dost náročný, takže opět ten někdo vymyslel malou optimalizaci : Funkce se bude zpětně korigovat každý n-tý pixel a mezi nimi se závislost bude brát jako lineární :


interpolation trick


Opticky tuto chybu téměř nemáte šanci postihnout, ale optimalizace je výrazná (v praxi by to bylo několik - podle počtu interpolovaných souřadnic - dělení na každý pixel). Tentokrát se nebude mapovat Z, ale 1/Z a každá souřadnice (mapovaná, ne x a y) bude na 1/Z závislá. Potom to bude vypadat asi takhle :


int n_Width, n_Height;
// šířka a výška obrazovky

__int32 *z_buffer;
// musí se naalokovat na velikost n_Width * n_Height

float dzdx, dzdy, begz;
// mapovací konstanty (jen pro Z)

int Compute_Interpolation_Consts(Polygon *p_poly)
{
    float x[2], y[2], z[2];
    float a, b, c;
    
    x[0] = p_poly->vertex[1].x - p_poly->vertex[0].x;
    x[1] = p_poly->vertex[2].x - p_poly->vertex[0].x;

    y[0] = p_poly->vertex[1].y - p_poly->vertex[0].y;
    y[1] = p_poly->vertex[2].y - p_poly->vertex[0].y;
    
    z[0] = (1 / p_poly->vertex[1].z) - (1 / p_poly->vertex[0].z); 
    z[1] = (1 / p_poly->vertex[2].z) - (1 / p_poly->vertex[0].z);
    // vektory (od vrocholu 0 do 1, 2 pro každou souradnici)
    // všimněte si, že se používá 1/z a ne z

    a = y[0] * z[1] - y[1] * z[0];
    b = z[0] * x[1] - z[1] * x[0];
    c = x[0] * y[1] - x[1] * y[0];
    // cross product

    if(c == 0) 
        return 0;
    
    dzdx = - a / c;
    dzdy = - b / c;
    // určení hodnot podle rovnice

    begz = (1 / p_poly->vertex[0].z) -
        p_poly->vertex[0].x * dzdx -
        p_poly->vertex[0].y * dzdy;
    // rychlejší určení - d / c

    return 1;
}
// tak, to máme spočítané konstanty pro mapování ..
// ted ještě jak je použít

void Interpolate_Segment(int z1, int dz, unsigned __int32 *video,
    unsigned __int32 *v2, __int32 *z_buff, unsigned __int32 color)
{
    do {
        if((unsigned)(*z_buff) > (unsigned)z1){
            *video = color;
            *z_buff = z1;
        }
        // pokud je bod blíž, vykreslí
        // se na obrazovku i do Z-Bufferu

        video ++;
        z_buff ++;
        // nová pozice (další bod)

        z1 += dz;
        // interpolace z-ka
    } while(video < v2);
    // porovnávat tyhle dvě hodnoty je rychlejší, než třeba for
    // cyklus, protoze ušetříte jedno přičítání navíc ...
}

void Draw_Segment(int c, int l, float z, __int32 *z_buff,
    unsigned __int32 *video, unsigned __int32 color)
{
    int dz, z1, z2;

    z1 = (int)(0x10000 / z);
    // převod zpátky na lineární, do 16:16 fp

    while(c >= 16) {
        z += dzdx * 16;
        // pro optimalizaci přepočítáme jen každý šestnáctý pixel

        z2 = (int)(0x10000 / z);
        // určení dalšího lineárního z

        dz = (z2 - z1) / 16;
        // spočítá rovnici přímky, reprezentující
        // vzdálenost polygonu od obrazovky

        Interpolate_Segment(z1, dz, video, video + 16, z_buff, color);
        // kreslící smyčka

        c -= 16;
        // sníží počet pixelů o 16
        
        z_buff += 16;
        video += 16;
        // posune ukazatele dál

        z1 = z2;
        // přesune se na další souřadnice
    }

    if(c > 0) { // kreslí zbytek lajny (pokud délka není násobkem 16)
        z += dzdx * c;

        z2 = (int)(0x10000 / z);

        dz = (z2 - z1) / 16;

        Interpolate_Segment(z1, dz, video, video + c, z_buff, color);
    }
}

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

    if(!Create_Edges(p)) 
        return 0;

    if(!Compute_Interpolation_Consts(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;
    z_buff = &z_buffer[n_Width * start_y];
    // nastaví video a z-buffer (jen pomocný pointer,
    // ne ten globální !) na první řádku rastru

    z = begz + dzdy * start_y;
    // Z prvního řádku rastru

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

        c -= l ++;

        if(c > 0)
            Draw_Segment(c, l, z + dzdx * l, z_buff + l, video + l, color);
        // kreslí vodorovný segment polygonu

        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;
}

void Clean_ZBuffer()
{
    int i, m;

    m = Width * Height;

    for(i = 0; i > m; i ++)
        z_buffer[i] = 0xffffffff;
    // naplní z-buffer maximálními hodnotami
    // nesmí se to zapomenout udělat, jinak
    // to bude kreslit nesmysly !
}

Ještě se podívejte na obrázek :


z-buffer 1

 Z_Buffer + Flat shading


No.. A máme tu engine se z-bufferem. Funguje to krásně přesně a pomalu. Proto hned v příštím díle bude popsaná trochu jiná technika na určování viditelnosti, jmenující se BSP Strom. Ale o tom až příště. Teď jsem ještě slíbil ty textury. Napřed budeme potřebovat ty textury z něčeho nahrát. Asi nejjednodušší je pro nás 24 Bpp Bitmapa. Možná jste si všimli, že rutina pro nahrávání bitmap je v text-enginu toho enginu se Smooth shading. Pro jistotu ji sem dám ještě jednou :


#include <windows.h>
#include <stdio.h>
// ve windows.h jsou informace o bitmape
// (typy BITMAPFILEHEADER a BITMAPINFOHEADER)
// a ve stdio.h jsou i/o rutiny (FILE)

struct BMP {
    unsigned __int32 *bytes; // pixely bitmapy - 32 bpp
    int width, height;       // vyska a sirka bitmapy
};

BMP *Load_TrueColorBMP(char *path)
{
    BITMAPFILEHEADER bmfh;
    BITMAPINFOHEADER bmih;
    int Width, Height;
    char *lpbBuf;
    int BuffLen;
    FILE *BMPfp;
    BMP *tmp;
    int i, j;
    
    if((BMPfp = fopen(path, "rb")) == NULL)
        return NULL;

    fread(&bmfh, sizeof(bmfh), 1, BMPfp);
    fread(&bmih, sizeof(bmih), 1, BMPfp);
    if(bmfh.bfType != ('B'|('M' << 8)))
        return NULL; 

    if(bmih.biBitCount == 24 && bmih.biCompression == BI_RGB) {
        if((tmp = (BMP*)malloc(sizeof(BMP))) == NULL)
            return NULL;

        Width = bmih.biWidth;
        Height = bmih.biHeight;

        tmp->n_Width = Width;
        tmp->n_Height = Height;
        if((tmp->bytes = (unsigned __int32*)malloc(Width * Height * 4)) == NULL)
            return NULL;
        BuffLen = ((Width * 3 + 3) >> 2) << 2;

        if((lpbBuf = (char*)malloc(BuffLen)) == NULL)
            return NULL;

        for(i = Height - 1; i >= 0; i --) {
            if((j = fread(lpbBuf, 1, BuffLen, BMPfp)) != BuffLen)
                return NULL;
            for(j = 0; j < Width; j ++) {
                tmp->bytes[j + Width * i] = RGB(lpbBuf[j * 3],
                    lpbBuf[j * 3 + 1], lpbBuf[j * 3 + 2]);
            }
        }
    } else
        return NULL;

    free(lpbBuf);
    return tmp;
}

No, doufám že to nemusím popisovat.. Napřed se načtou hlavičky bitmapy, určí se zda je bitmapa 24 bpp a RGB - nekomprimovaný truecolorový rastr. Potom se naalokuje pole pro pixely ve struktuře BMP a buffer pro čtení jednotlivých řádků obrazu (musí být zarovnaný na 32 bit) a pak už se jen načítá z disku.. Tak.. Ještě jsem pořád nevysvětlil, jak vygenerovat souřadnice pro textury.. Říkal jsem jen že jsou závislé na z, takže se na to vrhneme, už je to stejně moc dlouhý povídání :


int Compute_Interpolation_Consts(Polygon *p_poly)
{
    float x[2], y[2], z[2], u[2], v[2];
    float a, b, c;
    
    x[0] = p_poly->vertex[1].x - p_poly->vertex[0].x;
    x[1] = p_poly->vertex[2].x - p_poly->vertex[0].x;

    y[0] = p_poly->vertex[1].y - p_poly->vertex[0].y;
    y[1] = p_poly->vertex[2].y - p_poly->vertex[0].y;
    
    z[0] = (1 / p_poly->vertex[1].z) - (1 / p_poly->vertex[0].z); 
    z[1] = (1 / p_poly->vertex[2].z) - (1 / p_poly->vertex[0].z);
    
    u[0] = 1 / p_poly->vertex[1].z * p_poly->vertex[1].u -
        1 / p_poly->vertex[0].z * p_poly->vertex[0].u;
    u[1] = 1 / p_poly->vertex[2].z * p_poly->vertex[2].u -
        1 / p_poly->vertex[0].z * p_poly->vertex[0].u;

    v[0] = 1 / p_poly->vertex[1].z * p_poly->vertex[1].v -
        1 / p_poly->vertex[0].z * p_poly->vertex[0].v;
    v[1] = 1 / p_poly->vertex[2].z * p_poly->vertex[2].v -
        1 / p_poly->vertex[0].z * p_poly->vertex[0].v;
    // vektory (od vrocholu 0 do 1, 2 pro každou souradnici)
    // vektory u a v jsou ještě násobené 1/z

    a = y[0] * z[1] - y[1] * z[0];
    b = z[0] * x[1] - z[1] * x[0];
    c = x[0] * y[1] - x[1] * y[0];
    // cross product

    if(c == 0) 
        return 0;
    
    dzdx = - a / c;
    dzdy = - b / c;
    // určení hodnot podle rovnice

    begz = (1 / p_poly->vertex[0].z) -
        p_poly->vertex[0].x * dzdx -
        p_poly->vertex[0].y * dzdy;
    // rychlejší určení - d / c
    // Z

    a = y[0] * u[1] - y[1] * u[0];
    b = u[0] * x[1] - u[1] * x[0];
    //c = x[0] * y[1] - x[1] * y[0];
    // céčko už bude pořád stejný

    dudx = - a / c;
    dudy = - b / c;
    // určení hodnot podle rovnice

    begu = (1 / p_poly->vertex[0].z * p_poly->vertex[0].u) -
        p_poly->vertex[0].x * dudx - p_poly->vertex[0].y * dudy;
    // rychlejší určení - d / c
    // U
    
    a = y[0] * v[1] - y[1] * v[0];
    b = v[0] * x[1] - v[1] * x[0];
    //c = x[0] * y[1] - x[1] * y[0];
    // céčko už bude pořád stejný

    dvdx = - a / c;
    dvdy = - b / c;
    // určení hodnot podle rovnice

    begv = (1 / p_poly->vertex[0].z * p_poly->vertex[0].v) -
        p_poly->vertex[0].x * dvdx - p_poly->vertex[0].y * dvdy;
    // rychlejší určení - d / c
    // V

    return 1;
}

Pak je použijete úplně stejně jako z-souřadnici v tom minulým zdrojáku :


int DrawPolygon(Polygon *p, unsigned __int32 *video)
{
    int start_y, left_x, right_x;
    __int32 *z_buff;
    float z, u, v;
    int l, c;

    if(!Create_Edges(p)) 
        return 0;

    if(!Compute_Interpolation_Consts(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;
    z_buff = &z_buffer[n_Width * start_y];
    // nastaví video a z-buffer (jen pomocný pointer,
    // ne ten globální !) na první řádku rastru

    z = begz + dzdy * start_y;
    u = begu + dudy * start_y;
    v = begv + dvdy * start_y;
    // z prvniho radku rastru

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

        c -= l ++;

        if(c &ht; 0) {
            Draw_Segment(c, l, u + dudx * l, v + dvdx * l,
                z + dzdx * l, z_buff + l, video + l);
        }
        // kreslí vodorovny segment

        video += n_Width;
        z_buff += n_Width;
        z += dzdy;
        u += dudy;
        v += dvdy;
        // posun na dalsi řáek 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;
}

void Draw_Segment(int c, int l, float u, float v, float z,
    __int32 *z_buff, unsigned __int32 *video)
{
    float dz, z1, z2;
    int du, u1, u2;
    int dv, v1, v2;

    z1 = (0x10000 / z);
    // převod zpátky na lineární, do 16:16 fp

    u1 = (int)(u * z1);
    v1 = (int)(v * z1);
    // u a v se převede trošku jinak

    while(c >= 16) {
        z += dzdx * 16;
        u += dudx * 16;
        v += dvdx * 16;
        // pro optimalizaci přepočítáme jen kazdý 16. pixel

        z2 = (0x10000 / z);
        // určení dalšího lineárního z
        
        u2 = (int)(u * z2);
        v2 = (int)(v * z2);
        // u a v se převede trošku jinak

        dz = (z2 - z1) / 16;
        du = (u2 - u1) / 16;
        dv = (v2 - v1) / 16;
        // spočítá delty

        Interpolate_Segment(u1, v1, z1, du, dv, dz, video, video + 16, z_buff);
        // kreslící smyčka

        c -= 16;
        // sníží počet pixelů o 16
        
        z_buff += 16;
        video += 16;
        // posune se dál

        z1 = z2;
        u1 = u2;
        v1 = v2;
        // využije předpočítané hodnoty
    }

    if(c > 0) { // kreslí zbytek lajny (pokud délka není násobkem 16)
        z += dzdx * c;
        u += dudx * c;
        v += dvdx * c; // neuděláte velkou chybu, pokud budete násobit 16

        z2 = (0x10000 / z);
        u2 = (int)(u * z2);
        v2 = (int)(v * z2);

        dz = (z2 - z1) / c; // ... a tady dělit (kompilátor použije posuny)
        du = (u2 - u1) / c;
        dv = (v2 - v1) / c;

        Interpolate_Segment(u1, v1, z1, du, dv, dz, video, video + c, z_buff);
    }
}

Tak, interpolate_segment() dostane souřadnice textury v 16:16 fixed pointu. Mohli byste je normálně převést na reálné číslo a vynásobit tak, aby daly index pixelu v textuře, ale to by bylo zbytečně pomalé. Je tu finta s bitovými posuny, ale je tu jedna podmínka : Textura musí mít rozměry o velikosti mocniny dvou. A teď už se podíváme jak to bude vypadat :


void Interpolate_Segment(int u1, int v1, int z1,
    int du, int dv, int dz, unsigned __int32 *video,
    unsigned __int32 *v2, __int32 *z_buff)
{
    do {
        if((unsigned)(*z_buff) > (unsigned)z1) {
            *video = _texture[((u1 >> 16) & 0xff) + ((v1 >> 8) & 0xff00)];
            *z_buff = z1;
        }
        // nanáší texturu

        video ++;
        z_buff ++;
        // nová pozice

        z1 += dz;
        u1 += du;
        v1 += dv;
        // interpolace souřadnic
    } while(video < v2);
}

Tak, je to stará smyčka ze Z-Bufferu, jen místo barvy se nanáší textura.. u1 >> 16 je převod souřadnice U (x-ová souřadnice v textuře) z 16:16 na reál a & 0xff je její oříznutí na interval hodnot 0 - 255 (textura bude mít šířku 256). Zápis v1 >> 8 je převod v1 na reál. a zároveň ještě jeho násobení 256-ti. Log. součin s 0xff00 je opět oříznutí. Dohromady to tedy dá 0xff + 0xff00 = 0xffff = 65536. To je 256 × 256, tedy i výška textury bude 256. Jak to ale změnit ? Je potřeba vygenerovat tři čísla : 0xff, 8 a 0xff00. Když to zkrátím bez vysvětlení, je to takhle :


Width = 256;
Height = 256;
0xff = Width - 1;
0xff00 = Width * (Height - 1);
8 = 16 - log(Height) / log(2);

Tak už to nečtite, stáhnite si zdroják a naschle příště :


z-buffer 2

 Z_Buffer + Texturing

zpět


Valid HTML 4.01!
Valid HTML 4.01