English Welcome Kecy Programování 3D - Engine Guestbook Odkazy Downloady O autorovi Napiš mi Mailform
box_cz 3D - Engine 03 - Direct-X a 3D Studio box_cz

Ahoj lidi ! Co s váma je ? Píšu už třetí díl a ještě ani dotaz ! To všechno chápete ? Nebo to snad nikdo nečte ? Pokud do čtvrtého dílu nikdo nenapíše tak končím. Tak si to rozmyslete ! No nic. Sliboval jsem dneska neco o DirectX a 3D - Studiu. Tak napřed ty directy. Tady si můžete stáhnout modul pro práci s DirectX. Běží to na directx 3.0 a vyšších. Nevím jak si poradí s Dx 8 ale to už snad nějak dopadne. Pro přidání tohoto modulu do vašeho projektu je potřeba zkopírovat soubory Directx.cpp a Directx.h do adresáře projektu, přidat je do něj a potom v menu Project >> Settings (Visual C ++ 6.0) v záložce Link přidáte do textového pole Object / library modules text "ddraw.lib" (možná si budete muset stáhnout knihovny ze sekce downloads) :

ddraw lib settings

Pak ještě musíte tam kde ho chcete používat na začátek souboru připsat #include "directx.h". Modul má tyto funkce :


HRESULT InitDirectX(HWND hwnd, int WIDTH, int HEIGHT);
void TerminateDirectX(void);
int InitVideoBuffer(unsigned __int32 **pole);
void FlipVideoBuffer(void);
char * err_to_string(HRESULT h);

InitDirectX
Jak už napovídá název funkce, inicializuje DirectX na rozlišení Width×Height v TrueColor. Přičemž hwnd je handle okna. Vrátí DD_OK (musíte přidat #include <ddraw.h>), pokud inicializace dopadla dobře.

TerminateDirctX
Zruší DirectX (musí být předtím inicializované)

InitVideoBuffer
Vrátí jedničku, pokud byl úspěšně otevřen video - buffer. Parametrem je pointer na pointer na long, do kterého se zapíše adresa videopaměti.

FlipVideoBuffer
Uzavře předtím otevřený video - buffer a zobrazí ho na monitoru.

err_to_string
Vrátí popis chyby odpovídající parametru h

No, vidíte ! Ani to nebolelo. Pak nebude těžké přepsat náš malý engine do DirectX a už to nebude tak hrozně blikat ;-) jako ve windozech. Ti kteří nekompilují v C ++ si zřejmě budou muset nahradit proměnné typu __int32 typem long (musí mít velikost 4 Byte). Nyní přijde malý příklad použití :


#include <windows.h>
#include <malloc.h>
#include <stdio.h>
#include <math.h>
#include "directx.h"

#define APPNAME "DX_Window"
#define WNDNAME "DX_Window"
#define width  400
#define height 300

unsigned __int32 *video;

int WINAPI WinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance, PSTR szCmdline, int nCmdShow)
{
    static char szAppName[] = APPNAME;
    HWND hwnd;
    MSG    msg;
    WNDCLASSEX wndclass;

    wndclass.cbSize = sizeof(wndclass);
    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = Wokno;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = 0;
    wndclass.lpszClassName = szAppName;
    wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    RegisterClassEx(&wndclass);

    hwnd = CreateWindow(szAppName,
        WNDNAME,
        WS_POPUP,
        CW_USEDEFAULT, CW_USEDEFAULT,
        width, height,
        NULL,
        NULL,
        hInstance,
        NULL);

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    ShowCursor(FALSE);

    InitDirectX(hwnd, width, height);
    // inicializovane direct x

    SetTimer(hwnd, 0, 40, NULL);
    // timer na 40 msec

    while(GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

LRESULT CALLBACK Wokno (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
    switch(iMsg)
    {
    case WM_KEYDOWN:
        if(wParam == 27){
            ShowCursor(TRUE);
            SendMessage(hwnd, WM_CLOSE, 0, 0);
            return 0;
        }
        return 0;

    case WM_TIMER:
        if(InitVideoBuffer(&video)){
            memset((char*)video, 0, sizeof(unsigned __int32) *
                width * height);

            // tady se muze kreslit
    
            FlipVideoBuffer();
        }
        return 0;

    case WM_DESTROY:
        TerminateDirectX();
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, iMsg, wParam, lParam);
}

Je to opravdu jednoduchý sampl otevření directx okna na rozlišení 400×300, kde se zobrazuje jen černá obrazovka. No nic, přijde i něco lepšího. Teď k tomu 3D Studiu. Tady si stáhněte 3ds_loader (děkuji autorovi stránky www.the-labs.com za hezkou tabulku 3DS tagů), jsou to zase dva soubory - load_3ds.cpp a load_3ds.h. Ty přidáte do projektu a load_3ds.h zase inkludujete. Když si otevřete load_3ds.h, uvidíte něco takového :


#ifndef __3DS_INCLUDED
#define __3DS_INCLUDED

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

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

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

typedef float Matrix[4][4];

struct Material {
    int n_id;
    char *name;
};

struct Light {
    int n_id;
    char *name;
    //
    int b_spot;
    int b_off;
    int b_cast_shadows;
    //
    Vector v_position;
    Vector v_target;
    //
    float f_hotspot;
    float f_faloff;
    //
    float f_inner_range;
    float f_outer_range;
    float f_multiplier;
    //
    Vector v_color;
    int r, g, b;
};

struct Face {
    int n_id, n_used;
    int n_material;
    //
    Vertex *vertex[3];
    //
    Plane normal;
};

struct Object {
    int n_id;
    char *name;
    //
    int n_vertex_num;
    int n_face_num;
    //
    Face *face;
    Vertex *vertex;
    //
    Matrix m_object;
    //
    int n_parent;
    int n_pos_num;
    int n_rot_num;
    //
    float *pt, *px, *py, *pz;
    float *qt, *qw, *qx, *qy, *qz;
};

struct Camera {
    int n_id;
    char *name;
    //
    Matrix m_camera;
    //
    float f_pos_x, f_pos_y, f_pos_z;
    float f_tar_x, f_tar_y, f_tar_z;
    float f_bank_angle;
    float f_focus; 
    //
    int n_target_num;
    int n_position_num;
    float *tt, *tx, *ty, *tz;
    float *pt, *px, *py, *pz;
};

struct World {
    int n_start_frame;
    int n_end_frame;
    //
    int n_object_num;
    int n_camera_num;
    int n_light_num;
    int n_material_num;
    //
    Object *object;
    Camera *camera;
    Light *light;
    Material *material;
};


World *Load_3DS(char *file_name);

#endif

Je tady deklarace struktur. Plane je rovina, definovaná její obecnou rovnicí. Vector je vektor v prostoru. Vertex je náš vrchol. Ten má svou pozici, pak souřadnice u a v, určených pro texturování a jeden vektor, který nás zatím nezajímá. Pak tam je matice, Material - to je textura, to nás taky zatím nezajímá, Light - světlo, to má id, jméno, pozici, barvu a pár dalších vlastností, ale ke světlům se dostaneme až za dlouho. Pak tam je Face - ta má několik vlastností a hlavně tři pointery na její vrcholy. Object je v podstatě seznam vrcholů a trojúhelníků, Camera je kamera, prozatím nás může zajímat jen její matice a struktura World to všechno sdružuje dohromady. Je tu jedna funkce pro nahrání světa ze souboru na disku. Teď by mělo být jednoduché napsat Wire-Frame krychli. Aby to bylo opravdu vymakaný, dáme tam i náš Backface culling se kterým pořád otravuju ale ještě jsem neřekl, jak to vlastně funguje. Tak pokud jste se dívali do nějakých papírů o lineární algebře, tak víte, že rovnice roviny zní nějak takhle :

    A * x + B * y + C * z + D = 0

Kde A, B a C jsou souřadnice normálového vektoru, to je vektor kolmý na tu rovinu, a D je vzdálenost roviny od počátku souřadného systému O (O = [0, 0, 0]) v násobcích délky toho vektoru (my ho pro jednoduchost máme jednotkový, takže D opravdu bude kolmá vzdálenost roviny od O) No a x, y a z jsou souřadnice bodu. Rovnice platí, leží-li bod na rovině. Kromě toho se dá zjistit, zda bud leží před nebo za rovinou - pokud vyjde levá strana rovnice kladná, je bod před rovinou a pokud záporná, je bod za ní. Už vás to napadlo ? Takže z matice kamery vezmeme její souřadnice a pokud rovnice vyjde kladná tak bude face vidět. Jestli jsem vám to neřekl tak face je druhé slovo pro polygon. Ale takhle by to fungovalo, kdyby se face netransformovala žádnou maticí. Ale face bude součástí rotující krychle a tak se bude transformovat podle matice objektu (bude rotovat) Co s tím ? Místo matice kamery se použije inverzní matice k matici transformační a z ní se vytáhne pozice kamery v prostoru objektu. Zmatené, co ? Radši to napíšu do céčka :


void DrawObject(Matrix *m_object, Matrix *m_camera,
        Object *p_object, unsigned __int32 *vram)
{
    float x, y, z, tx, ty, tz;
    Vertex vtx[3];
    Matrix m, im;
    int i, j;
    Face fac;
    
    fac.vertex[0] = &vtx[0];
    fac.vertex[1] = &vtx[1];
    fac.vertex[2] = &vtx[2];
    // vertexy ve faci

    Matrix_Inverse(m_camera, &m);
    Matrix_Multiply(&m, m_object, &m);
    Matrix_Inverse(&m, &im);
    // transformacni matice

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

        for(j = 0; j < 3; j ++) {
            x = p_object->face[i].vertex[j]->x;
            y = p_object->face[i].vertex[j]->y;
            z = p_object->face[i].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];
            // transformace

            if(tz > 0.1){
                tx = n_Width / 2 + (tx * z_delta) / tz;
                ty = n_Height / 2 + (ty * z_delta) / tz;
                fac.vertex[j]->x = tx;
                fac.vertex[j]->y = ty;
                fac.vertex[j]->z = tz;
            }
            // perspektivni korekce
        }
        
        Wire_Polygon(&fac, vram);
    }
}

Někteří možná postřehli, že se tady nekontroluje, zda nějaký vertex není mimo obrazovku. To je pravda. Když pracujete s facy, dělá se to trochu jinak. Ještě před perspektivní korekcí se face prožene přes tzv. klipovací pyramidu a vznikne polygon, který však už nemusí mít tři vrcholy, ale když face vychází nějakým vrcholem z obrazovky, přidá se tam další hrana, takže do Draw_Polygon se dostávají jen útvary, kompletně ležící na obrazovce. Řekl jsem něco jako klipovací pyramida. Co to je ? Je to čtveřice rovin, tvořících stěny pyramidy, které svírají úhel, který je závislý na perspektivním zkreslení. O tom však až příště. Přidávám ještě malý obrázek a už pomalu budeme končit ..

pyramid


Polygon vzniklý z face může mít maximálně sedm vrcholů. Schválně, kdo z vás první pošle obrázek s právě tímto případem. No. Už zbývá jen ona Draw_Line. Určitě byste dokázali navrhnout něco sami, ale tohle bude skoro určitě jednodušší (Jinak se tomu říká Brenshamův algoritmus).


void Line(int xa, int ya, int xb, int yb, unsigned __int32 *vram)
{
    float len, stepx, stepy, x, y;
    int i, xlen, ylen;

    x = (float)xa;
    y = (float)ya;
    xlen = xa - xb;
    ylen = ya - yb;
    len = (float)fabs(((fabs(ylen) > fabs(xlen))? ylen : xlen));
    stepx = xlen / len;
    stepy = ylen / len;

    for(i = 0; i < (int)len; i ++){
        if(x > 0 && x < n_Width && y > 0 && y < n_Height)
            vram[(int)x + n_Width * (int)y] = RGB(150, 255, 100); // MATRIX zelená
        x -= stepx;
        y -= stepy;
    }
}

No, to je celé :-) Ještě vám přidám jednoduchý sampl pro wire-frame grafiku pod direct X a konec ..

wire-engine

 Wire-frame engine

zpět


Valid HTML 4.01!
Valid HTML 4.01